error modals are so on
This commit is contained in:
@@ -7,25 +7,6 @@ import (
|
||||
"git.haelnorr.com/h/oslstats/internal/view/page"
|
||||
)
|
||||
|
||||
// func ErrorPage(
|
||||
// error hws.HWSError,
|
||||
// ) (hws.ErrorPage, error) {
|
||||
// messages := map[int]string{
|
||||
// 400: "The request you made was malformed or unexpected.",
|
||||
// 401: "You need to login to view this page.",
|
||||
// 403: "You do not have permission to view this page.",
|
||||
// 404: "The page or resource you have requested does not exist.",
|
||||
// 500: `An error occured on the server. Please try again, and if this
|
||||
// continues to happen contact an administrator.`,
|
||||
// 503: "The server is currently down for maintenance and should be back soon. =)",
|
||||
// }
|
||||
// msg, exists := messages[error.StatusCode]
|
||||
// if !exists {
|
||||
// return nil, errors.New("No valid message for the given code")
|
||||
// }
|
||||
// return page.Error(error.StatusCode, http.StatusText(error.StatusCode), msg), nil
|
||||
// }
|
||||
|
||||
func ErrorPage(hwsError hws.HWSError) (hws.ErrorPage, error) {
|
||||
// Determine if this status code should show technical details
|
||||
showDetails := shouldShowDetails(hwsError.StatusCode)
|
||||
@@ -40,7 +21,7 @@ func ErrorPage(hwsError hws.HWSError) (hws.ErrorPage, error) {
|
||||
// Get technical details if applicable
|
||||
var details string
|
||||
if showDetails && hwsError.Error != nil {
|
||||
details = hwsError.Error.Error()
|
||||
details = formatErrorDetails(hwsError.Error)
|
||||
}
|
||||
|
||||
// Render appropriate template
|
||||
@@ -63,7 +44,7 @@ func ErrorPage(hwsError hws.HWSError) (hws.ErrorPage, error) {
|
||||
// shouldShowDetails determines if a status code should display technical details
|
||||
func shouldShowDetails(statusCode int) bool {
|
||||
switch statusCode {
|
||||
case 400, 500, 503: // Bad Request, Internal Server Error, Service Unavailable
|
||||
case 400, 418, 500, 503: // Bad Request, Internal Server Error, Service Unavailable
|
||||
return true
|
||||
case 401, 403, 404: // Unauthorized, Forbidden, Not Found
|
||||
return false
|
||||
@@ -80,6 +61,7 @@ func getDefaultMessage(statusCode int) string {
|
||||
401: "You need to login to view this page.",
|
||||
403: "You do not have permission to view this page.",
|
||||
404: "The page or resource you have requested does not exist.",
|
||||
418: "I'm a teapot!",
|
||||
500: `An error occurred on the server. Please try again, and if this
|
||||
continues to happen contact an administrator.`,
|
||||
503: "The server is currently down for maintenance and should be back soon. =)",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
@@ -107,3 +108,50 @@ func throwNotFound(
|
||||
err := errors.New("Resource not found")
|
||||
throwError(s, w, r, http.StatusNotFound, msg, err, "debug")
|
||||
}
|
||||
|
||||
// ErrorDetails contains structured error information for WebSocket error modals
|
||||
type ErrorDetails struct {
|
||||
Code int `json:"code"`
|
||||
Stacktrace string `json:"stacktrace"`
|
||||
}
|
||||
|
||||
// formatErrorDetails extracts and formats error details from wrapped errors
|
||||
func formatErrorDetails(err error) string {
|
||||
if err == nil {
|
||||
return ""
|
||||
}
|
||||
// Use %+v format to get stack trace from github.com/pkg/errors
|
||||
return fmt.Sprintf("%+v", err)
|
||||
}
|
||||
|
||||
// SerializeErrorDetails creates a JSON string with code and stacktrace
|
||||
// This is exported so it can be used when creating error notifications
|
||||
func SerializeErrorDetails(code int, err error) string {
|
||||
details := ErrorDetails{
|
||||
Code: code,
|
||||
Stacktrace: formatErrorDetails(err),
|
||||
}
|
||||
jsonData, jsonErr := json.Marshal(details)
|
||||
if jsonErr != nil {
|
||||
// Fallback if JSON encoding fails
|
||||
return fmt.Sprintf(`{"code":%d,"stacktrace":"Failed to serialize error"}`, code)
|
||||
}
|
||||
return string(jsonData)
|
||||
}
|
||||
|
||||
// parseErrorDetails extracts code and stacktrace from JSON Details field
|
||||
// Returns (code, stacktrace). If parsing fails, returns (500, original details string)
|
||||
func parseErrorDetails(details string) (int, string) {
|
||||
if details == "" {
|
||||
return 500, ""
|
||||
}
|
||||
|
||||
var errDetails ErrorDetails
|
||||
err := json.Unmarshal([]byte(details), &errDetails)
|
||||
if err != nil {
|
||||
// Not JSON or malformed - treat as plain stacktrace with default code
|
||||
return 500, details
|
||||
}
|
||||
|
||||
return errDetails.Code, errDetails.Stacktrace
|
||||
}
|
||||
|
||||
@@ -1,141 +0,0 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"git.haelnorr.com/h/golib/hws"
|
||||
)
|
||||
|
||||
// NotificationType defines the type of notification
|
||||
type NotificationType string
|
||||
|
||||
const (
|
||||
NotificationSuccess NotificationType = "success"
|
||||
NotificationWarning NotificationType = "warning"
|
||||
NotificationInfo NotificationType = "info"
|
||||
)
|
||||
|
||||
// Notification represents a toast notification (success, warning, info)
|
||||
type Notification struct {
|
||||
Type NotificationType `json:"type"`
|
||||
Title string `json:"title"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
// ErrorModal represents a full-screen error modal (500, 503)
|
||||
type ErrorModal struct {
|
||||
Code int `json:"code"`
|
||||
Title string `json:"title"`
|
||||
Message string `json:"message"`
|
||||
Details string `json:"details"`
|
||||
}
|
||||
|
||||
// setHXTrigger sets the HX-Trigger header with JSON-encoded data
|
||||
func setHXTrigger(w http.ResponseWriter, event string, data any) {
|
||||
payload := map[string]any{
|
||||
event: data,
|
||||
}
|
||||
|
||||
jsonData, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
// Fallback if JSON encoding fails
|
||||
w.Header().Set("HX-Trigger", event)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("HX-Trigger", string(jsonData))
|
||||
}
|
||||
|
||||
// formatErrorDetails extracts and formats error details from wrapped errors
|
||||
func formatErrorDetails(err error) string {
|
||||
if err == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
// Use %+v format to get stack trace from github.com/pkg/errors
|
||||
return fmt.Sprintf("%+v", err)
|
||||
}
|
||||
|
||||
// notifyToast sends a toast notification via HX-Trigger header
|
||||
func notifyToast(
|
||||
w http.ResponseWriter,
|
||||
notifType NotificationType,
|
||||
title string,
|
||||
message string,
|
||||
) {
|
||||
notification := Notification{
|
||||
Type: notifType,
|
||||
Title: title,
|
||||
Message: message,
|
||||
}
|
||||
|
||||
setHXTrigger(w, "showNotification", notification)
|
||||
}
|
||||
|
||||
// notifyErrorModal sends a full-screen error modal via HX-Trigger header
|
||||
func notifyErrorModal(
|
||||
s *hws.Server,
|
||||
w http.ResponseWriter,
|
||||
statusCode int,
|
||||
title string,
|
||||
message string,
|
||||
err error,
|
||||
) {
|
||||
modal := ErrorModal{
|
||||
Code: statusCode,
|
||||
Title: title,
|
||||
Message: message,
|
||||
Details: formatErrorDetails(err),
|
||||
}
|
||||
|
||||
// Log the error
|
||||
s.LogError(hws.HWSError{
|
||||
StatusCode: statusCode,
|
||||
Message: message,
|
||||
Error: err,
|
||||
Level: hws.ErrorERROR,
|
||||
})
|
||||
|
||||
// Set response status
|
||||
w.WriteHeader(statusCode)
|
||||
|
||||
// Send notification via HX-Trigger
|
||||
setHXTrigger(w, "showErrorModal", modal)
|
||||
}
|
||||
|
||||
// notifySuccess sends a success toast notification
|
||||
func notifySuccess(w http.ResponseWriter, title string, message string) {
|
||||
notifyToast(w, NotificationSuccess, title, message)
|
||||
}
|
||||
|
||||
// notifyWarning sends a warning toast notification
|
||||
func notifyWarning(w http.ResponseWriter, title string, message string) {
|
||||
notifyToast(w, NotificationWarning, title, message)
|
||||
}
|
||||
|
||||
// notifyInfo sends an info toast notification
|
||||
func notifyInfo(w http.ResponseWriter, title string, message string) {
|
||||
notifyToast(w, NotificationInfo, title, message)
|
||||
}
|
||||
|
||||
// notifyInternalServiceError sends a 500 error modal
|
||||
func notifyInternalServiceError(
|
||||
s *hws.Server,
|
||||
w http.ResponseWriter,
|
||||
message string,
|
||||
err error,
|
||||
) {
|
||||
notifyErrorModal(s, w, 500, "Internal Server Error", message, err)
|
||||
}
|
||||
|
||||
// notifyServiceUnavailable sends a 503 error modal
|
||||
func notifyServiceUnavailable(
|
||||
s *hws.Server,
|
||||
w http.ResponseWriter,
|
||||
message string,
|
||||
err error,
|
||||
) {
|
||||
notifyErrorModal(s, w, 503, "Service Unavailable", message, err)
|
||||
}
|
||||
@@ -104,7 +104,9 @@ func notifyLoop(ctx context.Context, c *hws.Client, ws *websocket.Conn) error {
|
||||
case notify.LevelWarn:
|
||||
err = popup.Toast(nt, count, 10000).Render(ctx, w)
|
||||
case notify.LevelError:
|
||||
// do error modal
|
||||
// Parse error code and stacktrace from Details field
|
||||
code, stacktrace := parseErrorDetails(nt.Details)
|
||||
err = popup.ErrorModalWS(code, stacktrace, nt, count).Render(ctx, w)
|
||||
default:
|
||||
err = popup.Toast(nt, count, 6000).Render(ctx, w)
|
||||
}
|
||||
|
||||
@@ -3,11 +3,11 @@ package handlers
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"git.haelnorr.com/h/oslstats/internal/view/page"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"git.haelnorr.com/h/golib/hws"
|
||||
"git.haelnorr.com/h/golib/notify"
|
||||
"git.haelnorr.com/h/oslstats/internal/view/page"
|
||||
)
|
||||
|
||||
// Handles responses to the / path. Also serves a 404 Page for paths that
|
||||
@@ -15,7 +15,14 @@ import (
|
||||
func NotifyTester(server *hws.Server) http.Handler {
|
||||
return http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
testErr := errors.New("This is a stack trace. No really i swear. Just pretend ok? Thanks")
|
||||
if r.Method == "GET" {
|
||||
// page, _ := ErrorPage(hws.HWSError{
|
||||
// StatusCode: http.StatusTeapot,
|
||||
// Message: "This error has been rendered as a test",
|
||||
// Error: testErr,
|
||||
// })
|
||||
// page.Render(r.Context(), w)
|
||||
page.Test().Render(r.Context(), w)
|
||||
} else {
|
||||
r.ParseForm()
|
||||
@@ -27,16 +34,20 @@ func NotifyTester(server *hws.Server) http.Handler {
|
||||
"warn": notify.LevelWarn,
|
||||
"error": notify.LevelError,
|
||||
}[r.Form.Get("type")]
|
||||
error := errors.New("This is a stack trace. No really i swear. Just pretend ok? Thanks")
|
||||
message := r.Form.Get("message")
|
||||
nt := notify.Notification{
|
||||
Target: notify.Target(target),
|
||||
Title: title,
|
||||
Message: message,
|
||||
Level: level,
|
||||
Details: error.Error(),
|
||||
}
|
||||
if target == "" {
|
||||
|
||||
// For error level, serialize error details with code
|
||||
if level == notify.LevelError {
|
||||
nt.Details = SerializeErrorDetails(500, testErr)
|
||||
}
|
||||
|
||||
if target == "all" {
|
||||
server.NotifyAll(nt)
|
||||
} else {
|
||||
server.NotifySub(nt)
|
||||
@@ -45,7 +56,3 @@ func NotifyTester(server *hws.Server) http.Handler {
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func notifyInfoWS() {
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user