mirror of
https://github.com/mentos1386/zdravko.git
synced 2024-11-25 00:58:07 +00:00
feat: readability imrovements, new pages, script escaping
Progress towards finalizing the building blocks: targets, monitors, triggers, notifications and worker groups.
This commit is contained in:
parent
b6058d4201
commit
c153fe1b8f
25 changed files with 312 additions and 112 deletions
|
@ -9,6 +9,7 @@ import (
|
|||
"net/http"
|
||||
|
||||
"code.tjo.space/mentos1386/zdravko/database/models"
|
||||
"code.tjo.space/mentos1386/zdravko/internal/script"
|
||||
"code.tjo.space/mentos1386/zdravko/pkg/api"
|
||||
"code.tjo.space/mentos1386/zdravko/pkg/k6"
|
||||
)
|
||||
|
@ -23,7 +24,7 @@ type MonitorResult struct {
|
|||
}
|
||||
|
||||
func (a *Activities) Monitor(ctx context.Context, param HealtcheckParam) (*MonitorResult, error) {
|
||||
execution := k6.NewExecution(slog.Default(), param.Script)
|
||||
execution := k6.NewExecution(slog.Default(), script.UnescapeString(param.Script))
|
||||
|
||||
result, err := execution.Run(ctx)
|
||||
if err != nil {
|
||||
|
|
35
internal/handlers/examples.yaml
Normal file
35
internal/handlers/examples.yaml
Normal file
|
@ -0,0 +1,35 @@
|
|||
# Example trigger code
|
||||
trigger: |
|
||||
import kv from 'zdravko/kv';
|
||||
import slack from 'zdravko/notify/slack';
|
||||
|
||||
export default function (monitor, outcome) {
|
||||
// If the outcome is not failure, we can reset the counter.
|
||||
if (outcome.status !== 'FAILURE') {
|
||||
return kv.delete(`${monitor.name}:issues:5min`);
|
||||
}
|
||||
|
||||
const count = kv.get(`${monitor.name}:issues:5min`) || 0;
|
||||
|
||||
if (count > 5) {
|
||||
slack.notify(`${monitor.name} has had more than 5 issues in the last 5 minutes`);
|
||||
}
|
||||
|
||||
// Increment and set TTL to 5 minutes
|
||||
kv.increment(`${monitor.name}:issues:5min`, count + 1);
|
||||
}
|
||||
|
||||
# Example monitor code
|
||||
monitor: |
|
||||
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');
|
||||
}
|
|
@ -1,16 +1,27 @@
|
|||
package handlers
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"log/slog"
|
||||
|
||||
"code.tjo.space/mentos1386/zdravko/internal/config"
|
||||
"code.tjo.space/mentos1386/zdravko/internal/kv"
|
||||
"code.tjo.space/mentos1386/zdravko/internal/script"
|
||||
"code.tjo.space/mentos1386/zdravko/web/templates/components"
|
||||
"github.com/gorilla/sessions"
|
||||
"github.com/jmoiron/sqlx"
|
||||
"go.temporal.io/sdk/client"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
//go:embed examples.yaml
|
||||
var examplesYaml embed.FS
|
||||
|
||||
type examples struct {
|
||||
Monitor string `yaml:"monitor"`
|
||||
Trigger string `yaml:"trigger"`
|
||||
}
|
||||
|
||||
var Pages = []*components.Page{
|
||||
{Path: "/", Title: "Status", Breadcrumb: "Status"},
|
||||
{Path: "/incidents", Title: "Incidents", Breadcrumb: "Incidents"},
|
||||
|
@ -35,11 +46,26 @@ type BaseHandler struct {
|
|||
temporal client.Client
|
||||
|
||||
store *sessions.CookieStore
|
||||
|
||||
examples examples
|
||||
}
|
||||
|
||||
func NewBaseHandler(db *sqlx.DB, kvStore kv.KeyValueStore, temporal client.Client, config *config.ServerConfig, logger *slog.Logger) *BaseHandler {
|
||||
store := sessions.NewCookieStore([]byte(config.SessionSecret))
|
||||
|
||||
examples := examples{}
|
||||
yamlFile, err := examplesYaml.ReadFile("examples.yaml")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
err = yaml.Unmarshal(yamlFile, &examples)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
examples.Monitor = script.EscapeString(examples.Monitor)
|
||||
examples.Trigger = script.EscapeString(examples.Trigger)
|
||||
|
||||
return &BaseHandler{
|
||||
db: db,
|
||||
kvStore: kvStore,
|
||||
|
@ -47,5 +73,6 @@ func NewBaseHandler(db *sqlx.DB, kvStore kv.KeyValueStore, temporal client.Clien
|
|||
logger: logger,
|
||||
temporal: temporal,
|
||||
store: store,
|
||||
examples: examples,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,6 +31,8 @@ func NewSettings(user *AuthenticatedUser, page *components.Page, breadCrumbs []*
|
|||
|
||||
var SettingsPages = []*components.Page{
|
||||
{Path: "/settings", Title: "Overview", Breadcrumb: "Overview"},
|
||||
{Path: "/settings/targets", Title: "Targets", Breadcrumb: "Monitors"},
|
||||
{Path: "/settings/targets/create", Title: "Targets Create", Breadcrumb: "Create"},
|
||||
{Path: "/settings/monitors", Title: "Monitors", Breadcrumb: "Monitors"},
|
||||
{Path: "/settings/monitors/create", Title: "Monitors Create", Breadcrumb: "Create"},
|
||||
{Path: "/settings/worker-groups", Title: "Worker Groups", Breadcrumb: "Worker Groups"},
|
||||
|
@ -45,6 +47,7 @@ var SettingsPages = []*components.Page{
|
|||
|
||||
var SettingsNavbar = []*components.Page{
|
||||
GetPageByTitle(SettingsPages, "Overview"),
|
||||
GetPageByTitle(SettingsPages, "Targets"),
|
||||
GetPageByTitle(SettingsPages, "Monitors"),
|
||||
GetPageByTitle(SettingsPages, "Triggers"),
|
||||
GetPageByTitle(SettingsPages, "Notifications"),
|
||||
|
|
30
internal/handlers/settings_targets.go
Normal file
30
internal/handlers/settings_targets.go
Normal file
|
@ -0,0 +1,30 @@
|
|||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"code.tjo.space/mentos1386/zdravko/web/templates/components"
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
type Target struct{}
|
||||
|
||||
type SettingsTargets struct {
|
||||
*Settings
|
||||
Targets []*Target
|
||||
}
|
||||
|
||||
func (h *BaseHandler) SettingsTargetsGET(c echo.Context) error {
|
||||
cc := c.(AuthenticatedContext)
|
||||
|
||||
targets := make([]*Target, 0)
|
||||
|
||||
return c.Render(http.StatusOK, "settings_targets.tmpl", &SettingsTargets{
|
||||
Settings: NewSettings(
|
||||
cc.Principal.User,
|
||||
GetPageByTitle(SettingsPages, "Targets"),
|
||||
[]*components.Page{GetPageByTitle(SettingsPages, "Targets")},
|
||||
),
|
||||
Targets: targets,
|
||||
})
|
||||
}
|
|
@ -6,6 +6,7 @@ import (
|
|||
"net/http"
|
||||
|
||||
"code.tjo.space/mentos1386/zdravko/database/models"
|
||||
"code.tjo.space/mentos1386/zdravko/internal/script"
|
||||
"code.tjo.space/mentos1386/zdravko/internal/services"
|
||||
"code.tjo.space/mentos1386/zdravko/web/templates/components"
|
||||
"github.com/go-playground/validator/v10"
|
||||
|
@ -20,28 +21,6 @@ type SettingsIncidents struct {
|
|||
Incidents []*Incident
|
||||
}
|
||||
|
||||
// FIXME: This should be moved in to a files to not worry about so much escaping.
|
||||
const exampleTrigger = `
|
||||
import kv from 'zdravko/kv';
|
||||
import slack from 'zdravko/notify/slack';
|
||||
|
||||
export default function (monitor, outcome) {
|
||||
// If the outcome is not failure, we can reset the counter.
|
||||
if (outcome.status !== 'FAILURE') {
|
||||
return kv.delete(` + "\\`\\${monitor.name}:issues:5min\\`" + `);
|
||||
}
|
||||
|
||||
const count = kv.get(` + "\\`\\${monitor.name}:issues:5min\\`" + `) || 0;
|
||||
|
||||
if (count > 5) {
|
||||
slack.notify(` + "\\`\\${monitor.name} has had more than 5 issues in the last 5 minutes\\`" + `);
|
||||
}
|
||||
|
||||
// Increment and set TTL to 5 minutes
|
||||
kv.increment(` + "\\`\\${monitor.name}:issues:5min\\`" + `, count + 1);
|
||||
}
|
||||
`
|
||||
|
||||
type CreateTrigger struct {
|
||||
Name string `validate:"required"`
|
||||
Script string `validate:"required"`
|
||||
|
@ -160,7 +139,7 @@ func (h *BaseHandler) SettingsTriggersDescribePOST(c echo.Context) error {
|
|||
triggerId := c.Param("id")
|
||||
|
||||
update := UpdateTrigger{
|
||||
Script: c.FormValue("script"),
|
||||
Script: script.EscapeString(c.FormValue("script")),
|
||||
}
|
||||
err := validator.New(validator.WithRequiredStructEnabled()).Struct(update)
|
||||
if err != nil {
|
||||
|
@ -197,7 +176,7 @@ func (h *BaseHandler) SettingsTriggersCreateGET(c echo.Context) error {
|
|||
GetPageByTitle(SettingsPages, "Triggers Create"),
|
||||
},
|
||||
),
|
||||
Example: exampleTrigger,
|
||||
Example: h.examples.Trigger,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -207,7 +186,7 @@ func (h *BaseHandler) SettingsTriggersCreatePOST(c echo.Context) error {
|
|||
|
||||
create := CreateTrigger{
|
||||
Name: c.FormValue("name"),
|
||||
Script: c.FormValue("script"),
|
||||
Script: script.EscapeString(c.FormValue("script")),
|
||||
}
|
||||
err := validator.New(validator.WithRequiredStructEnabled()).Struct(create)
|
||||
if err != nil {
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"strings"
|
||||
|
||||
"code.tjo.space/mentos1386/zdravko/database/models"
|
||||
"code.tjo.space/mentos1386/zdravko/internal/script"
|
||||
"code.tjo.space/mentos1386/zdravko/internal/services"
|
||||
"code.tjo.space/mentos1386/zdravko/web/templates/components"
|
||||
"github.com/go-playground/validator/v10"
|
||||
|
@ -16,21 +17,6 @@ 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"`
|
||||
|
@ -211,7 +197,7 @@ func (h *BaseHandler) SettingsMonitorsDescribePOST(c echo.Context) error {
|
|||
Group: strings.ToLower(c.FormValue("group")),
|
||||
WorkerGroups: strings.ToLower(strings.TrimSpace(c.FormValue("workergroups"))),
|
||||
Schedule: c.FormValue("schedule"),
|
||||
Script: c.FormValue("script"),
|
||||
Script: script.EscapeString(c.FormValue("script")),
|
||||
}
|
||||
err := validator.New(validator.WithRequiredStructEnabled()).Struct(update)
|
||||
if err != nil {
|
||||
|
@ -280,7 +266,7 @@ func (h *BaseHandler) SettingsMonitorsCreateGET(c echo.Context) error {
|
|||
GetPageByTitle(SettingsPages, "Monitors Create"),
|
||||
},
|
||||
),
|
||||
Example: example,
|
||||
Example: h.examples.Monitor,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -293,7 +279,7 @@ func (h *BaseHandler) SettingsMonitorsCreatePOST(c echo.Context) error {
|
|||
Group: strings.ToLower(c.FormValue("group")),
|
||||
WorkerGroups: strings.ToLower(strings.TrimSpace(c.FormValue("workergroups"))),
|
||||
Schedule: c.FormValue("schedule"),
|
||||
Script: c.FormValue("script"),
|
||||
Script: script.EscapeString(c.FormValue("script")),
|
||||
}
|
||||
err := validator.New(validator.WithRequiredStructEnabled()).Struct(create)
|
||||
if err != nil {
|
||||
|
|
16
internal/script/script.go
Normal file
16
internal/script/script.go
Normal file
|
@ -0,0 +1,16 @@
|
|||
package script
|
||||
|
||||
import (
|
||||
"html"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func EscapeString(s string) string {
|
||||
re := regexp.MustCompile(`\r?\n`)
|
||||
return re.ReplaceAllString(html.EscapeString(s), `\n`)
|
||||
}
|
||||
|
||||
func UnescapeString(s string) string {
|
||||
return html.UnescapeString(strings.Replace(s, `\n`, "\n", -1))
|
||||
}
|
|
@ -63,6 +63,15 @@ func Routes(
|
|||
settings.GET("/triggers/:id/disable", h.SettingsTriggersDisableGET)
|
||||
settings.GET("/triggers/:id/enable", h.SettingsTriggersEnableGET)
|
||||
|
||||
settings.GET("/targets", h.SettingsTargetsGET)
|
||||
//settings.GET("/targets/create", h.SettingsTargetsCreateGET)
|
||||
//settings.POST("/targets/create", h.SettingsTargetsCreatePOST)
|
||||
//settings.GET("/targets/:id", h.SettingsTargetsDescribeGET)
|
||||
//settings.POST("/targets/:id", h.SettingsTargetsDescribePOST)
|
||||
//settings.GET("/targets/:id/delete", h.SettingsTargetsDescribeDELETE)
|
||||
//settings.GET("/targets/:id/disable", h.SettingsTargetsDisableGET)
|
||||
//settings.GET("/targets/:id/enable", h.SettingsTargetsEnableGET)
|
||||
|
||||
settings.GET("/monitors", h.SettingsMonitorsGET)
|
||||
settings.GET("/monitors/create", h.SettingsMonitorsCreateGET)
|
||||
settings.POST("/monitors/create", h.SettingsMonitorsCreatePOST)
|
||||
|
|
|
@ -79,7 +79,7 @@ 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 h-min;
|
||||
@apply relative overflow-x-auto shadow-md sm:rounded-lg text-gray-700 bg-white h-min;
|
||||
}
|
||||
|
||||
.settings section h2 {
|
||||
|
@ -110,7 +110,7 @@ code {
|
|||
@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;
|
||||
@apply mt-1 text-sm font-normal text-gray-700;
|
||||
}
|
||||
.settings section table thead {
|
||||
@apply text-xs text-gray-700 uppercase bg-gray-50;
|
||||
|
|
|
@ -833,10 +833,6 @@ video {
|
|||
justify-content: center;
|
||||
}
|
||||
|
||||
.justify-items-center {
|
||||
justify-items: center;
|
||||
}
|
||||
|
||||
.gap-1 {
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
@ -861,12 +857,6 @@ video {
|
|||
gap: 1px;
|
||||
}
|
||||
|
||||
.space-y-4 > :not([hidden]) ~ :not([hidden]) {
|
||||
--tw-space-y-reverse: 0;
|
||||
margin-top: calc(1rem * calc(1 - var(--tw-space-y-reverse)));
|
||||
margin-bottom: calc(1rem * var(--tw-space-y-reverse));
|
||||
}
|
||||
|
||||
.self-center {
|
||||
align-self: center;
|
||||
}
|
||||
|
@ -949,6 +939,11 @@ video {
|
|||
background-color: rgb(29 78 216 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-fuchsia-100 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(250 232 255 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-gray-100 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(243 244 246 / var(--tw-bg-opacity));
|
||||
|
@ -1173,6 +1168,11 @@ video {
|
|||
color: rgb(30 64 175 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.text-fuchsia-800 {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(134 25 143 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.text-gray-400 {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(156 163 175 / var(--tw-text-opacity));
|
||||
|
@ -1472,7 +1472,7 @@ code {
|
|||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(255 255 255 / var(--tw-bg-opacity));
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(107 114 128 / var(--tw-text-opacity));
|
||||
color: rgb(55 65 81 / var(--tw-text-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);
|
||||
|
@ -1624,7 +1624,7 @@ code {
|
|||
line-height: 1.25rem;
|
||||
font-weight: 400;
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(107 114 128 / var(--tw-text-opacity));
|
||||
color: rgb(55 65 81 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.settings section table thead {
|
||||
|
@ -1693,10 +1693,6 @@ code {
|
|||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
.\*\:w-full > * {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.last-of-type\:border-0:last-of-type {
|
||||
border-width: 0px;
|
||||
}
|
||||
|
@ -1801,12 +1797,6 @@ code {
|
|||
justify-content: center;
|
||||
}
|
||||
|
||||
.sm\:space-y-0 > :not([hidden]) ~ :not([hidden]) {
|
||||
--tw-space-y-reverse: 0;
|
||||
margin-top: calc(0px * calc(1 - var(--tw-space-y-reverse)));
|
||||
margin-bottom: calc(0px * var(--tw-space-y-reverse));
|
||||
}
|
||||
|
||||
.sm\:px-8 {
|
||||
padding-left: 2rem;
|
||||
padding-right: 2rem;
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
<title>Zdravko - {{ $title }}</title>
|
||||
<link rel="stylesheet" href="/static/css/tailwind.css" />
|
||||
</head>
|
||||
<body class="bg-gray-100 grid justify-items-center *:w-full">
|
||||
<body class="bg-gray-100 flex flex-col">
|
||||
<nav class="navbar">
|
||||
{{ range .Navbar }}
|
||||
<a
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
{{ if eq .Title "Temporal" }}target="_blank" rel="noreferrer noopener"{{ end }}
|
||||
>
|
||||
{{ .Title }}
|
||||
{{ if eq .Title "Targets" }}<span class="text-slate-400">(3)</span>{{ end }}
|
||||
{{ if eq .Title "Monitors" }}<span class="text-slate-400">(3)</span>{{ end }}
|
||||
{{ if eq .Title "Triggers" }}<span class="text-slate-400">(3)</span>{{ end }}
|
||||
{{ if eq .Title "Notifications" }}<span class="text-slate-400">(3)</span>{{ end }}
|
||||
|
|
|
@ -10,14 +10,12 @@
|
|||
There are no monitors yet.
|
||||
</h1>
|
||||
<p
|
||||
class="mb-8 text-l font-normal text-gray-500 lg:text-l sm:px-8 lg:px-40"
|
||||
class="mb-8 text-l font-normal text-gray-700 lg:text-l sm:px-8 lg:px-40"
|
||||
>
|
||||
Create a monitor to monitor your services and get notified when they
|
||||
are down.
|
||||
</p>
|
||||
<div
|
||||
class="flex flex-col space-y-4 sm:flex-row sm:justify-center sm:space-y-0"
|
||||
>
|
||||
<div class="flex flex-col gap-4 sm:flex-row sm:justify-center">
|
||||
<a
|
||||
href="/settings/monitors/create"
|
||||
class="inline-flex justify-center items-center py-3 px-5 text-base font-medium text-center text-white rounded-lg bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300"
|
||||
|
@ -38,8 +36,8 @@
|
|||
>
|
||||
<use href="/static/icons/feather-sprite.svg#check" />
|
||||
</svg>
|
||||
<h1 class="text-slate-500 mt-4">All services are online</h1>
|
||||
<p class="text-slate-500 text-sm">
|
||||
<h1 class="text-gray-700 mt-4">All services are online</h1>
|
||||
<p class="text-gray-700 text-sm">
|
||||
Last updated on
|
||||
{{ Now.UTC.Format "Jan 02 at 15:04 MST" }}
|
||||
</p>
|
||||
|
@ -51,8 +49,8 @@
|
|||
>
|
||||
<use href="/static/icons/feather-sprite.svg#alert-triangle" />
|
||||
</svg>
|
||||
<h1 class="text-slate-500 mt-4">Degraded performance</h1>
|
||||
<p class="text-slate-500 text-sm">
|
||||
<h1 class="text-gray-700 mt-4">Degraded performance</h1>
|
||||
<p class="text-gray-700 text-sm">
|
||||
Last updated on
|
||||
{{ Now.UTC.Format "Jan 02 at 15:04 MST" }}
|
||||
</p>
|
||||
|
@ -83,7 +81,8 @@
|
|||
>
|
||||
</div>
|
||||
{{ range $group, $monitorsAndStatus := .Monitors }}
|
||||
<details open
|
||||
<details
|
||||
open
|
||||
class="bg-white shadow-md rounded-lg p-6 py-4 gap-2 [&_svg]:open:rotate-90"
|
||||
>
|
||||
<summary
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{{ define "settings" }}
|
||||
{{ $description := "Monitors are constantly checking weather a service is online and working correctly." }}
|
||||
{{ $description := "Monitors are constantly determining if targets are healthy or not." }}
|
||||
|
||||
{{ $length := len .Monitors }}
|
||||
{{ if eq $length 0 }}
|
||||
|
@ -10,13 +10,11 @@
|
|||
There are no monitors yet.
|
||||
</h1>
|
||||
<p
|
||||
class="mb-8 text-l font-normal text-gray-500 lg:text-l sm:px-8 lg:px-40"
|
||||
class="mb-8 text-l font-normal text-gray-700 lg:text-l sm:px-8 lg:px-40"
|
||||
>
|
||||
{{ $description }}
|
||||
</p>
|
||||
<div
|
||||
class="flex flex-col space-y-4 sm:flex-row sm:justify-center sm:space-y-0"
|
||||
>
|
||||
<div class="flex flex-col gap-4 sm:flex-row sm:justify-center">
|
||||
<a
|
||||
href="/settings/monitors/create"
|
||||
class="inline-flex justify-center items-center py-3 px-5 text-base font-medium text-center text-white rounded-lg bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300"
|
||||
|
@ -52,6 +50,7 @@
|
|||
<tr>
|
||||
<th scope="col">Monitor Group</th>
|
||||
<th scope="col">Name</th>
|
||||
<th scope="col">Visibility</th>
|
||||
<th scope="col">Worker Groups</th>
|
||||
<th scope="col">Status</th>
|
||||
<th scope="col">Schedule</th>
|
||||
|
@ -70,6 +69,7 @@
|
|||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
{{ range $group, $monitors := $.Monitors }}
|
||||
{{ if eq $group $currentGroup }}
|
||||
|
@ -79,6 +79,18 @@
|
|||
<th scope="row">
|
||||
{{ .Name }}
|
||||
</th>
|
||||
<td>
|
||||
<span
|
||||
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-blue-100 text-blue-800"
|
||||
>
|
||||
Public
|
||||
</span>
|
||||
<span
|
||||
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-fuchsia-100 text-fuchsia-800"
|
||||
>
|
||||
Private
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
{{ range .WorkerGroups }}
|
||||
<span
|
||||
|
|
|
@ -51,7 +51,7 @@
|
|||
</p>
|
||||
<label for="script">Script</label>
|
||||
<textarea required id="script" name="script" class="h-96">
|
||||
{{ .Example }}</textarea
|
||||
{{ ScriptUnescapeString .Example }}</textarea
|
||||
>
|
||||
<div
|
||||
id="editor"
|
||||
|
@ -70,7 +70,11 @@
|
|||
|
||||
<script src="/static/monaco/vs/loader.js"></script>
|
||||
<script>
|
||||
script = `{{ .Example }}`;
|
||||
function htmlDecode(input) {
|
||||
var doc = new DOMParser().parseFromString(input, "text/html");
|
||||
return doc.documentElement.textContent;
|
||||
}
|
||||
script = htmlDecode("{{ .Example }}");
|
||||
|
||||
document.getElementById("editor").classList.remove("hidden");
|
||||
document.getElementById("script").hidden = true;
|
||||
|
|
|
@ -44,7 +44,7 @@
|
|||
</p>
|
||||
<label for="script">Script</label>
|
||||
<textarea required id="script" name="script" class="h-96">
|
||||
{{ .Monitor.Script }}</textarea
|
||||
{{ ScriptUnescapeString .Monitor.Script }}</textarea
|
||||
>
|
||||
<div
|
||||
id="editor"
|
||||
|
@ -168,7 +168,11 @@
|
|||
document.getElementById('script').value = script;
|
||||
}
|
||||
|
||||
script = `{{ .Monitor.Script }}`
|
||||
function htmlDecode(input) {
|
||||
var doc = new DOMParser().parseFromString(input, "text/html");
|
||||
return doc.documentElement.textContent;
|
||||
}
|
||||
script = htmlDecode("{{ .Monitor.Script }}")
|
||||
|
||||
require.config({ paths: { vs: '/static/monaco/vs' } });
|
||||
require(['vs/editor/editor.main'], function () {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{{ define "settings" }}
|
||||
{{ $description := "Notifications are connections to other services to notify about events." }}
|
||||
{{ $description := "Notifications are connections to other services to notify about events. This can be slack messages, emails, webhooks, anything." }}
|
||||
|
||||
{{ $length := len .Notifications }}
|
||||
{{ if eq $length 0 }}
|
||||
|
@ -10,13 +10,11 @@
|
|||
There are no notifications yet.
|
||||
</h1>
|
||||
<p
|
||||
class="mb-8 text-l font-normal text-gray-500 lg:text-l sm:px-8 lg:px-40"
|
||||
class="mb-8 text-l font-normal text-gray-700 lg:text-l sm:px-8 lg:px-40"
|
||||
>
|
||||
{{ $description }}
|
||||
</p>
|
||||
<div
|
||||
class="flex flex-col space-y-4 sm:flex-row sm:justify-center sm:space-y-0"
|
||||
>
|
||||
<div class="flex flex-col gap-4 sm:flex-row sm:justify-center">
|
||||
<a
|
||||
href="/settings/notifications/create"
|
||||
class="inline-flex justify-center items-center py-3 px-5 text-base font-medium text-center text-white rounded-lg bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300"
|
||||
|
|
|
@ -15,10 +15,8 @@
|
|||
<div
|
||||
class="inline-block bg-white rounded-lg shadow p-5 text-center sm:text-left"
|
||||
>
|
||||
<h3 class="text-sm leading-6 font-medium text-gray-400">
|
||||
Total Worker Groups
|
||||
</h3>
|
||||
<p class="text-3xl font-bold text-black">{{ .WorkerGroupsCount }}</p>
|
||||
<h3 class="text-sm leading-6 font-medium text-gray-400">Total Targets</h3>
|
||||
<p class="text-3xl font-bold text-black">{{ .MonitorsCount }}</p>
|
||||
</div>
|
||||
<div
|
||||
class="inline-block bg-white rounded-lg shadow p-5 text-center sm:text-left"
|
||||
|
@ -28,6 +26,14 @@
|
|||
</h3>
|
||||
<p class="text-3xl font-bold text-black">{{ .MonitorsCount }}</p>
|
||||
</div>
|
||||
<div
|
||||
class="inline-block bg-white rounded-lg shadow p-5 text-center sm:text-left"
|
||||
>
|
||||
<h3 class="text-sm leading-6 font-medium text-gray-400">
|
||||
Total Worker Groups
|
||||
</h3>
|
||||
<p class="text-3xl font-bold text-black">{{ .WorkerGroupsCount }}</p>
|
||||
</div>
|
||||
<div
|
||||
class="inline-block bg-white rounded-lg shadow p-5 text-center sm:text-left"
|
||||
>
|
||||
|
|
87
web/templates/pages/settings_targets.tmpl
Normal file
87
web/templates/pages/settings_targets.tmpl
Normal file
|
@ -0,0 +1,87 @@
|
|||
{{ define "settings" }}
|
||||
{{ $description := "Targets are definitions of what will be monitored. This can include webpages, servers, anything. You can also use discovery mechanisem to automatically add targets from Kubernetes, Docker, Terraform, DNS and other sources." }}
|
||||
|
||||
{{ $length := len .Targets }}
|
||||
{{ if eq $length 0 }}
|
||||
<div class="py-8 px-4 mx-auto max-w-screen-xl text-center lg:py-16">
|
||||
<h1
|
||||
class="mb-4 text-2xl font-extrabold tracking-tight leading-none text-gray-900 md:text-3xl lg:text-4xl"
|
||||
>
|
||||
There are no targets yet.
|
||||
</h1>
|
||||
<p
|
||||
class="mb-8 text-l font-normal text-gray-700 lg:text-l sm:px-8 lg:px-40"
|
||||
>
|
||||
{{ $description }}
|
||||
</p>
|
||||
<div class="flex flex-col gap-4 sm:flex-row sm:justify-center">
|
||||
<a
|
||||
href="/settings/targets/create"
|
||||
class="inline-flex justify-center items-center py-3 px-5 text-base font-medium text-center text-white rounded-lg bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300"
|
||||
>
|
||||
Add Manual Target
|
||||
<svg class="feather ml-1 h-5 w-5 overflow-visible">
|
||||
<use href="/static/icons/feather-sprite.svg#plus" />
|
||||
</svg>
|
||||
</a>
|
||||
<a
|
||||
href="/settings/targets/create"
|
||||
class="inline-flex justify-center items-center py-3 px-5 text-base font-medium text-center text-white rounded-lg bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300"
|
||||
>
|
||||
Add Auto Discovered Targets
|
||||
<svg class="feather ml-1 h-5 w-5 overflow-visible">
|
||||
<use href="/static/icons/feather-sprite.svg#plus" />
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{{ else }}
|
||||
<section>
|
||||
<table>
|
||||
<caption>
|
||||
List of Targets
|
||||
<div class="mt-1 gap-4 grid grid-cols-1 md:grid-cols-[1fr,20%]">
|
||||
<p>
|
||||
{{ $description }}
|
||||
</p>
|
||||
<a
|
||||
href="/settings/targets/create"
|
||||
class="h-min inline-flex justify-center items-center py-2 px-4 text-sm font-medium text-center text-white rounded-lg bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300"
|
||||
>
|
||||
Create New
|
||||
<svg class="feather h-5 w-5 overflow-visible">
|
||||
<use href="/static/icons/feather-sprite.svg#plus" />
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Type</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
{{ range .Targets }}
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="row">
|
||||
{{ .Name }}
|
||||
</th>
|
||||
<td>
|
||||
<span
|
||||
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-blue-100 text-blue-800"
|
||||
>
|
||||
{{ .Type }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<a href="/settings/targets/{{ .Id }}" class="link">Details</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
{{ end }}
|
||||
</table>
|
||||
</section>
|
||||
{{ end }}
|
||||
{{ end }}
|
|
@ -1,5 +1,5 @@
|
|||
{{ define "settings" }}
|
||||
{{ $description := "Triggers can be manually created or based on monitors. They are used to notify about issues." }}
|
||||
{{ $description := "Triggers process monitor outcomes and determine if an incident should be created, updated or closed." }}
|
||||
|
||||
{{ $length := len .Triggers }}
|
||||
{{ if eq $length 0 }}
|
||||
|
@ -10,13 +10,11 @@
|
|||
There are no triggers yet.
|
||||
</h1>
|
||||
<p
|
||||
class="mb-8 text-l font-normal text-gray-500 lg:text-l sm:px-8 lg:px-40"
|
||||
class="mb-8 text-l font-normal text-gray-700 lg:text-l sm:px-8 lg:px-40"
|
||||
>
|
||||
{{ $description }}
|
||||
</p>
|
||||
<div
|
||||
class="flex flex-col space-y-4 sm:flex-row sm:justify-center sm:space-y-0"
|
||||
>
|
||||
<div class="flex flex-col gap-4 sm:flex-row sm:justify-center">
|
||||
<a
|
||||
href="/settings/triggers/create"
|
||||
class="inline-flex justify-center items-center py-3 px-5 text-base font-medium text-center text-white rounded-lg bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300"
|
||||
|
|
|
@ -2,11 +2,16 @@
|
|||
<section class="p-5">
|
||||
<form action="/settings/triggers/create" method="post">
|
||||
<label for="name">Name</label>
|
||||
<input type="text" name="name" id="name" value="Five subsequent failures trigger notification" />
|
||||
<input
|
||||
type="text"
|
||||
name="name"
|
||||
id="name"
|
||||
value="Five subsequent failures trigger notification"
|
||||
/>
|
||||
<p>Name of the trigger can be anything.</p>
|
||||
<label for="script">Script</label>
|
||||
<textarea required id="script" name="script" class="h-96">
|
||||
{{ .Example }}</textarea
|
||||
{{ ScriptUnescapeString .Example }}</textarea
|
||||
>
|
||||
<div
|
||||
id="editor"
|
||||
|
@ -25,7 +30,11 @@
|
|||
|
||||
<script src="/static/monaco/vs/loader.js"></script>
|
||||
<script>
|
||||
script = `{{ .Example }}`;
|
||||
function htmlDecode(input) {
|
||||
var doc = new DOMParser().parseFromString(input, "text/html");
|
||||
return doc.documentElement.textContent;
|
||||
}
|
||||
script = htmlDecode("{{ .Example }}");
|
||||
|
||||
document.getElementById("editor").classList.remove("hidden");
|
||||
document.getElementById("script").hidden = true;
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<h2>Configuration</h2>
|
||||
<label for="script">Script</label>
|
||||
<textarea required id="script" name="script" class="h-96">
|
||||
{{ .Trigger.Script }}</textarea
|
||||
{{ ScriptUnescapeString .Trigger.Script }}</textarea
|
||||
>
|
||||
<div
|
||||
id="editor"
|
||||
|
@ -120,7 +120,11 @@
|
|||
document.getElementById('script').value = script;
|
||||
}
|
||||
|
||||
script = `{{ .Trigger.Script }}`
|
||||
function htmlDecode(input) {
|
||||
var doc = new DOMParser().parseFromString(input, "text/html");
|
||||
return doc.documentElement.textContent;
|
||||
}
|
||||
script = htmlDecode("{{ .Trigger.Script }}")
|
||||
|
||||
require.config({ paths: { vs: '/static/monaco/vs' } });
|
||||
require(['vs/editor/editor.main'], function () {
|
||||
|
|
|
@ -10,13 +10,11 @@
|
|||
There are no worker groups yet.
|
||||
</h1>
|
||||
<p
|
||||
class="mb-8 text-l font-normal text-gray-500 lg:text-l sm:px-8 lg:px-40"
|
||||
class="mb-8 text-l font-normal text-gray-700 lg:text-l sm:px-8 lg:px-40"
|
||||
>
|
||||
{{ $description }}
|
||||
</p>
|
||||
<div
|
||||
class="flex flex-col space-y-4 sm:flex-row sm:justify-center sm:space-y-0"
|
||||
>
|
||||
<div class="flex flex-col gap-4 sm:flex-row sm:justify-center">
|
||||
<a
|
||||
href="/settings/worker-groups/create"
|
||||
class="inline-flex justify-center items-center py-3 px-5 text-base font-medium text-center text-white rounded-lg bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300"
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"text/template"
|
||||
"time"
|
||||
|
||||
"code.tjo.space/mentos1386/zdravko/internal/script"
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
|
@ -25,8 +26,10 @@ func load(files ...string) *template.Template {
|
|||
|
||||
t := template.New("default").Funcs(
|
||||
template.FuncMap{
|
||||
"StringsJoin": strings.Join,
|
||||
"Now": time.Now,
|
||||
"StringsJoin": strings.Join,
|
||||
"Now": time.Now,
|
||||
"ScriptUnescapeString": script.UnescapeString,
|
||||
"ScriptEscapeString": script.EscapeString,
|
||||
})
|
||||
|
||||
return template.Must(t.ParseFS(templates, files...))
|
||||
|
@ -47,6 +50,7 @@ func NewTemplates() *Templates {
|
|||
"settings_triggers.tmpl": loadSettings("pages/settings_triggers.tmpl"),
|
||||
"settings_triggers_create.tmpl": loadSettings("pages/settings_triggers_create.tmpl"),
|
||||
"settings_triggers_describe.tmpl": loadSettings("pages/settings_triggers_describe.tmpl"),
|
||||
"settings_targets.tmpl": loadSettings("pages/settings_targets.tmpl"),
|
||||
"settings_notifications.tmpl": loadSettings("pages/settings_notifications.tmpl"),
|
||||
"settings_worker_groups.tmpl": loadSettings("pages/settings_worker_groups.tmpl"),
|
||||
"settings_worker_groups_create.tmpl": loadSettings("pages/settings_worker_groups_create.tmpl"),
|
||||
|
|
Loading…
Reference in a new issue