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:
Tine 2024-05-14 21:38:45 +02:00
parent b6058d4201
commit c153fe1b8f
Signed by: mentos1386
SSH key fingerprint: SHA256:MNtTsLbihYaWF8j1fkOHfkKNlnN1JQfxEU/rBU8nCGw
25 changed files with 312 additions and 112 deletions

View file

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

View 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');
}

View file

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

View file

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

View 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,
})
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View 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 }}

View file

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

View file

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

View file

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

View file

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

View file

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