mirror of
https://github.com/mentos1386/zdravko.git
synced 2024-11-21 23:33:34 +00:00
feat: ui improvements on the home page
This commit is contained in:
parent
d2c22b5403
commit
65e2d8fd73
9 changed files with 317 additions and 137 deletions
|
@ -13,7 +13,7 @@ import (
|
|||
|
||||
type IndexData struct {
|
||||
*components.Base
|
||||
Monitors map[string][]*Monitor
|
||||
Monitors map[string]MonitorsAndStatus
|
||||
MonitorsLength int
|
||||
TimeRange string
|
||||
Status models.MonitorStatus
|
||||
|
@ -26,12 +26,22 @@ type Monitor struct {
|
|||
History *History
|
||||
}
|
||||
|
||||
type HistoryItem struct {
|
||||
Status models.MonitorStatus
|
||||
Date time.Time
|
||||
}
|
||||
|
||||
type History struct {
|
||||
List []models.MonitorStatus
|
||||
List []HistoryItem
|
||||
Uptime int
|
||||
}
|
||||
|
||||
func getHour(date time.Time) string {
|
||||
type MonitorsAndStatus struct {
|
||||
Status models.MonitorStatus
|
||||
Monitors []*Monitor
|
||||
}
|
||||
|
||||
func getDateString(date time.Time) string {
|
||||
return date.UTC().Format("2006-01-02T15:04:05")
|
||||
}
|
||||
|
||||
|
@ -41,15 +51,15 @@ func getHistory(history []*models.MonitorHistory, period time.Duration, buckets
|
|||
numTotal := 0
|
||||
|
||||
for i := 0; i < buckets; i++ {
|
||||
datetime := getHour(time.Now().Add(period * time.Duration(-i)).Truncate(period))
|
||||
historyMap[datetime] = models.MonitorUnknown
|
||||
dateString := getDateString(time.Now().Add(period * time.Duration(-i)).Truncate(period))
|
||||
historyMap[dateString] = models.MonitorUnknown
|
||||
}
|
||||
|
||||
for _, _history := range history {
|
||||
hour := getHour(_history.CreatedAt.Time.Truncate(period))
|
||||
dateString := getDateString(_history.CreatedAt.Time.Truncate(period))
|
||||
|
||||
// Skip if not part of the "buckets"
|
||||
if _, ok := historyMap[hour]; !ok {
|
||||
if _, ok := historyMap[dateString]; !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -59,17 +69,23 @@ func getHistory(history []*models.MonitorHistory, period time.Duration, buckets
|
|||
}
|
||||
|
||||
// skip if it is already set to failure
|
||||
if historyMap[hour] == models.MonitorFailure {
|
||||
if historyMap[dateString] == models.MonitorFailure {
|
||||
continue
|
||||
}
|
||||
|
||||
historyMap[hour] = _history.Status
|
||||
// FIXME: This is wrong! As we can have multiple checks in dateString.
|
||||
// We should look at only the newest one.
|
||||
historyMap[dateString] = _history.Status
|
||||
}
|
||||
|
||||
historyHourly := make([]models.MonitorStatus, buckets)
|
||||
historyItems := make([]HistoryItem, buckets)
|
||||
for i := 0; i < buckets; i++ {
|
||||
datetime := getHour(time.Now().Add(period * time.Duration(-buckets+i+1)).Truncate(period))
|
||||
historyHourly[i] = historyMap[datetime]
|
||||
date := time.Now().Add(period * time.Duration(-buckets+i+1)).Truncate(period)
|
||||
datestring := getDateString(date)
|
||||
historyItems[i] = HistoryItem{
|
||||
Status: historyMap[datestring],
|
||||
Date: date,
|
||||
}
|
||||
}
|
||||
|
||||
uptime := 0
|
||||
|
@ -78,7 +94,7 @@ func getHistory(history []*models.MonitorHistory, period time.Duration, buckets
|
|||
}
|
||||
|
||||
return &History{
|
||||
List: historyHourly,
|
||||
List: historyItems,
|
||||
Uptime: uptime,
|
||||
}
|
||||
}
|
||||
|
@ -95,7 +111,8 @@ func (h *BaseHandler) Index(c echo.Context) error {
|
|||
timeRange = "90days"
|
||||
}
|
||||
|
||||
overallStatus := models.MonitorSuccess
|
||||
overallStatus := models.MonitorUnknown
|
||||
statusByGroup := make(map[string]models.MonitorStatus)
|
||||
|
||||
monitorsWithHistory := make([]*Monitor, len(monitors))
|
||||
for i, monitor := range monitors {
|
||||
|
@ -114,22 +131,38 @@ func (h *BaseHandler) Index(c echo.Context) error {
|
|||
historyResult = getHistory(history, time.Minute, 90)
|
||||
}
|
||||
|
||||
if statusByGroup[monitor.Group] == "" {
|
||||
statusByGroup[monitor.Group] = models.MonitorUnknown
|
||||
}
|
||||
|
||||
status := historyResult.List[len(historyResult.List)-1]
|
||||
if status != models.MonitorSuccess {
|
||||
overallStatus = status
|
||||
if status.Status == models.MonitorSuccess {
|
||||
if overallStatus == models.MonitorUnknown {
|
||||
overallStatus = status.Status
|
||||
}
|
||||
if statusByGroup[monitor.Group] == models.MonitorUnknown {
|
||||
statusByGroup[monitor.Group] = status.Status
|
||||
}
|
||||
}
|
||||
if status.Status != models.MonitorSuccess && status.Status != models.MonitorUnknown {
|
||||
overallStatus = status.Status
|
||||
statusByGroup[monitor.Group] = status.Status
|
||||
}
|
||||
|
||||
monitorsWithHistory[i] = &Monitor{
|
||||
Name: monitor.Name,
|
||||
Group: monitor.Group,
|
||||
Status: status,
|
||||
Status: status.Status,
|
||||
History: historyResult,
|
||||
}
|
||||
}
|
||||
|
||||
monitorsByGroup := map[string][]*Monitor{}
|
||||
monitorsByGroup := map[string]MonitorsAndStatus{}
|
||||
for _, monitor := range monitorsWithHistory {
|
||||
monitorsByGroup[monitor.Group] = append(monitorsByGroup[monitor.Group], monitor)
|
||||
monitorsByGroup[monitor.Group] = MonitorsAndStatus{
|
||||
Status: statusByGroup[monitor.Group],
|
||||
Monitors: append(monitorsByGroup[monitor.Group].Monitors, monitor),
|
||||
}
|
||||
}
|
||||
|
||||
return c.Render(http.StatusOK, "index.tmpl", &IndexData{
|
||||
|
|
|
@ -16,6 +16,21 @@ import (
|
|||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
const example = `
|
||||
import http from 'k6/http';
|
||||
|
||||
export const options = {
|
||||
thresholds: {
|
||||
// http errors should be less than 1%
|
||||
http_req_failed: ['rate<0.01'],
|
||||
},
|
||||
};
|
||||
|
||||
export default function () {
|
||||
http.get('https://example.com');
|
||||
}
|
||||
`
|
||||
|
||||
type CreateMonitor struct {
|
||||
Name string `validate:"required"`
|
||||
Group string `validate:"required"`
|
||||
|
@ -48,6 +63,11 @@ type SettingsMonitor struct {
|
|||
History []*models.MonitorHistory
|
||||
}
|
||||
|
||||
type SettingsMonitorCreate struct {
|
||||
*Settings
|
||||
Example string
|
||||
}
|
||||
|
||||
func (h *BaseHandler) SettingsMonitorsGET(c echo.Context) error {
|
||||
cc := c.(AuthenticatedContext)
|
||||
|
||||
|
@ -188,8 +208,8 @@ func (h *BaseHandler) SettingsMonitorsDescribePOST(c echo.Context) error {
|
|||
monitorId := c.Param("id")
|
||||
|
||||
update := UpdateMonitor{
|
||||
Group: c.FormValue("group"),
|
||||
WorkerGroups: strings.TrimSpace(c.FormValue("workergroups")),
|
||||
Group: strings.ToLower(c.FormValue("group")),
|
||||
WorkerGroups: strings.ToLower(strings.TrimSpace(c.FormValue("workergroups"))),
|
||||
Schedule: c.FormValue("schedule"),
|
||||
Script: c.FormValue("script"),
|
||||
}
|
||||
|
@ -251,14 +271,17 @@ func (h *BaseHandler) SettingsMonitorsDescribePOST(c echo.Context) error {
|
|||
func (h *BaseHandler) SettingsMonitorsCreateGET(c echo.Context) error {
|
||||
cc := c.(AuthenticatedContext)
|
||||
|
||||
return c.Render(http.StatusOK, "settings_monitors_create.tmpl", NewSettings(
|
||||
cc.Principal.User,
|
||||
GetPageByTitle(SettingsPages, "Monitors"),
|
||||
[]*components.Page{
|
||||
return c.Render(http.StatusOK, "settings_monitors_create.tmpl", &SettingsMonitorCreate{
|
||||
Settings: NewSettings(
|
||||
cc.Principal.User,
|
||||
GetPageByTitle(SettingsPages, "Monitors"),
|
||||
GetPageByTitle(SettingsPages, "Monitors Create"),
|
||||
},
|
||||
))
|
||||
[]*components.Page{
|
||||
GetPageByTitle(SettingsPages, "Monitors"),
|
||||
GetPageByTitle(SettingsPages, "Monitors Create"),
|
||||
},
|
||||
),
|
||||
Example: example,
|
||||
})
|
||||
}
|
||||
|
||||
func (h *BaseHandler) SettingsMonitorsCreatePOST(c echo.Context) error {
|
||||
|
@ -267,9 +290,9 @@ func (h *BaseHandler) SettingsMonitorsCreatePOST(c echo.Context) error {
|
|||
|
||||
create := CreateMonitor{
|
||||
Name: c.FormValue("name"),
|
||||
Group: c.FormValue("group"),
|
||||
Group: strings.ToLower(c.FormValue("group")),
|
||||
WorkerGroups: strings.ToLower(strings.TrimSpace(c.FormValue("workergroups"))),
|
||||
Schedule: c.FormValue("schedule"),
|
||||
WorkerGroups: c.FormValue("workergroups"),
|
||||
Script: c.FormValue("script"),
|
||||
}
|
||||
err := validator.New(validator.WithRequiredStructEnabled()).Struct(create)
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"code.tjo.space/mentos1386/zdravko/database/models"
|
||||
"code.tjo.space/mentos1386/zdravko/internal/jwt"
|
||||
|
@ -134,7 +135,7 @@ func (h *BaseHandler) SettingsWorkerGroupsCreatePOST(c echo.Context) error {
|
|||
id := slug.Make(c.FormValue("name"))
|
||||
|
||||
workerGroup := &models.WorkerGroup{
|
||||
Name: c.FormValue("name"),
|
||||
Name: strings.ToLower(c.FormValue("name")),
|
||||
Id: id,
|
||||
}
|
||||
|
||||
|
|
|
@ -56,9 +56,6 @@ code {
|
|||
@apply bg-blue-700 text-white;
|
||||
}
|
||||
|
||||
.monitors .monitors-list {
|
||||
@apply grid justify-items-stretch justify-stretch items-center bg-white shadow-md rounded-lg;
|
||||
}
|
||||
.monitors .time-range > a {
|
||||
@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;
|
||||
|
|
|
@ -590,6 +590,14 @@ video {
|
|||
}
|
||||
}
|
||||
|
||||
.absolute {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.z-50 {
|
||||
z-index: 50;
|
||||
}
|
||||
|
||||
.col-span-1 {
|
||||
grid-column: span 1 / span 1;
|
||||
}
|
||||
|
@ -598,6 +606,11 @@ video {
|
|||
grid-column: span 2 / span 2;
|
||||
}
|
||||
|
||||
.-mx-3 {
|
||||
margin-left: -0.75rem;
|
||||
margin-right: -0.75rem;
|
||||
}
|
||||
|
||||
.mx-1 {
|
||||
margin-left: 0.25rem;
|
||||
margin-right: 0.25rem;
|
||||
|
@ -613,6 +626,10 @@ video {
|
|||
margin-right: auto;
|
||||
}
|
||||
|
||||
.-ml-4 {
|
||||
margin-left: -1rem;
|
||||
}
|
||||
|
||||
.mb-2 {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
@ -641,6 +658,14 @@ video {
|
|||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
.mt-10 {
|
||||
margin-top: 2.5rem;
|
||||
}
|
||||
|
||||
.mt-2 {
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.mt-20 {
|
||||
margin-top: 5rem;
|
||||
}
|
||||
|
@ -768,6 +793,10 @@ video {
|
|||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.cursor-pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.grid-flow-col {
|
||||
grid-auto-flow: column;
|
||||
}
|
||||
|
@ -838,10 +867,6 @@ video {
|
|||
justify-self: end;
|
||||
}
|
||||
|
||||
.justify-self-center {
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.overflow-hidden {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
@ -882,6 +907,11 @@ video {
|
|||
border-bottom-width: 1px;
|
||||
}
|
||||
|
||||
.border-gray-100 {
|
||||
--tw-border-opacity: 1;
|
||||
border-color: rgb(243 244 246 / var(--tw-border-opacity));
|
||||
}
|
||||
|
||||
.border-gray-200 {
|
||||
--tw-border-opacity: 1;
|
||||
border-color: rgb(229 231 235 / var(--tw-border-opacity));
|
||||
|
@ -975,6 +1005,10 @@ video {
|
|||
padding: 0.625rem;
|
||||
}
|
||||
|
||||
.p-3 {
|
||||
padding: 0.75rem;
|
||||
}
|
||||
|
||||
.p-4 {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
@ -983,6 +1017,10 @@ video {
|
|||
padding: 1.25rem;
|
||||
}
|
||||
|
||||
.p-6 {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.px-2 {
|
||||
padding-left: 0.5rem;
|
||||
padding-right: 0.5rem;
|
||||
|
@ -1023,6 +1061,10 @@ video {
|
|||
padding-bottom: 2rem;
|
||||
}
|
||||
|
||||
.pb-2 {
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.pt-8 {
|
||||
padding-top: 2rem;
|
||||
}
|
||||
|
@ -1051,11 +1093,6 @@ video {
|
|||
line-height: 1.25rem;
|
||||
}
|
||||
|
||||
.text-xl {
|
||||
font-size: 1.25rem;
|
||||
line-height: 1.75rem;
|
||||
}
|
||||
|
||||
.text-xs {
|
||||
font-size: 0.75rem;
|
||||
line-height: 1rem;
|
||||
|
@ -1085,6 +1122,10 @@ video {
|
|||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.capitalize {
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.leading-5 {
|
||||
line-height: 1.25rem;
|
||||
}
|
||||
|
@ -1185,11 +1226,33 @@ video {
|
|||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
|
||||
}
|
||||
|
||||
.shadow-lg {
|
||||
--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
|
||||
--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);
|
||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
|
||||
}
|
||||
|
||||
.shadow-md {
|
||||
--tw-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
|
||||
--tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);
|
||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
|
||||
}
|
||||
|
||||
.blur {
|
||||
--tw-blur: blur(8px);
|
||||
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-property: all;
|
||||
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
transition-duration: 150ms;
|
||||
}
|
||||
|
||||
.duration-300 {
|
||||
transition-duration: 300ms;
|
||||
}
|
||||
|
||||
.link,
|
||||
p > a {
|
||||
font-weight: 500;
|
||||
|
@ -1332,19 +1395,6 @@ code {
|
|||
color: rgb(255 255 255 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.monitors .monitors-list {
|
||||
display: grid;
|
||||
align-items: center;
|
||||
justify-content: stretch;
|
||||
justify-items: stretch;
|
||||
border-radius: 0.5rem;
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(255 255 255 / var(--tw-bg-opacity));
|
||||
--tw-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
|
||||
--tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);
|
||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
|
||||
}
|
||||
|
||||
.monitors .time-range > a {
|
||||
border-radius: 0.5rem;
|
||||
padding-left: 0.625rem;
|
||||
|
@ -1626,6 +1676,19 @@ code {
|
|||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
.last-of-type\:border-0:last-of-type {
|
||||
border-width: 0px;
|
||||
}
|
||||
|
||||
.last-of-type\:pb-0:last-of-type {
|
||||
padding-bottom: 0px;
|
||||
}
|
||||
|
||||
.hover\:bg-blue-50:hover {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(239 246 255 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.hover\:bg-blue-800:hover {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(30 64 175 / var(--tw-bg-opacity));
|
||||
|
@ -1723,15 +1786,6 @@ code {
|
|||
margin-bottom: calc(0px * var(--tw-space-y-reverse));
|
||||
}
|
||||
|
||||
.sm\:justify-self-end {
|
||||
justify-self: end;
|
||||
}
|
||||
|
||||
.sm\:px-0 {
|
||||
padding-left: 0px;
|
||||
padding-right: 0px;
|
||||
}
|
||||
|
||||
.sm\:px-8 {
|
||||
padding-left: 2rem;
|
||||
padding-right: 2rem;
|
||||
|
@ -1751,6 +1805,10 @@ code {
|
|||
flex-direction: row;
|
||||
}
|
||||
|
||||
.md\:justify-end {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.md\:px-40 {
|
||||
padding-left: 10rem;
|
||||
padding-right: 10rem;
|
||||
|
@ -1799,3 +1857,16 @@ code {
|
|||
line-height: 2.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.\[\&_\.tooltip\]\:hover\:visible:hover .tooltip {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.\[\&_\.tooltip\]\:hover\:flex:hover .tooltip {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.\[\&_svg\]\:open\:-rotate-180[open] svg {
|
||||
--tw-rotate: -180deg;
|
||||
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
|
||||
}
|
||||
|
|
|
@ -51,7 +51,7 @@
|
|||
>
|
||||
<use href="/static/icons/feather-sprite.svg#alert-triangle" />
|
||||
</svg>
|
||||
<h3 class="text-slate-500 mt-4">Degraded performance</h3>
|
||||
<h1 class="text-slate-500 mt-4">Degraded performance</h1>
|
||||
<p class="text-slate-500 text-sm">
|
||||
Last updated on
|
||||
{{ Now.UTC.Format "Jan 02 at 15:04 MST" }}
|
||||
|
@ -59,49 +59,62 @@
|
|||
</div>
|
||||
{{ end }}
|
||||
<div class="monitors flex flex-col gap-4">
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 px-4 sm:px-0">
|
||||
<h2
|
||||
class="text-xl font-normal text-gray-800 text-center sm:text-left"
|
||||
<div
|
||||
class="inline-flex gap-1 justify-center md:justify-end time-range"
|
||||
role="group"
|
||||
>
|
||||
<a
|
||||
href="/?time-range=90days"
|
||||
class="{{ if eq .TimeRange "90days" }}active{{ end }}"
|
||||
type="button"
|
||||
>90 Days</a
|
||||
>
|
||||
Monitors
|
||||
</h2>
|
||||
<div
|
||||
class="inline-flex gap-1 justify-self-center sm:justify-self-end time-range"
|
||||
role="group"
|
||||
<a
|
||||
href="/?time-range=48hours"
|
||||
class="{{ if eq .TimeRange "48hours" }}active{{ end }}"
|
||||
type="button"
|
||||
>48 Hours</a
|
||||
>
|
||||
<a
|
||||
href="/?time-range=90minutes"
|
||||
class="{{ if eq .TimeRange "90minutes" }}active{{ end }}"
|
||||
type="button"
|
||||
>90 Minutes</a
|
||||
>
|
||||
<a
|
||||
href="/?time-range=90days"
|
||||
class="{{ if eq .TimeRange "90days" }}active{{ end }}"
|
||||
type="button"
|
||||
>90 Days</a
|
||||
>
|
||||
<a
|
||||
href="/?time-range=48hours"
|
||||
class="{{ if eq .TimeRange "48hours" }}active{{ end }}"
|
||||
type="button"
|
||||
>48 Hours</a
|
||||
>
|
||||
<a
|
||||
href="/?time-range=90minutes"
|
||||
class="{{ if eq .TimeRange "90minutes" }}active{{ end }}"
|
||||
type="button"
|
||||
>90 Minutes</a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
{{ range $group, $monitors := .Monitors }}
|
||||
<div class="monitors-list gap-2">
|
||||
<h3 class="flex flex-row gap-2 p-5 py-4 border-b border-gray-200">
|
||||
<span
|
||||
class="flex w-3 h-3 bg-green-400 rounded-full self-center"
|
||||
></span>
|
||||
<span class="flex-1 font-semibold uppercase ">{{ $group }}</span>
|
||||
<svg class="feather h-6 w-6 overflow-visible self-center">
|
||||
{{ range $group, $monitorsAndStatus := .Monitors }}
|
||||
<details
|
||||
class="bg-white shadow-md rounded-lg p-6 py-4 gap-2 [&_svg]:open:-rotate-180"
|
||||
>
|
||||
<summary
|
||||
class="flex flex-row gap-2 p-3 py-2 -mx-3 cursor-pointer hover:bg-blue-50 rounded-lg"
|
||||
>
|
||||
{{ if eq $monitorsAndStatus.Status "SUCCESS" }}
|
||||
<span
|
||||
class="flex w-3 h-3 bg-green-400 rounded-full self-center"
|
||||
></span>
|
||||
{{ else if eq $monitorsAndStatus.Status "FAILURE" }}
|
||||
<span
|
||||
class="flex w-3 h-3 bg-red-400 rounded-full self-center"
|
||||
></span>
|
||||
{{ else }}
|
||||
<span
|
||||
class="flex w-3 h-3 bg-gray-200 rounded-full self-center"
|
||||
></span>
|
||||
{{ end }}
|
||||
<h2 class="flex-1 font-semibold capitalize">
|
||||
{{ $group }}
|
||||
</h2>
|
||||
<svg
|
||||
class="feather h-6 w-6 overflow-visible self-center transition-all duration-300"
|
||||
>
|
||||
<use href="/static/icons/feather-sprite.svg#chevron-down" />
|
||||
</svg>
|
||||
</h3>
|
||||
{{ range $monitors }}
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-2 p-5 py-2">
|
||||
</summary>
|
||||
{{ range $monitorsAndStatus.Monitors }}
|
||||
<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"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
{{ if eq .Status "SUCCESS" }}
|
||||
<span class="flex w-3 h-3 bg-green-400 rounded-full"></span>
|
||||
|
@ -119,17 +132,47 @@
|
|||
class="grid gap-px col-span-2 grid-flow-col h-8 rounded overflow-hidden"
|
||||
>
|
||||
{{ range .History.List }}
|
||||
{{ if eq . "SUCCESS" }}
|
||||
<div
|
||||
class="has-tooltip [&_.tooltip]:hover:flex [&_.tooltip]:hover:visible flex"
|
||||
>
|
||||
{{ if eq .Status "SUCCESS" }}
|
||||
<div
|
||||
class="bg-green-400 hover:bg-green-500 flex-auto"
|
||||
></div>
|
||||
{{ else if eq .Status "FAILURE" }}
|
||||
<div
|
||||
class="bg-red-400 hover:bg-red-500 flex-auto"
|
||||
></div>
|
||||
{{ else }}
|
||||
<div
|
||||
class="bg-gray-200 hover:bg-gray-300 flex-auto"
|
||||
></div>
|
||||
{{ end }}
|
||||
<div
|
||||
class="bg-green-400 hover:bg-green-500 flex-auto"
|
||||
></div>
|
||||
{{ else if eq . "FAILURE" }}
|
||||
<div class="bg-red-400 hover:bg-red-500 flex-auto"></div>
|
||||
{{ else }}
|
||||
<div
|
||||
class="bg-gray-200 hover:bg-gray-300 flex-auto"
|
||||
></div>
|
||||
{{ end }}
|
||||
class="tooltip gap-2 bg-white border border-gray-200 rounded p-2 shadow-lg hidden z-50 absolute mt-10 -ml-4 flex-row text-xs"
|
||||
>
|
||||
{{ if eq .Status "SUCCESS" }}
|
||||
<span
|
||||
class="flex w-3 h-3 bg-green-400 rounded-full self-center"
|
||||
></span>
|
||||
{{ else if eq .Status "FAILURE" }}
|
||||
<span
|
||||
class="flex w-3 h-3 bg-red-400 rounded-full self-center"
|
||||
></span>
|
||||
{{ else }}
|
||||
<span
|
||||
class="flex w-3 h-3 bg-gray-200 rounded-full self-center"
|
||||
></span>
|
||||
{{ end }}
|
||||
{{ if eq $.TimeRange "90days" }}
|
||||
{{ .Date.Format "Jan 02" }}
|
||||
{{ else if eq $.TimeRange "48hours" }}
|
||||
{{ .Date.Format "Jan 02, 15:00 MST" }}
|
||||
{{ else if eq $.TimeRange "90minutes" }}
|
||||
{{ .Date.Format "Jan 02, 15:04 MST" }}
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
<div
|
||||
|
@ -150,7 +193,7 @@
|
|||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
</details>
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ end }}
|
||||
|
|
|
@ -50,7 +50,7 @@
|
|||
</caption>
|
||||
<thead class="text-xs text-gray-700 uppercase bg-gray-50">
|
||||
<tr>
|
||||
<th scope="col">Group</th>
|
||||
<th scope="col">Monitor Group</th>
|
||||
<th scope="col">Name</th>
|
||||
<th scope="col">Worker Groups</th>
|
||||
<th scope="col">Status</th>
|
||||
|
|
|
@ -4,14 +4,18 @@
|
|||
<label for="name">Name</label>
|
||||
<input type="text" name="name" id="name" placeholder="Github.com" />
|
||||
<p>Name of the monitor can be anything.</p>
|
||||
<label for="group">Group</label>
|
||||
<label list="existing-groups" for="group">Monitor Group</label>
|
||||
<input
|
||||
type="text"
|
||||
name="group"
|
||||
id="group"
|
||||
placeholder="default"
|
||||
value="default"
|
||||
required
|
||||
/>
|
||||
<datalist id="existing-groups">
|
||||
<option value="default"></option>
|
||||
</datalist>
|
||||
<p>
|
||||
Group monitors together. This affects how they are presented on the
|
||||
homepage.
|
||||
|
@ -22,6 +26,7 @@
|
|||
name="workergroups"
|
||||
id="workergroups"
|
||||
placeholder="NA EU"
|
||||
required
|
||||
/>
|
||||
<p>
|
||||
Worker groups are used to distribute the monitor to specific workers.
|
||||
|
@ -33,6 +38,7 @@
|
|||
id="schedule"
|
||||
placeholder="@every 1m"
|
||||
value="@every 1m"
|
||||
required
|
||||
/>
|
||||
<p>
|
||||
Schedule is a cron expression that defines when the monitor should be
|
||||
|
@ -44,10 +50,12 @@
|
|||
<code>@yearly</code>.
|
||||
</p>
|
||||
<label for="script">Script</label>
|
||||
<input required type="hidden" id="script" name="script" />
|
||||
<textarea required id="script" name="script" class="h-96">
|
||||
{{ .Example }}</textarea
|
||||
>
|
||||
<div
|
||||
id="editor"
|
||||
class="block w-full h-96 rounded-lg border border-gray-300 overflow-hidden"
|
||||
class="hidden block w-full h-96 rounded-lg border border-gray-300 overflow-hidden"
|
||||
></div>
|
||||
<p>
|
||||
Script is what determines the status of a service. You can read more
|
||||
|
@ -62,25 +70,16 @@
|
|||
|
||||
<script src="/static/monaco/vs/loader.js"></script>
|
||||
<script>
|
||||
script = `{{ .Example }}`;
|
||||
|
||||
document.getElementById("editor").classList.remove("hidden");
|
||||
document.getElementById("script").hidden = true;
|
||||
|
||||
function save() {
|
||||
const script = window.editor.getValue();
|
||||
document.getElementById("script").value = script;
|
||||
}
|
||||
|
||||
script = `import http from 'k6/http';
|
||||
|
||||
export const options = {
|
||||
thresholds: {
|
||||
// http errors should be less than 1%
|
||||
http_req_failed: ['rate<0.01'],
|
||||
},
|
||||
};
|
||||
|
||||
export default function () {
|
||||
http.get('https://example.com');
|
||||
}
|
||||
`;
|
||||
|
||||
require.config({ paths: { vs: "/static/monaco/vs" } });
|
||||
require(["vs/editor/editor.main"], function () {
|
||||
window.editor = monaco.editor.create(document.getElementById("editor"), {
|
||||
|
|
|
@ -2,8 +2,14 @@
|
|||
<section class="p-5">
|
||||
<form action="/settings/monitors/{{ .Monitor.Id }}" method="post">
|
||||
<h2>Configuration</h2>
|
||||
<label for="group">Group</label>
|
||||
<input type="text" name="group" id="group" value="{{ .Monitor.Group }}" />
|
||||
<label for="group">Monitor Group</label>
|
||||
<input
|
||||
type="text"
|
||||
name="group"
|
||||
id="group"
|
||||
value="{{ .Monitor.Group }}"
|
||||
required
|
||||
/>
|
||||
<p>
|
||||
Group monitors together. This affects how they are presented on the
|
||||
homepage.
|
||||
|
@ -14,6 +20,7 @@
|
|||
name="workergroups"
|
||||
id="workergroups"
|
||||
value="{{ range .Monitor.WorkerGroups }}{{ . }}{{ end }}"
|
||||
required
|
||||
/>
|
||||
<p>
|
||||
Worker groups are used to distribute the monitor to specific workers.
|
||||
|
@ -24,6 +31,7 @@
|
|||
name="schedule"
|
||||
id="schedule"
|
||||
value="{{ .Monitor.Schedule }}"
|
||||
required
|
||||
/>
|
||||
<p>
|
||||
Schedule is a cron expression that defines when the monitor should be
|
||||
|
@ -35,10 +43,12 @@
|
|||
<code>@yearly</code>.
|
||||
</p>
|
||||
<label for="script">Script</label>
|
||||
<input required type="hidden" id="script" name="script" />
|
||||
<textarea required id="script" name="script" class="h-96">
|
||||
{{ .Monitor.Script }}</textarea
|
||||
>
|
||||
<div
|
||||
id="editor"
|
||||
class="block w-full h-96 rounded-lg border border-gray-300 overflow-hidden"
|
||||
class="block w-full h-96 rounded-lg border border-gray-300 overflow-hidden hidden"
|
||||
></div>
|
||||
<p>
|
||||
Script is what determines the status of a service. You can read more
|
||||
|
@ -150,6 +160,9 @@
|
|||
|
||||
<script src="/static/monaco/vs/loader.js"></script>
|
||||
<script>
|
||||
document.getElementById("editor").classList.remove("hidden");
|
||||
document.getElementById("script").hidden = true;
|
||||
|
||||
function save() {
|
||||
const script = window.editor.getValue();
|
||||
document.getElementById('script').value = script;
|
||||
|
|
Loading…
Reference in a new issue