mirror of
https://github.com/juanfont/headscale.git
synced 2026-04-23 17:18:50 +02:00
templates: add error box component and error page template
Add errorBox() and errorIcon() to the design system, mirroring the existing successBox()/checkboxIcon() pattern with red error styling. Extract error color constants from the inline values in statusMessage(). Add AuthError() template that renders a styled HTML error page using the same HtmlStructure/mdTypesetBody/logo/footer as all other browser-facing pages. Updates juanfont/headscale#3182
This commit is contained in:
41
hscontrol/templates/auth_error.go
Normal file
41
hscontrol/templates/auth_error.go
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
package templates
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/chasefleming/elem-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AuthErrorResult contains the text content for an error page shown
|
||||||
|
// to users in their browser when a browser-facing operation fails
|
||||||
|
// (OIDC callback, SSH check, registration confirmation, etc.).
|
||||||
|
type AuthErrorResult struct {
|
||||||
|
// Title is the browser tab / page title,
|
||||||
|
// e.g. "Headscale - Error".
|
||||||
|
Title string
|
||||||
|
|
||||||
|
// Heading is the bold red text inside the error box,
|
||||||
|
// e.g. "Forbidden".
|
||||||
|
Heading string
|
||||||
|
|
||||||
|
// Message is the actionable user-facing message shown below
|
||||||
|
// the heading, e.g. "You are not authorized. Please contact
|
||||||
|
// your administrator."
|
||||||
|
Message string
|
||||||
|
}
|
||||||
|
|
||||||
|
// AuthError renders a styled error page for browser-facing failures.
|
||||||
|
// The caller controls every user-visible string via [AuthErrorResult].
|
||||||
|
func AuthError(result AuthErrorResult) *elem.Element {
|
||||||
|
box := errorBox(
|
||||||
|
result.Heading,
|
||||||
|
elem.Text(result.Message),
|
||||||
|
)
|
||||||
|
|
||||||
|
return HtmlStructure(
|
||||||
|
elem.Title(nil, elem.Text(result.Title)),
|
||||||
|
mdTypesetBody(
|
||||||
|
headscaleLogo(),
|
||||||
|
box,
|
||||||
|
pageFooter(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -39,6 +39,10 @@ const (
|
|||||||
// Success colors.
|
// Success colors.
|
||||||
colorSuccess = "#059669" //nolint:unused // Success states
|
colorSuccess = "#059669" //nolint:unused // Success states
|
||||||
colorSuccessLight = "#d1fae5" //nolint:unused // Success backgrounds
|
colorSuccessLight = "#d1fae5" //nolint:unused // Success backgrounds
|
||||||
|
|
||||||
|
// Error colors.
|
||||||
|
colorError = "#dc2626" //nolint:unused // Error states (red-600)
|
||||||
|
colorErrorLight = "#fee2e2" //nolint:unused // Error backgrounds (red-100)
|
||||||
)
|
)
|
||||||
|
|
||||||
// Spacing System
|
// Spacing System
|
||||||
@@ -406,6 +410,47 @@ func checkboxIcon() elem.Node {
|
|||||||
</svg>`)
|
</svg>`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// errorBox creates a red error feedback box with an X-circle icon.
|
||||||
|
// The heading is displayed as bold red text, and children are rendered below it.
|
||||||
|
// Pairs with successBox for consistent feedback styling.
|
||||||
|
//
|
||||||
|
//nolint:unused // Used in auth_error.go template.
|
||||||
|
func errorBox(heading string, children ...elem.Node) *elem.Element {
|
||||||
|
return elem.Div(attrs.Props{
|
||||||
|
attrs.Style: styles.Props{
|
||||||
|
styles.Display: "flex",
|
||||||
|
styles.AlignItems: "center",
|
||||||
|
styles.Gap: spaceM,
|
||||||
|
styles.Padding: spaceL,
|
||||||
|
styles.BackgroundColor: colorErrorLight,
|
||||||
|
styles.Border: "1px solid " + colorError,
|
||||||
|
styles.BorderRadius: "0.5rem",
|
||||||
|
styles.MarginBottom: spaceXL,
|
||||||
|
}.ToInline(),
|
||||||
|
},
|
||||||
|
errorIcon(),
|
||||||
|
elem.Div(nil,
|
||||||
|
append([]elem.Node{
|
||||||
|
elem.Strong(attrs.Props{
|
||||||
|
attrs.Style: styles.Props{
|
||||||
|
styles.Display: "block",
|
||||||
|
styles.Color: colorError,
|
||||||
|
styles.FontSize: fontSizeH3,
|
||||||
|
styles.MarginBottom: spaceXS,
|
||||||
|
}.ToInline(),
|
||||||
|
}, elem.Text(heading)),
|
||||||
|
}, children...)...,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// errorIcon returns the error X-circle SVG icon as raw HTML.
|
||||||
|
func errorIcon() elem.Node {
|
||||||
|
return elem.Raw(`<svg id="error-icon" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 512 512">
|
||||||
|
<path fill="#dc2626" d="M256 32C132.3 32 32 132.3 32 256s100.3 224 224 224 224-100.3 224-224S379.7 32 256 32zm97.7 272.7c6.2 6.2 6.2 16.4 0 22.6-3.1 3.1-7.2 4.7-11.3 4.7s-8.2-1.6-11.3-4.7L256 251.3l-75.1 76c-3.1 3.1-7.2 4.7-11.3 4.7s-8.2-1.6-11.3-4.7c-6.2-6.2-6.2-16.4 0-22.6l75.1-76-75.1-76c-6.2-6.2-6.2-16.4 0-22.6s16.4-6.2 22.6 0l75.1 76 75.1-76c6.2-6.2 16.4-6.2 22.6 0s6.2 16.4 0 22.6L278.6 229l75.1 75.7z"></path>
|
||||||
|
</svg>`)
|
||||||
|
}
|
||||||
|
|
||||||
// warningBox creates a warning message box with icon and content.
|
// warningBox creates a warning message box with icon and content.
|
||||||
//
|
//
|
||||||
//nolint:unused // Used in apple.go template.
|
//nolint:unused // Used in apple.go template.
|
||||||
@@ -504,8 +549,8 @@ func statusMessage(message string, isSuccess bool) *elem.Element {
|
|||||||
textColor := colorSuccess
|
textColor := colorSuccess
|
||||||
|
|
||||||
if !isSuccess {
|
if !isSuccess {
|
||||||
bgColor = "#fee2e2" // red-100
|
bgColor = colorErrorLight
|
||||||
textColor = "#dc2626" // red-600
|
textColor = colorError
|
||||||
}
|
}
|
||||||
|
|
||||||
return elem.Div(attrs.Props{
|
return elem.Div(attrs.Props{
|
||||||
|
|||||||
Reference in New Issue
Block a user