mirror of
https://github.com/airlabspl/uptimemonitor.git
synced 2025-08-14 20:29:16 +02:00
initial commit
This commit is contained in:
76
html/app.html
Normal file
76
html/app.html
Normal file
@@ -0,0 +1,76 @@
|
||||
{{ define "body" }}
|
||||
<div class="flex flex-col gap-2 min-h-screen">
|
||||
{{ template "nav" . }}
|
||||
<main class="flex-1">
|
||||
{{ block "content" . }}{{ end }}
|
||||
</main>
|
||||
{{ template "footer" . }}
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
{{ define "nav" }}
|
||||
<div class="max-w-[1600px] w-full mx-auto">
|
||||
<div class="flex justify-between gap-8 items-center py-2 p-4 pb-0">
|
||||
<div class="flex-1">
|
||||
<div class="breadcrumbs text-xs">
|
||||
<ul>
|
||||
{{ block "breadcrumbs" . }}{{ end }}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="shrink-0">
|
||||
<a class="btn btn-neutral" href="/new" hx-boost preload="mouseover">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="size-5!">
|
||||
<path
|
||||
d="M2 4.25A2.25 2.25 0 0 1 4.25 2h2.5A2.25 2.25 0 0 1 9 4.25v2.5A2.25 2.25 0 0 1 6.75 9h-2.5A2.25 2.25 0 0 1 2 6.75v-2.5ZM2 13.25A2.25 2.25 0 0 1 4.25 11h2.5A2.25 2.25 0 0 1 9 13.25v2.5A2.25 2.25 0 0 1 6.75 18h-2.5A2.25 2.25 0 0 1 2 15.75v-2.5ZM11 4.25A2.25 2.25 0 0 1 13.25 2h2.5A2.25 2.25 0 0 1 18 4.25v2.5A2.25 2.25 0 0 1 15.75 9h-2.5A2.25 2.25 0 0 1 11 6.75v-2.5ZM15.25 11.75a.75.75 0 0 0-1.5 0v2h-2a.75.75 0 0 0 0 1.5h2v2a.75.75 0 0 0 1.5 0v-2h2a.75.75 0 0 0 0-1.5h-2v-2Z" />
|
||||
</svg>
|
||||
|
||||
<span class="">
|
||||
Create
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
{{ define "footer" }}
|
||||
<div class="max-w-[1600px] mx-auto w-full p-4 pt-10">
|
||||
<footer class="text-sm text-base-content p-4 flex flex-col md:flex-row gap-12 md:gap-4">
|
||||
<nav class="flex flex-col gap-2 flex-1">
|
||||
<h6 class="footer-title truncate">{{ .User.Name }}</h6>
|
||||
<a class="link link-hover">Account settings</a>
|
||||
<a class="link link-hover">Manage users</a>
|
||||
<a class="link link-hover" href="/logout">Log out</a>
|
||||
</nav>
|
||||
<nav class="flex flex-col gap-2 flex-1">
|
||||
<h6 class="footer-title">UPTIMEMONITOR</h6>
|
||||
<a class="link link-hover">Documentation</a>
|
||||
<a class="link link-hover">Pricing</a>
|
||||
<a class="link link-hover">Terms of use</a>
|
||||
</nav>
|
||||
<nav class="flex flex-col gap-2 flex-1">
|
||||
<h6 class="footer-title">AIR Labs</h6>
|
||||
<a class="link link-hover">About us</a>
|
||||
<a class="link link-hover">Other products</a>
|
||||
<a class="link link-hover flex items-center gap-1" href="https://x.com/airlabspl" target="_blank">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 300 300" version="1.1" class="size-[11px]!">
|
||||
<path
|
||||
d="M178.57 127.15 290.27 0h-26.46l-97.03 110.38L89.34 0H0l117.13 166.93L0 300.25h26.46l102.4-116.59 81.8 116.59h89.34M36.01 19.54H76.66l187.13 262.13h-40.66" />
|
||||
</svg>
|
||||
<div>Account</div>
|
||||
</a>
|
||||
</nav>
|
||||
</footer>
|
||||
<footer class="footer text-base-content p-4">
|
||||
<aside class="flex flex-col gap-3">
|
||||
<div>
|
||||
{{ template "logo" . }}
|
||||
</div>
|
||||
<div>
|
||||
© 2025 AIR Labs
|
||||
</div>
|
||||
</aside>
|
||||
</footer>
|
||||
</div>
|
||||
{{ end }}
|
57
html/check.html
Normal file
57
html/check.html
Normal file
@@ -0,0 +1,57 @@
|
||||
{{ define "check_list" }}
|
||||
<div class="flex flex-col gap-1" hx-swap="outerHTML" hx-get="/monitors/{{ .Monitor.ID }}/checks" hx-trigger="every 60s">
|
||||
<div class="flex justify-between items-center w-full gap-1.5 relative">
|
||||
{{ $checkMaxIndex := sub (len $.Checks) 1 }}
|
||||
{{ range $i, $v := .Skeletons }}
|
||||
{{ $currentIndex := sub 59 $i }}
|
||||
{{ $check := "" }}
|
||||
{{ if lt $currentIndex (len $.Checks) }}
|
||||
{{ $check = index $.Checks $currentIndex }}
|
||||
{{ end }}
|
||||
<div
|
||||
class="flex-1 tooltip justify-center items-center {{ if gt $i 30 }} {{ else if gt $i 25 }} hidden md:inline-flex {{ else }} hidden lg:inline-flex {{ end }} ">
|
||||
<div class="tooltip-content tooltip-bottom flex flex-col gap-1 bg-light">
|
||||
{{ if $check }}
|
||||
<div class="stats shadow bg-white">
|
||||
<div class="stat flex flex-col gap-1 justify-stretch">
|
||||
<div class="stat-desc">
|
||||
<div class="badge badge-soft {{ $check.BadgeClass }} w-full">{{ $check.StatusCode }} {{
|
||||
$check.StatusText }}</div>
|
||||
</div>
|
||||
<div class="stat-value text-center text-sm font-medium">
|
||||
{{ $check.CreatedAt.Format "2006-01-02 15:04" }}, <span class="font-semibold">{{
|
||||
$check.ResponseTimeMs
|
||||
}}</span>ms
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{ else }}
|
||||
{{ end }}
|
||||
</div>
|
||||
<div class="relative h-[64px] w-[6px] rounded-full bg-neutral-100 overflow-hidden"
|
||||
id="{{ if $check }}monitors-{{ $check.MonitorID }}-checks-{{ $check.ID }}{{ else }}placeholder-{{ $i }}{{ end }}">
|
||||
<div id="{{ if $check }}monitors-{{ $check.MonitorID }}-index-{{ $currentIndex }}{{ end }}"
|
||||
class="absolute w-full bottom-0 rounded-lg transition-all duration-300 ease-in-out {{ if $check }} {{ $check.HeightClass $.MaxTime }} {{ $check.ColorClass }} {{ else }}bg-neutral-200 {{ end }} ">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
<div class="relative h-[64px] w-[6px] overflow-x-hidden rounded-full bg-neutral-100 ">
|
||||
<div
|
||||
class="absolute w-[6px] bottom-0 rounded-lg transition-all duration-300 ease-in-out bg-neutral-200 h-full animate-pulse">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{ if eq (len $.Checks ) 0}}
|
||||
<div class="absolute inset-0 flex items-center justify-center z-[999999] opacity-80 text-xs animate-pulse">
|
||||
waiting...
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
|
||||
<div class="w-full justify-between text-xs opacity-60 hidden lg:flex">
|
||||
<div>{{ .StartTime }}</div>
|
||||
<div>{{ .EndTime }}</div>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
62
html/delete.html
Normal file
62
html/delete.html
Normal file
@@ -0,0 +1,62 @@
|
||||
{{ define "title" }}Delete monitor{{end}}
|
||||
|
||||
{{ define "content" }}
|
||||
<div class="mx-auto max-w-[1600px] w-full">
|
||||
<div class="md:flex md:items-center md:justify-center p-8 md:p-10">
|
||||
{{template "delete_form" . }}
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
{{ define "delete_form" }}
|
||||
<form hx-delete="/monitors/{{ .Monitor.ID }}" hx-swap="outerHTML" class="flex flex-col gap-4 w-full max-w-md">
|
||||
<div>
|
||||
<h1 class="font-semibold text-lg">Delete monitor</h1>
|
||||
</div>
|
||||
<div>
|
||||
<div role="alert" class="alert alert-vertical alert-error alert-soft">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
|
||||
stroke="currentColor" class="size-6! text-black">
|
||||
<path stroke-linecap="round" stroke-linejoin="round"
|
||||
d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0" />
|
||||
</svg>
|
||||
<span>Are you sure? All the data will be lost.</span>
|
||||
<div>
|
||||
<a href="/m/{{ .Monitor.Uuid }}" hx-boost preload>
|
||||
<button class="btn btn-sm btn-soft" type="button">Cancel</button>
|
||||
</a>
|
||||
<button class="btn btn-sm btn-neutral " type="submit">Remove this monitor</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{{ end }}
|
||||
|
||||
{{ define "breadcrumbs" }}
|
||||
<li class="">
|
||||
<a href="/" hx-boost preload>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" class="size-4! stroke-current shrink-0">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z"></path>
|
||||
</svg>
|
||||
Monitors
|
||||
</a>
|
||||
</li>
|
||||
<li class="">
|
||||
<a href="/m/{{ .Monitor.Uuid }}" hx-boost preload>
|
||||
<div class="flex items-center gap-2 ">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
|
||||
stroke="currentColor" class="size-4! stroke-current shrink-0">
|
||||
<path stroke-linecap="round" stroke-linejoin="round"
|
||||
d="M9 17.25v1.007a3 3 0 0 1-.879 2.122L7.5 21h9l-.621-.621A3 3 0 0 1 15 18.257V17.25m6-12V15a2.25 2.25 0 0 1-2.25 2.25H5.25A2.25 2.25 0 0 1 3 15V5.25m18 0A2.25 2.25 0 0 0 18.75 3H5.25A2.25 2.25 0 0 0 3 5.25m18 0V12a2.25 2.25 0 0 1-2.25 2.25H5.25A2.25 2.25 0 0 1 3 12V5.25" />
|
||||
</svg>
|
||||
<span class="truncate">{{ .Monitor.Host }}</span>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
<li class="">
|
||||
<div class="flex items-center gap-2 ">
|
||||
Delete
|
||||
</div>
|
||||
</li>
|
||||
{{ end }}
|
181
html/edit.html
Normal file
181
html/edit.html
Normal file
@@ -0,0 +1,181 @@
|
||||
{{ define "title" }}Edit monitor{{end}}
|
||||
|
||||
{{ define "content" }}
|
||||
<div class="mx-auto max-w-[1600px] w-full">
|
||||
<div class="md:flex md:items-center md:justify-center p-8 md:p-10">
|
||||
{{template "edit_form" . }}
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
{{ define "edit_form" }}
|
||||
<form hx-patch="/monitors/{{ .Monitor.ID }}" hx-swap="outerHTML" class="flex flex-col gap-4 w-full md:max-w-md">
|
||||
<div>
|
||||
<h1 class="font-semibold text-lg">Edit monitor</h1>
|
||||
</div>
|
||||
<fieldset class="fieldset">
|
||||
<div class="join w-full flex">
|
||||
<div>
|
||||
<select class="select join-item" name="http_method">
|
||||
<option {{ if eq .Form.HttpMethod "GET" }}selected{{ end }}>GET</option>
|
||||
<option {{ if eq .Form.HttpMethod "POST" }}selected{{ end }}>POST</option>
|
||||
<option {{ if eq .Form.HttpMethod "PATCH" }}selected{{ end }}>PATCH</option>
|
||||
<option {{ if eq .Form.HttpMethod "PUT" }}selected{{ end }}>PUT</option>
|
||||
<option {{ if eq .Form.HttpMethod "DELETE" }}selected{{ end }}>DELETE</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="flex-1 ">
|
||||
<input name="url" id="edit_form--url"
|
||||
class="input join-item {{ with .Form.Errors.Url }} input-error {{ end }} w-full" required type="url"
|
||||
placeholder="https://example.com" value="{{ .Form.Url }}" />
|
||||
{{ with .Form.Errors.Url }}
|
||||
<div class="label text-red-500">{{ . }}</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
<div class="card border border-base-200">
|
||||
<div class="card-body p-4">
|
||||
<div class="card-actions justify-start peer">
|
||||
<label class="label">
|
||||
<input type="checkbox" class="toggle toggle-sm" name="has_custom_headers" {{ if
|
||||
.Form.HasCustomHeaders }} checked {{ end }} />
|
||||
Custom Headers
|
||||
</label>
|
||||
</div>
|
||||
<div class="hidden peer-has-checked:block">
|
||||
<textarea class="textarea bg-neutral text-neutral-content w-full textarea" placeholder="{}"
|
||||
name="http_headers">{{ .Form.HttpHeaders }}</textarea>
|
||||
{{ with .Form.Errors.HttpHeaders }}
|
||||
<div class="label text-red-500">{{ . }}</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card border border-base-200">
|
||||
<div class="card-body p-4">
|
||||
<div class="card-actions justify-start peer">
|
||||
<label class="label">
|
||||
<input type="checkbox" class="toggle toggle-sm" name="has_custom_body" {{ if .Form.HasCustomBody }}
|
||||
checked {{ end }} />
|
||||
Custom Body
|
||||
</label>
|
||||
</div>
|
||||
<div class="hidden peer-has-checked:block">
|
||||
<textarea class="textarea bg-neutral text-neutral-content w-full textarea" placeholder="{}"
|
||||
name="http_body">{{ .Form.HttpBody }}</textarea>
|
||||
{{ with .Form.Errors.HttpBody }}
|
||||
<div class="label text-red-500">{{ . }}</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card border border-base-200">
|
||||
<div class="card-body p-4">
|
||||
<div class="card-actions justify-start peer">
|
||||
<label class="label">
|
||||
<input type="checkbox" class="toggle toggle-sm" name="has_webhook" {{ if .Form.HasWebhook }} checked
|
||||
{{ end }} />
|
||||
Webhook
|
||||
<div class="tooltip">
|
||||
<div class="tooltip-content tooltip-neutral bg-neutral text-white/90 text-xs">
|
||||
After each incident the given URL will be notified
|
||||
</div>
|
||||
<button class="btn btn-square btn-ghost btn-sm">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
|
||||
stroke="currentColor" class="size-5!">
|
||||
<path stroke-linecap="round" stroke-linejoin="round"
|
||||
d="m11.25 11.25.041-.02a.75.75 0 0 1 1.063.852l-.708 2.836a.75.75 0 0 0 1.063.853l.041-.021M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9-3.75h.008v.008H12V8.25Z" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="hidden peer-has-checked:block">
|
||||
<fieldset class="fieldset">
|
||||
<legend class="fieldset-legend">
|
||||
Webhook URL
|
||||
</legend>
|
||||
<div class="join w-full flex">
|
||||
<div>
|
||||
<select class="select join-item" name="webhook_method">
|
||||
<option {{ if eq .Form.WebhookMethod "GET" }}selected{{ end }}>GET</option>
|
||||
<option {{ if eq .Form.WebhookMethod "POST" }}selected{{ end }}>POST</option>
|
||||
<option {{ if eq .Form.WebhookMethod "PATCH" }}selected{{ end }}>PATCH</option>
|
||||
<option {{ if eq .Form.WebhookMethod "PUT" }}selected{{ end }}>PUT</option>
|
||||
<option {{ if eq .Form.WebhookMethod "DELETE" }}selected{{ end }}>DELETE</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<input name="webhook_url" id="new_form--webhook_url"
|
||||
class="input join-item {{ with .Form.Errors.WebhookUrl }} input-error {{ end }} w-full"
|
||||
type="url" placeholder="https://example.com" value="{{ .Form.WebhookUrl }}" />
|
||||
{{ with .Form.Errors.WebhookUrl }}
|
||||
<div class="label text-red-500">{{ . }}</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
<div class="hidden peer-has-checked:block">
|
||||
<fieldset class="fieldset">
|
||||
<legend class="fieldset-legend">
|
||||
Webhook Headers
|
||||
</legend>
|
||||
<textarea class="textarea w-full textarea bg-neutral text-neutral-content" placeholder="{}"
|
||||
name="webhook_headers">{{ .Form.WebhookHeaders }}</textarea>
|
||||
{{ with .Form.Errors.WebhookHeaders }}
|
||||
<div class="label text-red-500">{{ . }}</div>
|
||||
{{ end }}
|
||||
</fieldset>
|
||||
</div>
|
||||
<div class="hidden peer-has-checked:block">
|
||||
<fieldset class="fieldset">
|
||||
<legend class="fieldset-legend">
|
||||
Webhook Body
|
||||
</legend>
|
||||
<textarea class="textarea w-full textarea bg-neutral text-neutral-content" placeholder="{}"
|
||||
name="webhook_body">{{ .Form.WebhookBody }}</textarea>
|
||||
{{ with .Form.Errors.WebhookBody }}
|
||||
<div class="label text-red-500">{{ . }}</div>
|
||||
{{ end }}
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<button type="submit" class="btn btn-neutral">
|
||||
Submit
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
{{ end }}
|
||||
|
||||
{{ define "breadcrumbs" }}
|
||||
<li class="">
|
||||
<a href="/" hx-boost preload>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" class="size-4! stroke-current shrink-0">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z"></path>
|
||||
</svg>
|
||||
Monitors
|
||||
</a>
|
||||
</li>
|
||||
<li class="">
|
||||
<a href="/m/{{ .Monitor.Uuid }}" hx-boost preload>
|
||||
<div class="flex items-center gap-2 ">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
|
||||
stroke="currentColor" class="size-4! stroke-current shrink-0">
|
||||
<path stroke-linecap="round" stroke-linejoin="round"
|
||||
d="M9 17.25v1.007a3 3 0 0 1-.879 2.122L7.5 21h9l-.621-.621A3 3 0 0 1 15 18.257V17.25m6-12V15a2.25 2.25 0 0 1-2.25 2.25H5.25A2.25 2.25 0 0 1 3 15V5.25m18 0A2.25 2.25 0 0 0 18.75 3H5.25A2.25 2.25 0 0 0 3 5.25m18 0V12a2.25 2.25 0 0 1-2.25 2.25H5.25A2.25 2.25 0 0 1 3 12V5.25" />
|
||||
</svg>
|
||||
<span class="truncate">{{ .Monitor.Host }}</span>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
<li class="">
|
||||
<div class="flex items-center gap-2 ">
|
||||
Edit
|
||||
</div>
|
||||
</li>
|
||||
{{ end }}
|
8
html/embed.go
Normal file
8
html/embed.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package html
|
||||
|
||||
import "embed"
|
||||
|
||||
var (
|
||||
//go:embed *.html
|
||||
FS embed.FS
|
||||
)
|
28
html/home.html
Normal file
28
html/home.html
Normal file
@@ -0,0 +1,28 @@
|
||||
{{ define "title" }}Home{{ end }}
|
||||
|
||||
{{define "content"}}
|
||||
<div class="flex flex-col-reverse lg:flex-row gap-4 justify-between max-w-[1600px] mx-auto px-4">
|
||||
<div class="flex-1">
|
||||
<div hx-get="/monitors" hx-trigger="intersect" hx-swap="outerHTML"></div>
|
||||
</div>
|
||||
|
||||
<div class="min-w-[300px]! flex flex-col gap-4">
|
||||
<div hx-get="/incidents" hx-trigger="intersect" hx-swap="outerHTML"></div>
|
||||
|
||||
{{ template "sponsors" . }}
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
|
||||
{{ define "breadcrumbs" }}
|
||||
<li class="">
|
||||
<a href="/" hx-boost preload>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" class="size-4! stroke-current">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z"></path>
|
||||
</svg>
|
||||
Monitors
|
||||
</a>
|
||||
</li>
|
||||
{{ end }}
|
184
html/incident.html
Normal file
184
html/incident.html
Normal file
@@ -0,0 +1,184 @@
|
||||
{{ define "incident_list" }}
|
||||
<ul class="list card card-border {{ if .Incidents }} bg-neutral text-neutral-content {{ end }} bg-base-100 w-full w-full rounded-box lg:max-w-sm"
|
||||
hx-get="/incidents" hx-trigger="every 60s" hx-swap="outerHTML">
|
||||
{{ if .Incidents }}
|
||||
<li class="p-4 pb-2 tracking-wide font-semibold">
|
||||
🚨 Open Incidents
|
||||
</li>
|
||||
|
||||
{{ range .Incidents }}
|
||||
<a href="/m/{{ .Monitor.Uuid }}/i/{{ .Uuid}}" hx-boost preload>
|
||||
<li class="list-row w-full max-w-full block" id="incidents-{{ .ID }}">
|
||||
<div class="flex flex-col gap-1.5">
|
||||
<div class="flex gap-2 justify-between items-center">
|
||||
<div aria-label="error" class="status status-error animate-ping shrink-0"></div>
|
||||
<div class="text-xs font-semibold truncate flex-1 shrink-0">
|
||||
<div class="flex items-base gap-1 w-full max-w-full">
|
||||
<span class="flex-none truncate"> {{ .Monitor.Domain }}</span>
|
||||
<span class="flex-grow leader-dots hidden lg:block"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="flex gap-2 justify-between items-center">
|
||||
<div class="max-w-full overflow-hidden text-xs opacity-80 text-right">
|
||||
{{ .CreatedAt.Format "2006-01-02 15:04" }}
|
||||
</div>
|
||||
<div class="text-xs">
|
||||
<div class="badge badge-soft truncate max-w-full inline-block text-xs py-1 {{ .BadgeClass }}">
|
||||
{{ .StatusCode }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</li>
|
||||
</a>
|
||||
{{ end }}
|
||||
{{ else }}
|
||||
<li class="p-4 pb-0 tracking-wide font-semibold">
|
||||
Open Incidents
|
||||
</li>
|
||||
<li class="p-4 tracking-wide opacity-60 text-xs">
|
||||
No incidents to report.
|
||||
</li>
|
||||
{{ end }}
|
||||
</ul>
|
||||
{{ end }}
|
||||
|
||||
{{ define "title" }}
|
||||
{{ .Incident.StatusCode }} {{ .Incident.StatusCodeText }} ({{ .Incident.CreatedAt.Format "2006-01-02 15:04"}})
|
||||
{{ end }}
|
||||
|
||||
{{ define "content" }}
|
||||
<div class="max-w-[1600px] mx-auto px-4">
|
||||
<div class="flex justify-between gap-4 flex-col lg:flex-row">
|
||||
{{ with .Incident }}
|
||||
<div class="flex-1 flex flex-col gap-4">
|
||||
<div class="card card-border p-4 flex flex-col gap-4">
|
||||
<div class="flex flex-col md:flex-row gap-1.5 md:gap-4 md:items-start justify-between text-sm">
|
||||
<div>
|
||||
<div
|
||||
class="badge badge-soft truncate max-w-full inline-block text-xs py-1 {{ .StatusBadgeClass }}">
|
||||
{{ .StatusBadgeText }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-1 flex flex-col gap-1">
|
||||
<div>
|
||||
{{ .StatusCode }} {{ .StatusCodeText }}
|
||||
</div>
|
||||
<div class="w-auto max-w-[350px] text-xs leading-[1.2rem] opacity-80">
|
||||
{{ .CreatedAt.Format "2006-01-02 15:04" }}
|
||||
{{ if .ResolvedAt }}
|
||||
(resolved at {{ .ResolvedAt.Format "2006-01-02 15:04" }})
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="px-4">
|
||||
<h2 class="font-semibold">Request</h2>
|
||||
</div>
|
||||
|
||||
<div class="mockup-code w-full">
|
||||
<div class="p-4 max-h-[600px] overflow-y-scroll overflow-x-scroll flex flex-col gap-1">
|
||||
{{ if and $.Incident.ReqMethod $.Incident.ReqUrl }}
|
||||
<pre>curl -X{{ $.Incident.ReqMethod }} {{ $.Incident.ReqUrl }} \</pre>
|
||||
{{ end}}
|
||||
{{ if $.Incident.ReqHeaders }}
|
||||
{{ range $key, $value := $.Incident.ReqHeadersMap }}
|
||||
<pre class="opacity-60 pl-4">-H '{{ $key }}: {{ $value }}' \</pre>
|
||||
{{ end }}
|
||||
{{ end}}
|
||||
{{ if $.Incident.ReqBody }}
|
||||
<pre class="pl-4">-d '{{ $.Incident.ReqBody }}'</pre>
|
||||
{{ end}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="px-4">
|
||||
<h2 class="font-semibold">Response</h2>
|
||||
</div>
|
||||
|
||||
<div class="mockup-browser border border-base-200 w-full bg-base-100 text-neutral-800 text-sm">
|
||||
<div class="mockup-browser-toolbar">
|
||||
<div class="input">{{ $.Monitor.HttpMethod }} {{ $.Monitor.Url }}</div>
|
||||
</div>
|
||||
<div class="p-4 max-h-[600px] overflow-y-scroll overflow-x-scroll flex flex-col gap-2">
|
||||
{{ if $.Incident.Headers }}
|
||||
<pre class="opacity-60">{{ $.Incident.Headers }}</pre>
|
||||
{{ end}}
|
||||
{{ if $.Incident.Body }}
|
||||
<pre>{{ $.Incident.Body }}</pre>
|
||||
{{ else }}
|
||||
<pre>//</pre>
|
||||
{{ end}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
<div class="lg:w-[237px] flex flex-col gap-4">
|
||||
<ul class="list bg-base-100 rounded-box border border-base-200 p-2 text-left">
|
||||
<form class="block list-row justify-stretch px-0 py-0" hx-delete="/incidents/{{ .Incident.ID }}"
|
||||
hx-confirm="Are you sure?" hx-swap="none">
|
||||
<button type="submit">
|
||||
<div class="list-col-grow">
|
||||
<button class="btn btn-ghost w-full text-left align-start justify-start text-error">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
|
||||
stroke-width="1.5" stroke="currentColor" class="size-5!">
|
||||
<path stroke-linecap="round" stroke-linejoin="round"
|
||||
d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0" />
|
||||
</svg>
|
||||
|
||||
Remove incident
|
||||
</button>
|
||||
</div>
|
||||
</button>
|
||||
</form>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{ define "breadcrumbs" }}
|
||||
<li class="">
|
||||
<a href="/" hx-boost preload>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" class="size-4! stroke-current shrink-0">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z"></path>
|
||||
</svg>
|
||||
Monitors
|
||||
</a>
|
||||
</li>
|
||||
<li class="">
|
||||
<a href="/m/{{ .Monitor.Uuid }}" hx-boost preload>
|
||||
<div class="flex items-center gap-2 hidden lg:flex">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
|
||||
stroke="currentColor" class="size-4! stroke-current shrink-0">
|
||||
<path stroke-linecap="round" stroke-linejoin="round"
|
||||
d="M9 17.25v1.007a3 3 0 0 1-.879 2.122L7.5 21h9l-.621-.621A3 3 0 0 1 15 18.257V17.25m6-12V15a2.25 2.25 0 0 1-2.25 2.25H5.25A2.25 2.25 0 0 1 3 15V5.25m18 0A2.25 2.25 0 0 0 18.75 3H5.25A2.25 2.25 0 0 0 3 5.25m18 0V12a2.25 2.25 0 0 1-2.25 2.25H5.25A2.25 2.25 0 0 1 3 12V5.25" />
|
||||
</svg>
|
||||
<span class="truncate">{{ .Monitor.Host }}</span>
|
||||
</div>
|
||||
<span class="lg:hidden opacity-40">...</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="">
|
||||
<div class="flex items-center gap-2 ">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"
|
||||
class="size-4! stroke-current shrink-0">
|
||||
<path stroke-linecap="round" stroke-linejoin="round"
|
||||
d="M12 9v3.75m9-.75a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9 3.75h.008v.008H12v-.008Z" />
|
||||
</svg>
|
||||
|
||||
<span class="truncate">{{ .Incident.StatusCode }} {{ .Incident.StatusCodeText }}</span>
|
||||
</div>
|
||||
</li>
|
||||
{{ end }}
|
45
html/layout.html
Normal file
45
html/layout.html
Normal file
@@ -0,0 +1,45 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">
|
||||
<title>{{ block "title" .}}{{ end }} - Uptime Monitor</title>
|
||||
<link rel="stylesheet" href="/static/css/daisyui.css?v=5.0.46" />
|
||||
<link rel="stylesheet" href="/static/css/themes.css?v=5.0.46" />
|
||||
<link rel="stylesheet" href="/static/css/main.css?v=1" />
|
||||
{{ block "head" .}}{{ end }}
|
||||
</head>
|
||||
|
||||
<body hx-ext="preload" data-theme="emerald" class="min-h-screen font-mono">
|
||||
{{ block "body" . }}{{ end }}
|
||||
|
||||
<script defer src="/static/js/tailwind.min.js?v=5.0.46"></script>
|
||||
<script defer src="/static/js/htmx.min.js?v=2.0.6"></script>
|
||||
<script defer src="/static/js/htmx-ext-preload.min.js?v=2.1.0"></script>
|
||||
<script defer src="/static/js/main.js?v=1"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
||||
{{ define "logo"}}
|
||||
<a href="/" hx-boost preload="mouseover" class="flex items-center gap-1.5 text-sm font-medium">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"
|
||||
class="size-5!">
|
||||
<path stroke-linecap="round" stroke-linejoin="round"
|
||||
d="M2.25 18 9 11.25l4.306 4.306a11.95 11.95 0 0 1 5.814-5.518l2.74-1.22m0 0-5.94-2.281m5.94 2.28-2.28 5.941" />
|
||||
</svg>
|
||||
|
||||
<span class="">
|
||||
UM {{ template "version" . }}
|
||||
</span>
|
||||
</a>
|
||||
{{ end }}
|
||||
|
||||
{{ define "version" }}
|
||||
<span class="text-neutral-400 text-xs font-normal truncate">v0.5.0-alpha</span>
|
||||
{{ end }}
|
||||
|
||||
{{ define "sponsors" }}
|
||||
<div hx-get="/sponsors" hx-trigger="intersect" hx-swap="outerHTML"></div>
|
||||
{{ end }}
|
53
html/login.html
Normal file
53
html/login.html
Normal file
@@ -0,0 +1,53 @@
|
||||
{{ define "title" }}Log in{{end}}
|
||||
|
||||
{{ define "body" }}
|
||||
<div class="flex items-center justify-center min-h-screen p-8 pt-16">
|
||||
<div class="absolute top-2 left-2">
|
||||
{{ template "logo" . }}
|
||||
</div>
|
||||
{{template "login_form" . }}
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
{{ define "login_form" }}
|
||||
<form hx-post="/login" hx-swap="outerHTML" class="w-full max-w-sm flex flex-col gap-4">
|
||||
<div>
|
||||
<h1 class="flex gap-1 items-center text-lg font-semibold">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor"
|
||||
style="height: 20px; width: auto;">
|
||||
<path fill-rule="evenodd"
|
||||
d="M10 1a4.5 4.5 0 0 0-4.5 4.5V9H5a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-6a2 2 0 0 0-2-2h-.5V5.5A4.5 4.5 0 0 0 10 1Zm3 8V5.5a3 3 0 1 0-6 0V9h6Z"
|
||||
clip-rule="evenodd" />
|
||||
</svg>
|
||||
|
||||
Log in
|
||||
</h1>
|
||||
<h5 class="text-sm">
|
||||
Please fill in your credentials to proceed
|
||||
</h5>
|
||||
</div>
|
||||
<div>
|
||||
<fieldset class="fieldset">
|
||||
<legend class="fieldset-legend">Email</legend>
|
||||
<input class="input w-full {{ with .Form.Errors.Email }} input-error {{ end }}" id="login_form--email"
|
||||
name="email" type="email" autocomplete="email" value="{{ .Form.Email}}" placeholder="email@example.com"
|
||||
required />
|
||||
{{ with .Form.Errors.Email }}
|
||||
<div class="label text-red-500">{{ . }}</div>
|
||||
{{ end }}
|
||||
</fieldset>
|
||||
<fieldset class="fieldset">
|
||||
<legend class="fieldset-legend">Password</legend>
|
||||
<input class="input w-full {{ with .Form.Errors.Password }} input-error {{ end }}" id="login_form--password"
|
||||
name="password" type="password" autocomplete="current-password" placeholder="••••••••" required
|
||||
hx-preserve />
|
||||
{{ with .Form.Errors.Password }}
|
||||
<div class="label text-red-500">{{ . }}</div>
|
||||
{{ end }}
|
||||
</fieldset>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-neutral">
|
||||
Submit
|
||||
</button>
|
||||
</form>
|
||||
{{ end }}
|
245
html/monitor.html
Normal file
245
html/monitor.html
Normal file
@@ -0,0 +1,245 @@
|
||||
{{ define "title" }}
|
||||
{{ .Monitor.Domain }}
|
||||
{{end}}
|
||||
|
||||
{{ define "monitor_list" }}
|
||||
<div class="grid gap-4">
|
||||
{{ range .Monitors }}
|
||||
<a href="{{ .URI }}" hx-boost preload>
|
||||
<div class="card card-border p-4 flex flex-col gap-4">
|
||||
<h2 class="card-title text-sm">
|
||||
<div class="font-semibold flex gap-1.5 items-center flex-1 ">
|
||||
{{ if .Secure }}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="size-4!">
|
||||
<path fill-rule="evenodd"
|
||||
d="M12 1.5a5.25 5.25 0 0 0-5.25 5.25v3a3 3 0 0 0-3 3v6.75a3 3 0 0 0 3 3h10.5a3 3 0 0 0 3-3v-6.75a3 3 0 0 0-3-3v-3c0-2.9-2.35-5.25-5.25-5.25Zm3.75 8.25v-3a3.75 3.75 0 1 0-7.5 0v3h7.5Z"
|
||||
clip-rule="evenodd" />
|
||||
</svg>
|
||||
{{ else }}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"
|
||||
class="size-4! opacity-50">
|
||||
<path
|
||||
d="M18 1.5c2.9 0 5.25 2.35 5.25 5.25v3.75a.75.75 0 0 1-1.5 0V6.75a3.75 3.75 0 1 0-7.5 0v3a3 3 0 0 1 3 3v6.75a3 3 0 0 1-3 3H3.75a3 3 0 0 1-3-3v-6.75a3 3 0 0 1 3-3h9v-3c0-2.9 2.35-5.25 5.25-5.25Z" />
|
||||
</svg>
|
||||
{{ end }}
|
||||
<span class="truncate">{{ .Domain }}</span>
|
||||
</div>
|
||||
</h2>
|
||||
<div hx-get="/monitors/{{ .ID }}/checks" hx-swap="outerHTML" hx-trigger="intersect"
|
||||
class="flex flex-col gap-1">
|
||||
<div class="flex justify-between items-center w-full gap-1.5">
|
||||
{{ range $i, $e := $.Skeletons }}
|
||||
<div class="h-[64px] rounded-full w-[6px] bg-neutral-100 animate-pulse stagger {{ if gt $i 30 }} {{ else if gt $i 25 }} hidden md:block{{ else }} hidden lg:block{{ end }}"
|
||||
style="--index: {{ $i }};">
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
|
||||
|
||||
<div class="w-full hidden lg:flex justify-between text-xs opacity-60">
|
||||
<div>--:--</div>
|
||||
<div>--:--</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
{{ define "content" }}
|
||||
<div class="max-w-[1600px] mx-auto px-4">
|
||||
<div class="flex flex-col gap-4 lg:flex-row">
|
||||
<div class="flex-1 flex flex-col gap-4">
|
||||
<div class="card card-border p-4 flex flex-col gap-4">
|
||||
<div class="flex items-center justify-between gap-2">
|
||||
<div class="font-semibold flex gap-1.5 items-center flex-1 ">
|
||||
<div class="flex-1 truncate text-sm">
|
||||
{{ .Monitor.Url }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div hx-get="/monitors/{{ .Monitor.ID }}/checks" hx-swap="outerHTML" hx-trigger="intersect"
|
||||
class="flex flex-col gap-1">
|
||||
<div class="flex justify-between items-center w-full gap-1.5">
|
||||
{{ range $i, $e := .Skeletons }}
|
||||
<div class="h-[64px] rounded-full w-[6px] bg-neutral-100 animate-pulse stagger {{ if gt $i 30 }} {{ else if gt $i 25 }} hidden md:block{{ else }} hidden lg:block{{ end }}"
|
||||
style="--index: {{ $i }};">
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
|
||||
<div class="w-full idden lg:flex justify-between text-xs opacity-60">
|
||||
<div>--:--</div>
|
||||
<div>--:--</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stats w-full flex flex-col md:flex-row gap-1.5 px-4" hx-get="/monitors/{{ .Monitor.ID }}/stats"
|
||||
hx-trigger="intersect" hx-swap="outerHTML">
|
||||
<div class="stat md:place-items-center p-0">
|
||||
<div class="stat-title">Avg. response time</div>
|
||||
<div class="stat-value text-lg md:text-3xl ">-</div>
|
||||
</div>
|
||||
|
||||
<div class="stat md:place-items-center p-0">
|
||||
<div class="stat-title">Checks</div>
|
||||
<div class="stat-value text-lg md:text-3xl ">-</div>
|
||||
</div>
|
||||
|
||||
<div class="stat md:place-items-center p-0">
|
||||
<div class="stat-title">Fails</div>
|
||||
<div class="stat-value text-lg md:text-3xl ">-</div>
|
||||
</div>
|
||||
|
||||
<div class="stat md:place-items-center p-0">
|
||||
<div class="stat-title">Uptime</div>
|
||||
<div class="stat-value text-lg md:text-3xl ">-</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div hx-get="/monitors/{{ .Monitor.ID }}/incidents" hx-trigger="intersect" hx-swap="outerHTML"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="lg:w-[237px] flex flex-col gap-4">
|
||||
<ul class="list bg-base-100 rounded-box border border-base-200 p-2 text-left">
|
||||
<a href="/m/{{ .Monitor.Uuid }}/edit" hx-boost preload>
|
||||
<div class="list-row justify-stretch px-0 py-0">
|
||||
<div class="list-col-grow">
|
||||
<button class="btn btn-ghost w-full text-left align-start justify-start">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
|
||||
stroke-width="1.5" stroke="currentColor" class="size-5!">
|
||||
<path stroke-linecap="round" stroke-linejoin="round"
|
||||
d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L10.582 16.07a4.5 4.5 0 0 1-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 0 1 1.13-1.897l8.932-8.931Zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0 1 15.75 21H5.25A2.25 2.25 0 0 1 3 18.75V8.25A2.25 2.25 0 0 1 5.25 6H10" />
|
||||
</svg>
|
||||
|
||||
Edit monitor
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
<div class="list-row justify-stretch px-0 py-0">
|
||||
<a href="/m/{{ .Monitor.Uuid }}/delete" hx-boost preload>
|
||||
<div class="list-col-grow">
|
||||
<button class="btn btn-ghost w-full text-left align-start justify-start text-error">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
|
||||
stroke-width="1.5" stroke="currentColor" class="size-5!">
|
||||
<path stroke-linecap="round" stroke-linejoin="round"
|
||||
d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0" />
|
||||
</svg>
|
||||
|
||||
Remove monitor
|
||||
</button>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
{{ define "monitor_stats" }}
|
||||
<div class="stats w-full flex flex-col md:flex-row gap-1.5 px-4" hx-get="/monitors/{{ .ID }}/stats"
|
||||
hx-trigger="every 60s" hx-swap="outerHTML">
|
||||
<div class="stat md:place-items-center p-0">
|
||||
<div class="stat-title">Avg. response time</div>
|
||||
<div class="stat-value text-lg md:text-3xl ">{{ .AvgResponseTime }}ms</div>
|
||||
</div>
|
||||
|
||||
<div class="stat md:place-items-center p-0">
|
||||
<div class="stat-title">Checks</div>
|
||||
<div class="stat-value text-lg md:text-3xl ">{{ .ChecksCount }}</div>
|
||||
</div>
|
||||
|
||||
<div class="stat md:place-items-center p-0">
|
||||
<div class="stat-title">Fails</div>
|
||||
<div class="stat-value text-lg md:text-3xl ">{{ .FailureCount }}</div>
|
||||
</div>
|
||||
|
||||
<div class="stat md:place-items-center p-0">
|
||||
<div class="stat-title">Uptime</div>
|
||||
<div class="stat-value text-lg md:text-3xl ">{{ .Uptime }}%</div>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
{{ define "monitor_incident_list" }}
|
||||
<div class="card card-border bg-base-100 {{ if .Incidents }} bg-neutral text-neutral-content {{ end }} w-full w-full rounded-box"
|
||||
hx-get="/monitors/{{ .ID }}/incidents" hx-trigger="every 60s" hx-swap="outerHTML">
|
||||
{{ if .Incidents }}
|
||||
<div class="p-4 pb-2 tracking-wide font-semibold">
|
||||
Incidents
|
||||
</div>
|
||||
|
||||
<div class="overflow-x-auto flex flex-col gap-4 md:gap-0 pb-2">
|
||||
{{ range .Incidents }}
|
||||
<a href="/m/{{ .Monitor.Uuid }}/i/{{ .Uuid }}" hx-boost preload>
|
||||
<div class="p-0" id="incidents-{{ .ID }}">
|
||||
<div class="px-4 py-2">
|
||||
<div class="">
|
||||
<div class="flex flex-col md:flex-row gap-1.5 md:gap-4 md:items-start justify-between text-sm">
|
||||
<div>
|
||||
<div
|
||||
class="badge badge-soft truncate max-w-full inline-block text-xs py-1 w-[100px] text-center {{ .StatusBadgeClass }}">
|
||||
{{ .StatusBadgeText }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-1 flex flex-col gap-1">
|
||||
<div>
|
||||
{{ .StatusCode }} {{ .StatusCodeText }}
|
||||
</div>
|
||||
<div class="w-auto max-w-[350px] text-xs leading-[1.2rem] opacity-80">
|
||||
{{ .CreatedAt.Format "2006-01-02 15:04" }}
|
||||
{{ if .ResolvedAt }}
|
||||
(resolved at {{ .ResolvedAt.Format "2006-01-02 15:04" }})
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label for="incident-details-{{ .ID }}" class="btn btn-sm btn-warning">
|
||||
Details
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ else }}
|
||||
<div class="p-4 pb-0 tracking-wide font-semibold">
|
||||
Incidents
|
||||
</div>
|
||||
<div class="p-4 tracking-wide opacity-60 text-xs">
|
||||
No incidents to report.
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
{{ define "breadcrumbs" }}
|
||||
<li class="">
|
||||
<a href="/" hx-boost preload>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" class="size-4! stroke-current shrink-0">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z"></path>
|
||||
</svg>
|
||||
Monitors
|
||||
</a>
|
||||
</li>
|
||||
<li class="">
|
||||
<div class="flex items-center gap-2 ">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"
|
||||
class="size-4! stroke-current shrink-0">
|
||||
<path stroke-linecap="round" stroke-linejoin="round"
|
||||
d="M9 17.25v1.007a3 3 0 0 1-.879 2.122L7.5 21h9l-.621-.621A3 3 0 0 1 15 18.257V17.25m6-12V15a2.25 2.25 0 0 1-2.25 2.25H5.25A2.25 2.25 0 0 1 3 15V5.25m18 0A2.25 2.25 0 0 0 18.75 3H5.25A2.25 2.25 0 0 0 3 5.25m18 0V12a2.25 2.25 0 0 1-2.25 2.25H5.25A2.25 2.25 0 0 1 3 12V5.25" />
|
||||
</svg>
|
||||
<span class="truncate">{{ .Monitor.Host }}</span>
|
||||
</div>
|
||||
</li>
|
||||
{{ end }}
|
175
html/new.html
Normal file
175
html/new.html
Normal file
@@ -0,0 +1,175 @@
|
||||
{{ define "title" }}Create monitor{{end}}
|
||||
|
||||
{{ define "content" }}
|
||||
<div class="mx-auto max-w-[1600px] w-full">
|
||||
<div class="md:flex md:items-center md:justify-center p-8 md:p-10">
|
||||
{{template "new_form" . }}
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
{{ define "breadcrumbs" }}
|
||||
<li class="">
|
||||
<a href="/" hx-boost preload>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" class="size-4! stroke-current shrink-0">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z"></path>
|
||||
</svg>
|
||||
Monitors
|
||||
</a>
|
||||
</li>
|
||||
<li class="">
|
||||
<div class="flex items-center gap-2 ">
|
||||
New
|
||||
</div>
|
||||
</li>
|
||||
{{ end }}
|
||||
|
||||
{{ define "new_form" }}
|
||||
<form hx-post="/monitors" hx-swap="outerHTML" class="flex flex-col gap-4 w-full md:max-w-md">
|
||||
<div>
|
||||
<h1 class="font-semibold text-lg">Create monitor</h1>
|
||||
<h5 class="text-sm">
|
||||
The url you provide will be monitored every minute.
|
||||
</h5>
|
||||
</div>
|
||||
<fieldset class="fieldset">
|
||||
<legend class="fieldset-legend">
|
||||
Website URL
|
||||
</legend>
|
||||
<div class="join w-full flex">
|
||||
<div>
|
||||
<select class="select join-item" name="http_method">
|
||||
<option selected>GET</option>
|
||||
<option>POST</option>
|
||||
<option>PATCH</option>
|
||||
<option>PUT</option>
|
||||
<option>DELETE</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<input name="url" id="new_form--url"
|
||||
class="input join-item {{ with .Form.Errors.Url }} input-error {{ end }} w-full" autofocus required
|
||||
type="url" placeholder="https://example.com" value="{{ .Form.Url }}" />
|
||||
{{ with .Form.Errors.Url }}
|
||||
<div class="label text-red-500">{{ . }}</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
<div class="card border border-base-200">
|
||||
<div class="card-body p-4">
|
||||
<div class="card-actions justify-start peer">
|
||||
<label class="label">
|
||||
<input type="checkbox" class="toggle toggle-sm" name="has_custom_headers" {{ if
|
||||
.Form.HasCustomHeaders }} checked {{ end }} />
|
||||
Custom Headers
|
||||
</label>
|
||||
</div>
|
||||
<div class="hidden peer-has-checked:block">
|
||||
<textarea class="textarea w-full textarea bg-neutral text-neutral-content" placeholder="{}"
|
||||
name="http_headers">{{ .Form.HttpHeaders }}</textarea>
|
||||
{{ with .Form.Errors.HttpHeaders }}
|
||||
<div class="label text-red-500">{{ . }}</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card border border-base-200">
|
||||
<div class="card-body p-4">
|
||||
<div class="card-actions justify-start peer">
|
||||
<label class="label">
|
||||
<input type="checkbox" class="toggle toggle-sm" name="has_custom_body" {{ if .Form.HasCustomBody }}
|
||||
checked {{ end }} />
|
||||
Custom Body
|
||||
</label>
|
||||
</div>
|
||||
<div class="hidden peer-has-checked:block">
|
||||
<textarea class="textarea w-full textarea bg-neutral text-neutral-content" placeholder="{}"
|
||||
name="http_body">{{ .Form.HttpBody }}</textarea>
|
||||
{{ with .Form.Errors.HttpBody }}
|
||||
<div class="label text-red-500">{{ . }}</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card border border-base-200">
|
||||
<div class="card-body p-4">
|
||||
<div class="card-actions justify-start peer">
|
||||
<label class="label">
|
||||
<input type="checkbox" class="toggle toggle-sm" name="has_webhook" {{ if .Form.HasWebhook }} checked
|
||||
{{ end }} />
|
||||
Webhook
|
||||
<div class="tooltip">
|
||||
<div class="tooltip-content tooltip-neutral bg-neutral text-white/90 text-xs">
|
||||
After each incident the given URL will be notified
|
||||
</div>
|
||||
<button class="btn btn-square btn-ghost btn-sm">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
|
||||
stroke="currentColor" class="size-5!">
|
||||
<path stroke-linecap="round" stroke-linejoin="round"
|
||||
d="m11.25 11.25.041-.02a.75.75 0 0 1 1.063.852l-.708 2.836a.75.75 0 0 0 1.063.853l.041-.021M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9-3.75h.008v.008H12V8.25Z" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="hidden peer-has-checked:block">
|
||||
<fieldset class="fieldset">
|
||||
<legend class="fieldset-legend">
|
||||
Webhook URL
|
||||
</legend>
|
||||
<div class="join w-full flex">
|
||||
<div>
|
||||
<select class="select join-item" name="webhook_method">
|
||||
<option>GET</option>
|
||||
<option selected>POST</option>
|
||||
<option>PATCH</option>
|
||||
<option>PUT</option>
|
||||
<option>DELETE</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<input name="webhook_url" id="new_form--webhook_url"
|
||||
class="input join-item {{ with .Form.Errors.WebhookUrl }} input-error {{ end }} w-full"
|
||||
type="url" placeholder="https://example.com" value="{{ .Form.WebhookUrl }}" />
|
||||
{{ with .Form.Errors.WebhookUrl }}
|
||||
<div class="label text-red-500">{{ . }}</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
<div class="hidden peer-has-checked:block">
|
||||
<fieldset class="fieldset">
|
||||
<legend class="fieldset-legend">
|
||||
Webhook Headers
|
||||
</legend>
|
||||
<textarea class="textarea w-full textarea bg-neutral text-neutral-content" placeholder="{}"
|
||||
name="webhook_headers">{{ .Form.WebhookHeaders }}</textarea>
|
||||
{{ with .Form.Errors.WebhookHeaders }}
|
||||
<div class="label text-red-500">{{ . }}</div>
|
||||
{{ end }}
|
||||
</fieldset>
|
||||
</div>
|
||||
<div class="hidden peer-has-checked:block">
|
||||
<fieldset class="fieldset">
|
||||
<legend class="fieldset-legend">
|
||||
Webhook Body
|
||||
</legend>
|
||||
<textarea class="textarea w-full textarea bg-neutral text-neutral-content" rows="4" placeholder="{}"
|
||||
name="webhook_body">{{ .Form.WebhookBody }}</textarea>
|
||||
{{ with .Form.Errors.WebhookBody }}
|
||||
<div class="label text-red-500">{{ . }}</div>
|
||||
{{ end }}
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<button type="submit" class="btn btn-neutral">
|
||||
Submit
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
{{ end }}
|
60
html/setup.html
Normal file
60
html/setup.html
Normal file
@@ -0,0 +1,60 @@
|
||||
{{ define "title" }}Setup{{end}}
|
||||
|
||||
{{ define "body" }}
|
||||
<div class="flex items-center justify-center min-h-screen p-8 pt-16">
|
||||
<div class="absolute top-2 left-2">
|
||||
{{ template "logo" . }}
|
||||
</div>
|
||||
{{template "setup_form" . }}
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
{{ define "setup_form" }}
|
||||
<form hx-post="/setup" hx-swap="outerHTML" class="w-full max-w-sm flex flex-col gap-4">
|
||||
<div>
|
||||
<h1 class="flex gap-1 items-center text-lg font-semibold">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"
|
||||
style="height: 20px; width: auto;">
|
||||
<path fill-rule="evenodd"
|
||||
d="M11.078 2.25c-.917 0-1.699.663-1.85 1.567L9.05 4.889c-.02.12-.115.26-.297.348a7.493 7.493 0 0 0-.986.57c-.166.115-.334.126-.45.083L6.3 5.508a1.875 1.875 0 0 0-2.282.819l-.922 1.597a1.875 1.875 0 0 0 .432 2.385l.84.692c.095.078.17.229.154.43a7.598 7.598 0 0 0 0 1.139c.015.2-.059.352-.153.43l-.841.692a1.875 1.875 0 0 0-.432 2.385l.922 1.597a1.875 1.875 0 0 0 2.282.818l1.019-.382c.115-.043.283-.031.45.082.312.214.641.405.985.57.182.088.277.228.297.35l.178 1.071c.151.904.933 1.567 1.85 1.567h1.844c.916 0 1.699-.663 1.85-1.567l.178-1.072c.02-.12.114-.26.297-.349.344-.165.673-.356.985-.57.167-.114.335-.125.45-.082l1.02.382a1.875 1.875 0 0 0 2.28-.819l.923-1.597a1.875 1.875 0 0 0-.432-2.385l-.84-.692c-.095-.078-.17-.229-.154-.43a7.614 7.614 0 0 0 0-1.139c-.016-.2.059-.352.153-.43l.84-.692c.708-.582.891-1.59.433-2.385l-.922-1.597a1.875 1.875 0 0 0-2.282-.818l-1.02.382c-.114.043-.282.031-.449-.083a7.49 7.49 0 0 0-.985-.57c-.183-.087-.277-.227-.297-.348l-.179-1.072a1.875 1.875 0 0 0-1.85-1.567h-1.843ZM12 15.75a3.75 3.75 0 1 0 0-7.5 3.75 3.75 0 0 0 0 7.5Z"
|
||||
clip-rule="evenodd" />
|
||||
</svg>
|
||||
|
||||
Setup
|
||||
</h1>
|
||||
<h5 class="text-sm">
|
||||
Create your admin account
|
||||
</h5>
|
||||
</div>
|
||||
<div>
|
||||
<fieldset class="fieldset">
|
||||
<legend class="fieldset-legend">Name</legend>
|
||||
<input class="input w-full {{ with .Form.Errors.Name }} input-error {{ end }}" id="setup_form--name"
|
||||
name="name" required autofocus autocomplete="name" value="{{ .Form.Name}}" placeholder="John Doe" />
|
||||
{{ with .Form.Errors.Name }}
|
||||
<div class="label text-red-500">{{ . }}</div>
|
||||
{{ end }}
|
||||
</fieldset>
|
||||
<fieldset class="fieldset">
|
||||
<legend class="fieldset-legend">E-mail</legend>
|
||||
<input class="input w-full {{ with .Form.Errors.Email }} input-error {{ end }}" id="setup_form--email"
|
||||
name="email" type="email" autocomplete="email" value="{{ .Form.Email}}" placeholder="email@example.com"
|
||||
required />
|
||||
{{ with .Form.Errors.Email }}
|
||||
<div class="label text-red-500">{{ . }}</div>
|
||||
{{ end }}
|
||||
</fieldset>
|
||||
<fieldset class="fieldset">
|
||||
<legend class="fieldset-legend">Password</legend>
|
||||
<input class="input w-full {{ with .Form.Errors.Password }} input-error {{ end }}" id="setup_form--password"
|
||||
name="password" type="password" required autocomplete="new-password" placeholder="••••••••" />
|
||||
{{ with .Form.Errors.Password }}
|
||||
<div class="label text-red-500">{{ . }}</div>
|
||||
{{ end }}
|
||||
</fieldset>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-neutral">
|
||||
Submit
|
||||
</button>
|
||||
</form>
|
||||
{{ end }}
|
15
html/sponsor.html
Normal file
15
html/sponsor.html
Normal file
@@ -0,0 +1,15 @@
|
||||
<div class="card border border-base-200 p-4 flex flex-col gap-2">
|
||||
<div class="text-center text-xs font-semibold text-neutral-400 uppercase tracking-widest">
|
||||
Sponsors
|
||||
</div>
|
||||
<div class="flex lg:flex-col gap-6 overflow-x-auto">
|
||||
{{ range .Sponsors }}
|
||||
<a href="{{ .Url }}" target="_blank" rel="noopener noreferrer">
|
||||
<div class="text-center flex flex-col shrink-0">
|
||||
<img src="{{ .Image }}" alt="{{ .Name }}" class="max-h-[30px] w-auto" />
|
||||
<div class="text-sm truncate">{{ .Name }}</div>
|
||||
</div>
|
||||
</a>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
Reference in New Issue
Block a user