mirror of
https://github.com/airlabspl/uptimemonitor.git
synced 2025-08-15 04:39:15 +02:00
initial commit
This commit is contained in:
69
handler/check_handler.go
Normal file
69
handler/check_handler.go
Normal file
@@ -0,0 +1,69 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
"uptimemonitor"
|
||||
"uptimemonitor/html"
|
||||
)
|
||||
|
||||
func (h *Handler) ListChecks() http.HandlerFunc {
|
||||
tmpl := template.Must(template.New("check.html").Funcs(template.FuncMap{
|
||||
"sub": func(a, b int) int {
|
||||
return a - b
|
||||
},
|
||||
}).ParseFS(html.FS, "check.html"))
|
||||
|
||||
type data struct {
|
||||
Monitor uptimemonitor.Monitor
|
||||
Checks []uptimemonitor.Check
|
||||
Skeletons []int
|
||||
MaxTime int64
|
||||
StartTime string
|
||||
EndTime string
|
||||
}
|
||||
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
monitorID, err := strconv.Atoi(r.PathValue("monitor"))
|
||||
if err != nil {
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
monitor, err := h.Store.GetMonitorByID(r.Context(), monitorID)
|
||||
if err != nil || monitor.ID == 0 {
|
||||
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
checks, err := h.Store.ListChecks(r.Context(), int64(monitorID), 60)
|
||||
if err != nil {
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
maxTime := int64(0)
|
||||
for _, check := range checks {
|
||||
if check.ResponseTimeMs > maxTime {
|
||||
maxTime = check.ResponseTimeMs
|
||||
}
|
||||
}
|
||||
|
||||
err = tmpl.ExecuteTemplate(w, "check_list", data{
|
||||
Monitor: monitor,
|
||||
Checks: checks,
|
||||
Skeletons: make([]int, 60),
|
||||
MaxTime: maxTime,
|
||||
StartTime: time.Now().Add(-1 * time.Hour).Format("15:04"),
|
||||
EndTime: time.Now().Format("15:04"),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
http.Error(w, fmt.Sprintf("%v", err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
29
handler/handler.go
Normal file
29
handler/handler.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"uptimemonitor"
|
||||
"uptimemonitor/service"
|
||||
"uptimemonitor/store"
|
||||
)
|
||||
|
||||
type Handler struct {
|
||||
Store *store.Store
|
||||
Secure bool
|
||||
}
|
||||
|
||||
func New(store *store.Store, service *service.Service, secure bool) *Handler {
|
||||
return &Handler{
|
||||
Store: store,
|
||||
Secure: secure,
|
||||
}
|
||||
}
|
||||
|
||||
func getUserFromRequest(r *http.Request) uptimemonitor.User {
|
||||
user, ok := r.Context().Value(userContextKey).(uptimemonitor.User)
|
||||
if !ok {
|
||||
return uptimemonitor.User{}
|
||||
}
|
||||
|
||||
return user
|
||||
}
|
28
handler/home_handler.go
Normal file
28
handler/home_handler.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"net/http"
|
||||
"uptimemonitor"
|
||||
"uptimemonitor/html"
|
||||
)
|
||||
|
||||
func (h *Handler) HomePage() http.HandlerFunc {
|
||||
tmpl := template.Must(template.ParseFS(html.FS, "layout.html", "app.html", "home.html"))
|
||||
|
||||
type data struct {
|
||||
User uptimemonitor.User
|
||||
}
|
||||
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
count := h.Store.CountMonitors(r.Context())
|
||||
if count == 0 {
|
||||
http.Redirect(w, r, "/new", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
|
||||
tmpl.Execute(w, data{
|
||||
User: getUserFromRequest(r),
|
||||
})
|
||||
}
|
||||
}
|
80
handler/incident_handler.go
Normal file
80
handler/incident_handler.go
Normal file
@@ -0,0 +1,80 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"uptimemonitor"
|
||||
"uptimemonitor/html"
|
||||
)
|
||||
|
||||
func (h *Handler) ListIncidents() http.HandlerFunc {
|
||||
tmpl := template.Must(template.ParseFS(html.FS, "incident.html"))
|
||||
|
||||
type data struct {
|
||||
Incidents []uptimemonitor.Incident
|
||||
}
|
||||
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
incidents, err := h.Store.ListOpenIncidents(r.Context())
|
||||
if err != nil {
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
tmpl.ExecuteTemplate(w, "incident_list", data{
|
||||
Incidents: incidents,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) DeleteIncident() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
id, err := strconv.Atoi(r.PathValue("incident"))
|
||||
if err != nil {
|
||||
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
i, err := h.Store.GetIncidentByID(r.Context(), int64(id))
|
||||
if err != nil {
|
||||
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
h.Store.DeleteIncident(r.Context(), int64(id))
|
||||
|
||||
w.Header().Set("HX-Redirect", fmt.Sprintf("/m/%s", i.Monitor.Uuid))
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) IncidentPage() http.HandlerFunc {
|
||||
tmpl := template.Must(template.ParseFS(html.FS, "layout.html", "app.html", "incident.html"))
|
||||
|
||||
type data struct {
|
||||
User uptimemonitor.User
|
||||
Incident uptimemonitor.Incident
|
||||
Monitor uptimemonitor.Monitor
|
||||
}
|
||||
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
uuid := r.PathValue("incident")
|
||||
incident, err := h.Store.GetIncidentByUuid(r.Context(), uuid)
|
||||
if err != nil {
|
||||
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
if incident.Monitor.Uuid != r.PathValue("monitor") {
|
||||
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
tmpl.Execute(w, data{
|
||||
User: getUserFromRequest(r),
|
||||
Incident: incident,
|
||||
Monitor: incident.Monitor,
|
||||
})
|
||||
}
|
||||
}
|
95
handler/login_handler.go
Normal file
95
handler/login_handler.go
Normal file
@@ -0,0 +1,95 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"net/http"
|
||||
"time"
|
||||
"uptimemonitor"
|
||||
"uptimemonitor/form"
|
||||
"uptimemonitor/html"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
func (h *Handler) LoginPage() http.HandlerFunc {
|
||||
type data struct {
|
||||
Form form.LoginForm
|
||||
}
|
||||
|
||||
tmpl := template.Must(template.ParseFS(html.FS, "layout.html", "login.html"))
|
||||
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
tmpl.Execute(w, data{
|
||||
Form: form.LoginForm{},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) LoginForm() http.HandlerFunc {
|
||||
type data struct {
|
||||
Form form.LoginForm
|
||||
}
|
||||
|
||||
tmpl := template.Must(template.ParseFS(html.FS, "login.html"))
|
||||
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
r.ParseForm()
|
||||
|
||||
f := form.LoginForm{
|
||||
Email: r.PostFormValue("email"),
|
||||
Password: r.PostFormValue("password"),
|
||||
}
|
||||
|
||||
if !f.Validate() {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
tmpl.ExecuteTemplate(w, "login_form", data{
|
||||
Form: f,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
user, err := h.Store.GetUserByEmail(r.Context(), f.Email)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
f.Errors["Email"] = "The credentials do not match our records"
|
||||
tmpl.ExecuteTemplate(w, "login_form", data{
|
||||
Form: f,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if err = bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(f.Password)); err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
f.Errors["Email"] = "The credentials do not match our records"
|
||||
tmpl.ExecuteTemplate(w, "login_form", data{
|
||||
Form: f,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
session, err := h.Store.CreateSession(r.Context(), uptimemonitor.Session{
|
||||
UserID: user.ID,
|
||||
ExpiresAt: time.Now().Add(time.Hour * 24 * 30),
|
||||
User: user,
|
||||
})
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
f.Errors["Email"] = "Something went wrong, try again later"
|
||||
tmpl.ExecuteTemplate(w, "login_form", data{
|
||||
Form: f,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
Name: "session",
|
||||
Value: session.Uuid,
|
||||
HttpOnly: true,
|
||||
SameSite: http.SameSiteLaxMode,
|
||||
Secure: h.Secure,
|
||||
Expires: session.ExpiresAt,
|
||||
})
|
||||
|
||||
w.Header().Set("HX-Redirect", "/")
|
||||
}
|
||||
}
|
34
handler/logout_handler.go
Normal file
34
handler/logout_handler.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
"uptimemonitor"
|
||||
)
|
||||
|
||||
func (h *Handler) Logout() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
session, ok := r.Context().Value(sessionContextKey).(uptimemonitor.Session)
|
||||
if !ok {
|
||||
http.Redirect(w, r, "/login", http.StatusSeeOther)
|
||||
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
|
||||
err := h.Store.RemoveSessionByID(r.Context(), session.ID)
|
||||
if err != nil {
|
||||
// todo: log
|
||||
}
|
||||
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
Name: "session",
|
||||
Value: "",
|
||||
HttpOnly: true,
|
||||
SameSite: http.SameSiteLaxMode,
|
||||
Secure: h.Secure,
|
||||
Expires: time.Now().Add(time.Hour * 24 * 30 * -1),
|
||||
})
|
||||
|
||||
http.Redirect(w, r, "/login", http.StatusSeeOther)
|
||||
}
|
||||
}
|
118
handler/middleware.go
Normal file
118
handler/middleware.go
Normal file
@@ -0,0 +1,118 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"strings"
|
||||
"uptimemonitor"
|
||||
)
|
||||
|
||||
type contextKey string
|
||||
|
||||
const (
|
||||
sessionContextKey contextKey = "session"
|
||||
userContextKey contextKey = "user"
|
||||
)
|
||||
|
||||
func (m *Handler) Installed(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
count, err := m.Store.CountUsers(r.Context())
|
||||
if err != nil {
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if count == 0 && r.Method == http.MethodGet {
|
||||
http.Redirect(w, r, "/setup", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
|
||||
if count == 0 {
|
||||
http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
func (m *Handler) UserFromCookie(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
c, err := r.Cookie("session")
|
||||
if err != nil || c.Value == "" {
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
session, err := m.Store.GetSessionByUuid(r.Context(), c.Value)
|
||||
if err != nil {
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
ctx := context.WithValue(r.Context(), sessionContextKey, session)
|
||||
ctx = context.WithValue(ctx, userContextKey, session.User)
|
||||
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
})
|
||||
}
|
||||
|
||||
func (m *Handler) Authenticated(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
value := r.Context().Value(sessionContextKey)
|
||||
if value == nil {
|
||||
http.Redirect(w, r, "/login", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
|
||||
_, ok := value.(uptimemonitor.Session)
|
||||
if !ok {
|
||||
http.Redirect(w, r, "/login", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
func (m *Handler) Guest(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
value := r.Context().Value(sessionContextKey)
|
||||
if value != nil {
|
||||
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
func (m *Handler) Recoverer(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
slog.Error("panic", "error", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
}
|
||||
}()
|
||||
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
func (m *Handler) NoCache(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == http.MethodGet && strings.HasPrefix(r.URL.Path, "/static") {
|
||||
w.Header().Set("Cache-Control", "public, max-age=31536000")
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
|
||||
w.Header().Set("Pragma", "no-cache")
|
||||
w.Header().Set("Expires", "0")
|
||||
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
397
handler/monitor_handler.go
Normal file
397
handler/monitor_handler.go
Normal file
@@ -0,0 +1,397 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"uptimemonitor"
|
||||
"uptimemonitor/form"
|
||||
"uptimemonitor/html"
|
||||
)
|
||||
|
||||
func (h *Handler) ListMonitors() http.HandlerFunc {
|
||||
tmpl := template.Must(template.ParseFS(html.FS, "monitor.html"))
|
||||
|
||||
type data struct {
|
||||
Monitors []uptimemonitor.Monitor
|
||||
Skeletons []int
|
||||
}
|
||||
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
monitors, err := h.Store.ListMonitors(r.Context())
|
||||
if err != nil {
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
tmpl.ExecuteTemplate(w, "monitor_list", data{
|
||||
Monitors: monitors,
|
||||
Skeletons: make([]int, 60),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) CreateMonitorPage() http.HandlerFunc {
|
||||
tmpl := template.Must(template.ParseFS(html.FS, "layout.html", "app.html", "new.html"))
|
||||
|
||||
type data struct {
|
||||
Form form.MonitorForm
|
||||
User uptimemonitor.User
|
||||
}
|
||||
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
tmpl.Execute(w, data{
|
||||
Form: form.MonitorForm{
|
||||
HttpHeaders: `{
|
||||
"Content-Type": "application/json"
|
||||
}`,
|
||||
HttpBody: `{}`,
|
||||
WebhookHeaders: `{
|
||||
"Content-Type": "application/json"
|
||||
}`,
|
||||
WebhookBody: `{
|
||||
"url": "{{ .Url }}",
|
||||
"code": "{{ .StatusCode }}"
|
||||
}`,
|
||||
},
|
||||
User: getUserFromRequest(r),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) CreateMonitorForm() http.HandlerFunc {
|
||||
tmpl := template.Must(template.ParseFS(html.FS, "new.html"))
|
||||
|
||||
type data struct {
|
||||
Form form.MonitorForm
|
||||
}
|
||||
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
r.ParseForm()
|
||||
|
||||
f := form.MonitorForm{
|
||||
HttpMethod: r.PostFormValue("http_method"),
|
||||
Url: r.PostFormValue("url"),
|
||||
HasCustomHeaders: r.PostFormValue("has_custom_headers") == "on",
|
||||
HasCustomBody: r.PostFormValue("has_custom_body") == "on",
|
||||
HttpHeaders: r.PostFormValue("http_headers"),
|
||||
HttpBody: r.PostFormValue("http_body"),
|
||||
HasWebhook: r.PostFormValue("has_webhook") == "on",
|
||||
WebhookMethod: r.PostFormValue("webhook_method"),
|
||||
WebhookUrl: r.PostFormValue("webhook_url"),
|
||||
WebhookHeaders: r.PostFormValue("webhook_headers"),
|
||||
WebhookBody: r.PostFormValue("webhook_body"),
|
||||
}
|
||||
|
||||
if !f.Validate() {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
tmpl.ExecuteTemplate(w, "new_form", data{Form: f})
|
||||
return
|
||||
}
|
||||
|
||||
monitor := uptimemonitor.Monitor{
|
||||
HttpMethod: f.HttpMethod,
|
||||
Url: f.Url,
|
||||
}
|
||||
|
||||
if f.HasCustomHeaders {
|
||||
monitor.HttpHeaders = f.HttpHeaders
|
||||
}
|
||||
|
||||
if f.HasCustomBody {
|
||||
monitor.HttpBody = f.HttpBody
|
||||
}
|
||||
|
||||
if f.HasWebhook {
|
||||
monitor.WebhookUrl = f.WebhookUrl
|
||||
monitor.WebhookMethod = f.WebhookMethod
|
||||
monitor.WebhookHeaders = f.WebhookHeaders
|
||||
monitor.WebhookBody = f.WebhookBody
|
||||
}
|
||||
|
||||
m, err := h.Store.CreateMonitor(r.Context(), monitor)
|
||||
if err != nil {
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("HX-Redirect", m.URI())
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) MonitorPage() http.HandlerFunc {
|
||||
tmpl := template.Must(template.ParseFS(html.FS, "layout.html", "app.html", "monitor.html"))
|
||||
|
||||
type data struct {
|
||||
Monitor uptimemonitor.Monitor
|
||||
Skeletons []int
|
||||
User uptimemonitor.User
|
||||
}
|
||||
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
m, err := h.Store.GetMonitorByUuid(r.Context(), r.PathValue("monitor"))
|
||||
if err != nil || m.ID == 0 {
|
||||
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
tmpl.Execute(w, data{
|
||||
Monitor: m,
|
||||
Skeletons: make([]int, 60),
|
||||
User: getUserFromRequest(r),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) MonitorStats() http.HandlerFunc {
|
||||
tmpl := template.Must(template.ParseFS(html.FS, "monitor.html"))
|
||||
|
||||
type data struct {
|
||||
ID int64
|
||||
AvgResponseTime int64
|
||||
Uptime string
|
||||
Count int64
|
||||
ChecksCount int64
|
||||
FailureCount int64
|
||||
}
|
||||
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
id, err := strconv.Atoi(r.PathValue("monitor"))
|
||||
if err != nil {
|
||||
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
m, err := h.Store.GetMonitorByID(r.Context(), id)
|
||||
if err != nil {
|
||||
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
tmpl.ExecuteTemplate(w, "monitor_stats", data{
|
||||
ID: int64(id),
|
||||
AvgResponseTime: int64(m.AvgResponseTimeMs),
|
||||
Uptime: fmt.Sprintf("%.1f", m.Uptime),
|
||||
Count: m.IncidentsCount,
|
||||
ChecksCount: m.N,
|
||||
FailureCount: m.IncidentsCount,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) ListMonitorIncidents() http.HandlerFunc {
|
||||
tmpl := template.Must(template.ParseFS(html.FS, "monitor.html"))
|
||||
|
||||
type data struct {
|
||||
ID int64
|
||||
Monitor uptimemonitor.Monitor
|
||||
Incidents []uptimemonitor.Incident
|
||||
}
|
||||
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
id, err := strconv.Atoi(r.PathValue("monitor"))
|
||||
if err != nil {
|
||||
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
m, err := h.Store.GetMonitorByID(r.Context(), id)
|
||||
if err != nil {
|
||||
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
incidents, err := h.Store.ListMonitorIncidents(r.Context(), int64(id))
|
||||
if err != nil {
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
tmpl.ExecuteTemplate(w, "monitor_incident_list", data{
|
||||
ID: int64(id),
|
||||
Monitor: m,
|
||||
Incidents: incidents,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) EditMonitorPage() http.HandlerFunc {
|
||||
tmpl := template.Must(template.ParseFS(html.FS, "layout.html", "app.html", "edit.html"))
|
||||
|
||||
type data struct {
|
||||
Form form.MonitorForm
|
||||
User uptimemonitor.User
|
||||
Monitor uptimemonitor.Monitor
|
||||
}
|
||||
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
m, err := h.Store.GetMonitorByUuid(r.Context(), r.PathValue("monitor"))
|
||||
if err != nil {
|
||||
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
f := form.MonitorForm{
|
||||
Url: m.Url,
|
||||
HttpMethod: m.HttpMethod,
|
||||
HttpHeaders: m.HttpHeaders,
|
||||
HttpBody: m.HttpBody,
|
||||
HasCustomHeaders: m.HttpHeaders != "",
|
||||
HasCustomBody: m.HttpBody != "",
|
||||
HasWebhook: m.WebhookUrl != "",
|
||||
WebhookUrl: m.WebhookUrl,
|
||||
WebhookMethod: m.WebhookMethod,
|
||||
WebhookHeaders: m.WebhookHeaders,
|
||||
WebhookBody: m.WebhookBody,
|
||||
}
|
||||
|
||||
if !f.HasCustomBody {
|
||||
f.HttpBody = "{}"
|
||||
}
|
||||
|
||||
if !f.HasCustomHeaders {
|
||||
f.HttpHeaders = "{}"
|
||||
}
|
||||
|
||||
tmpl.Execute(w, data{
|
||||
Monitor: m,
|
||||
Form: f,
|
||||
User: getUserFromRequest(r),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) EditMonitorForm() http.HandlerFunc {
|
||||
tmpl := template.Must(template.ParseFS(html.FS, "edit.html"))
|
||||
|
||||
type data struct {
|
||||
Form form.MonitorForm
|
||||
User uptimemonitor.User
|
||||
Monitor uptimemonitor.Monitor
|
||||
}
|
||||
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
id, err := strconv.Atoi(r.PathValue("monitor"))
|
||||
if err != nil {
|
||||
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
monitor, err := h.Store.GetMonitorByID(r.Context(), id)
|
||||
if err != nil {
|
||||
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
r.ParseForm()
|
||||
|
||||
customHeaders := r.PostFormValue("http_headers")
|
||||
customBody := r.PostFormValue("http_body")
|
||||
|
||||
if r.PostFormValue("has_custom_headers") != "on" {
|
||||
customHeaders = ""
|
||||
}
|
||||
|
||||
if r.PostFormValue("has_custom_body") != "on" {
|
||||
customBody = ""
|
||||
}
|
||||
|
||||
f := form.MonitorForm{
|
||||
HttpMethod: r.PostFormValue("http_method"),
|
||||
Url: r.PostFormValue("url"),
|
||||
HasCustomHeaders: r.PostFormValue("has_custom_headers") == "on",
|
||||
HasCustomBody: r.PostFormValue("has_custom_body") == "on",
|
||||
HttpHeaders: customHeaders,
|
||||
HttpBody: customBody,
|
||||
HasWebhook: r.PostFormValue("has_webhook") == "on",
|
||||
WebhookMethod: r.PostFormValue("webhook_method"),
|
||||
WebhookUrl: r.PostFormValue("webhook_url"),
|
||||
WebhookHeaders: r.PostFormValue("webhook_headers"),
|
||||
WebhookBody: r.PostFormValue("webhook_body"),
|
||||
}
|
||||
|
||||
if !f.Validate() {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
tmpl.ExecuteTemplate(w, "edit_form", data{
|
||||
Monitor: monitor,
|
||||
Form: f,
|
||||
User: getUserFromRequest(r),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
monitor.Url = f.Url
|
||||
monitor.HttpMethod = f.HttpMethod
|
||||
|
||||
if f.HasCustomHeaders {
|
||||
monitor.HttpHeaders = f.HttpHeaders
|
||||
} else {
|
||||
monitor.HttpHeaders = ""
|
||||
}
|
||||
|
||||
if f.HasCustomBody {
|
||||
monitor.HttpBody = f.HttpBody
|
||||
} else {
|
||||
monitor.HttpBody = ""
|
||||
}
|
||||
|
||||
if f.HasWebhook {
|
||||
monitor.WebhookUrl = f.WebhookUrl
|
||||
monitor.WebhookMethod = f.WebhookMethod
|
||||
monitor.WebhookHeaders = f.WebhookHeaders
|
||||
monitor.WebhookBody = f.WebhookBody
|
||||
} else {
|
||||
monitor.WebhookUrl = ""
|
||||
monitor.WebhookMethod = ""
|
||||
monitor.WebhookHeaders = ""
|
||||
monitor.WebhookBody = ""
|
||||
}
|
||||
|
||||
err = h.Store.UpdateMonitor(r.Context(), monitor)
|
||||
if err != nil {
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("HX-Redirect", monitor.URI())
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) DeleteMonitorPage() http.HandlerFunc {
|
||||
tmpl := template.Must(template.ParseFS(html.FS, "layout.html", "app.html", "delete.html"))
|
||||
|
||||
type data struct {
|
||||
User uptimemonitor.User
|
||||
Monitor uptimemonitor.Monitor
|
||||
}
|
||||
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
uuid := r.PathValue("monitor")
|
||||
m, err := h.Store.GetMonitorByUuid(r.Context(), uuid)
|
||||
if err != nil || m.ID == 0 {
|
||||
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
tmpl.Execute(w, data{
|
||||
User: getUserFromRequest(r),
|
||||
Monitor: m,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) DeleteMonitorForm() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
id, _ := strconv.Atoi(r.PathValue("monitor"))
|
||||
m, err := h.Store.GetMonitorByID(r.Context(), id)
|
||||
if err != nil || m.ID == 0 {
|
||||
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
h.Store.DeleteMonitor(r.Context(), int64(id))
|
||||
|
||||
w.Header().Add("HX-Redirect", "/")
|
||||
}
|
||||
}
|
104
handler/setup_handler.go
Normal file
104
handler/setup_handler.go
Normal file
@@ -0,0 +1,104 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"net/http"
|
||||
"time"
|
||||
"uptimemonitor"
|
||||
"uptimemonitor/form"
|
||||
"uptimemonitor/html"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
func (h *Handler) SetupPage() http.HandlerFunc {
|
||||
type data struct {
|
||||
Form form.SetupForm
|
||||
}
|
||||
|
||||
tmpl := template.Must(template.ParseFS(html.FS, "layout.html", "setup.html"))
|
||||
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
count, err := h.Store.CountUsers(r.Context())
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if count > 0 {
|
||||
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
|
||||
tmpl.Execute(w, data{
|
||||
Form: form.SetupForm{},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) SetupForm() http.HandlerFunc {
|
||||
type data struct {
|
||||
Form form.SetupForm
|
||||
}
|
||||
|
||||
tmpl := template.Must(template.ParseFS(html.FS, "setup.html"))
|
||||
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
r.ParseForm()
|
||||
|
||||
f := form.SetupForm{
|
||||
Name: r.PostFormValue("name"),
|
||||
Email: r.PostFormValue("email"),
|
||||
Password: r.PostFormValue("password"),
|
||||
}
|
||||
|
||||
if !f.Validate() {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
tmpl.ExecuteTemplate(w, "setup_form", data{
|
||||
Form: f,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
hash, err := bcrypt.GenerateFromPassword([]byte(f.Password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
user, err := h.Store.CreateUser(r.Context(), uptimemonitor.User{
|
||||
Name: f.Name,
|
||||
Email: f.Email,
|
||||
PasswordHash: string(hash),
|
||||
})
|
||||
if err != nil {
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
session, err := h.Store.CreateSession(r.Context(), uptimemonitor.Session{
|
||||
UserID: user.ID,
|
||||
ExpiresAt: time.Now().Add(time.Hour * 24 * 30),
|
||||
User: user,
|
||||
})
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
f.Errors["Email"] = "Something went wrong, try again later"
|
||||
tmpl.ExecuteTemplate(w, "login_form", data{
|
||||
Form: f,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
Name: "session",
|
||||
Value: session.Uuid,
|
||||
HttpOnly: true,
|
||||
SameSite: http.SameSiteLaxMode,
|
||||
Secure: h.Secure,
|
||||
Expires: session.ExpiresAt,
|
||||
})
|
||||
|
||||
w.Header().Set("HX-Redirect", "/")
|
||||
}
|
||||
}
|
58
handler/sponsor_handler.go
Normal file
58
handler/sponsor_handler.go
Normal file
@@ -0,0 +1,58 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"uptimemonitor"
|
||||
"uptimemonitor/html"
|
||||
)
|
||||
|
||||
func (*Handler) ListSponsors() http.HandlerFunc {
|
||||
layout := template.Must(template.ParseFS(html.FS, "layout.html"))
|
||||
sponsor := template.Must(template.ParseFS(html.FS, "sponsor.html"))
|
||||
|
||||
type data struct {
|
||||
Sponsors []uptimemonitor.Sponsor
|
||||
}
|
||||
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Header.Get("HX-Request") != "true" {
|
||||
layout.ExecuteTemplate(w, "sponsors", nil)
|
||||
return
|
||||
}
|
||||
|
||||
d := data{
|
||||
Sponsors: []uptimemonitor.Sponsor{
|
||||
{
|
||||
Name: "AIR Labs",
|
||||
Url: "https://airlabs.pl",
|
||||
Image: "/static/img/airlabs.svg",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, "https://sponsors.uptimemonitor.dev", nil)
|
||||
if err != nil {
|
||||
sponsor.Execute(w, d)
|
||||
return
|
||||
}
|
||||
|
||||
res, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
sponsor.Execute(w, d)
|
||||
return
|
||||
}
|
||||
|
||||
var sponsors []uptimemonitor.Sponsor
|
||||
err = json.NewDecoder(res.Body).Decode(&sponsors)
|
||||
if err != nil {
|
||||
sponsor.Execute(w, d)
|
||||
return
|
||||
}
|
||||
|
||||
sponsor.Execute(w, data{
|
||||
Sponsors: sponsors,
|
||||
})
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user