diff --git a/hscontrol/templates/apple.go b/hscontrol/templates/apple.go index 3b120069..8376b19a 100644 --- a/hscontrol/templates/apple.go +++ b/hscontrol/templates/apple.go @@ -1,191 +1,196 @@ package templates import ( - "fmt" - "github.com/chasefleming/elem-go" "github.com/chasefleming/elem-go/attrs" "github.com/chasefleming/elem-go/styles" ) func Apple(url string) *elem.Element { - return HtmlStructure( - elem.Title(nil, - elem.Text("headscale - Apple")), - mdTypesetBody( - headscaleLogo(), - H1(elem.Text("iOS configuration")), - H2(elem.Text("GUI")), - Ol( - elem.Li( - nil, - elem.Text("Install the official Tailscale iOS client from the "), - externalLink("https://apps.apple.com/app/tailscale/id1470499037", "App Store"), - ), - elem.Li( - nil, - elem.Text("Open the "), - elem.Strong(nil, elem.Text("Tailscale")), - elem.Text(" app"), - ), - elem.Li( - nil, - elem.Text("Click the account icon in the top-right corner and select "), - elem.Strong(nil, elem.Text("Log in…")), - ), - elem.Li( - nil, - elem.Text("Tap the top-right options menu button and select "), - elem.Strong(nil, elem.Text("Use custom coordination server")), - ), - elem.Li( - nil, - elem.Text("Enter your instance URL: "), - Code(elem.Text(url)), - ), - elem.Li( - nil, - elem.Text( - "Enter your credentials and log in. Headscale should now be working on your iOS device", - ), - ), + return page( + "headscale - Apple", + H1(elem.Text("iOS configuration")), + H2(elem.Text("GUI")), + Ol( + elem.Li( + nil, + elem.Text("Install the official Tailscale iOS client from the "), + externalLink("https://apps.apple.com/app/tailscale/id1470499037", "App Store"), ), - H1(elem.Text("macOS configuration")), - H2(elem.Text("Command line")), - P( - elem.Text("Use Tailscale's login command to add your profile:"), + elem.Li( + nil, + elem.Text("Open the "), + elem.Strong(nil, elem.Text("Tailscale")), + elem.Text(" app"), ), - Pre(PreCode("tailscale login --login-server "+url)), - H2(elem.Text("GUI")), - Ol( - elem.Li( - nil, - elem.Text("Option + Click the "), - elem.Strong(nil, elem.Text("Tailscale")), - elem.Text(" icon in the menu and hover over the "), - elem.Strong(nil, elem.Text("Debug")), - elem.Text(" menu"), - ), - elem.Li(nil, - elem.Text("Under "), - elem.Strong(nil, elem.Text("Custom Login Server")), - elem.Text(", select "), - elem.Strong(nil, elem.Text("Add Account...")), - ), - elem.Li( - nil, - elem.Text("Enter "), - Code(elem.Text(url)), - elem.Text(" of the headscale instance and press "), - elem.Strong(nil, elem.Text("Add Account")), - ), - elem.Li(nil, - elem.Text("Follow the login procedure in the browser"), - ), + elem.Li( + nil, + elem.Text("Click the account icon in the top-right corner and select "), + elem.Strong(nil, elem.Text("Log in…")), ), - H2(elem.Text("Profiles")), - P( + elem.Li( + nil, + elem.Text("Tap the top-right options menu button and select "), + elem.Strong(nil, elem.Text("Use custom coordination server")), + ), + elem.Li( + nil, + elem.Text("Enter your instance URL: "), + Code(elem.Text(url)), + ), + elem.Li( + nil, elem.Text( - "Headscale can be set to the default server by installing a Headscale configuration profile:", + "Enter your credentials and log in. Headscale should now be working on your iOS device", ), ), - elem.Div(attrs.Props{attrs.Style: styles.Props{styles.MarginTop: spaceL, styles.MarginBottom: spaceL}.ToInline()}, - downloadButton("/apple/macos-app-store", "macOS AppStore profile"), - downloadButton("/apple/macos-standalone", "macOS Standalone profile"), + ), + H1(elem.Text("macOS configuration")), + H2(elem.Text("Command line")), + P( + elem.Text("Use Tailscale's login command to add your profile:"), + ), + codeBlockText("tailscale login --login-server "+url), + H2(elem.Text("GUI")), + Ol( + elem.Li( + nil, + elem.Text("Option + Click the "), + elem.Strong(nil, elem.Text("Tailscale")), + elem.Text(" icon in the menu and hover over the "), + elem.Strong(nil, elem.Text("Debug")), + elem.Text(" menu"), ), - Ol( - elem.Li( - nil, - elem.Text( - "Download the profile, then open it. When it has been opened, there should be a notification that a profile can be installed", - ), - ), - elem.Li(nil, - elem.Text("Open "), - elem.Strong(nil, elem.Text("System Preferences")), - elem.Text(" and go to "), - elem.Strong(nil, elem.Text("Profiles")), - ), - elem.Li(nil, - elem.Text("Find and install the "), - elem.Strong(nil, elem.Text("Headscale")), - elem.Text(" profile"), - ), - elem.Li(nil, - elem.Text("Restart "), - elem.Strong(nil, elem.Text("Tailscale.app")), - elem.Text(" and log in"), - ), + elem.Li( + nil, + elem.Text("Under "), + elem.Strong(nil, elem.Text("Custom Login Server")), + elem.Text(", select "), + elem.Strong(nil, elem.Text("Add Account...")), ), - orDivider(), - P( + elem.Li( + nil, + elem.Text("Enter "), + Code(elem.Text(url)), + elem.Text(" of the headscale instance and press "), + elem.Strong(nil, elem.Text("Add Account")), + ), + elem.Li( + nil, + elem.Text("Follow the login procedure in the browser"), + ), + ), + H2(elem.Text("Profiles")), + P( + elem.Text( + "Headscale can be set to the default server by installing a Headscale configuration profile:", + ), + ), + elem.Div( + attrs.Props{attrs.Style: styles.Props{styles.MarginTop: spaceL, styles.MarginBottom: spaceL}.ToInline()}, + downloadButton("/apple/macos-app-store", "macOS AppStore profile"), + downloadButton("/apple/macos-standalone", "macOS Standalone profile"), + ), + Ol( + elem.Li( + nil, elem.Text( - "Use your terminal to configure the default setting for Tailscale by issuing one of the following commands:", + "Download the profile, then open it. When it has been opened, there should be a notification that a profile can be installed", ), ), - P(elem.Text("For app store client:")), - Pre(PreCode("defaults write io.tailscale.ipn.macos ControlURL "+url)), - P(elem.Text("For standalone client:")), - Pre(PreCode("defaults write io.tailscale.ipn.macsys ControlURL "+url)), - P( + elem.Li( + nil, + elem.Text("Open "), + elem.Strong(nil, elem.Text("System Preferences")), + elem.Text(" and go to "), + elem.Strong(nil, elem.Text("Profiles")), + ), + elem.Li( + nil, + elem.Text("Find and install the "), + elem.Strong(nil, elem.Text("Headscale")), + elem.Text(" profile"), + ), + elem.Li( + nil, elem.Text("Restart "), elem.Strong(nil, elem.Text("Tailscale.app")), - elem.Text(" and log in."), + elem.Text(" and log in"), ), - warningBox("Caution", "You should always download and inspect the profile before installing it."), - P(elem.Text("For app store client:")), - Pre(PreCode(fmt.Sprintf(`curl %s/apple/macos-app-store`, url))), - P(elem.Text("For standalone client:")), - Pre(PreCode(fmt.Sprintf(`curl %s/apple/macos-standalone`, url))), - H1(elem.Text("tvOS configuration")), - H2(elem.Text("GUI")), - Ol( - elem.Li( - nil, - elem.Text("Install the official Tailscale tvOS client from the "), - externalLink("https://apps.apple.com/app/tailscale/id1470499037", "App Store"), - ), - elem.Li( - nil, - elem.Text("Open "), - elem.Strong(nil, elem.Text("Settings")), - elem.Text(" (the Apple tvOS settings) > "), - elem.Strong(nil, elem.Text("Apps")), - elem.Text(" > "), - elem.Strong(nil, elem.Text("Tailscale")), - ), - elem.Li( - nil, - elem.Text("Enter "), - Code(elem.Text(url)), - elem.Text(" under "), - elem.Strong(nil, elem.Text("ALTERNATE COORDINATION SERVER URL")), - ), - elem.Li(nil, - elem.Text("Return to the tvOS "), - elem.Strong(nil, elem.Text("Home")), - elem.Text(" screen"), - ), - elem.Li(nil, - elem.Text("Open "), - elem.Strong(nil, elem.Text("Tailscale")), - ), - elem.Li(nil, - elem.Text("Select "), - elem.Strong(nil, elem.Text("Install VPN configuration")), - ), - elem.Li(nil, - elem.Text("Select "), - elem.Strong(nil, elem.Text("Allow")), - ), - elem.Li(nil, - elem.Text("Scan the QR code and follow the login procedure"), - ), - elem.Li(nil, - elem.Text("Headscale should now be working on your tvOS device"), - ), + ), + orDivider(), + P( + elem.Text( + "Use your terminal to configure the default setting for Tailscale by issuing one of the following commands:", + ), + ), + P(elem.Text("For app store client:")), + codeBlockText("defaults write io.tailscale.ipn.macos ControlURL "+url), + P(elem.Text("For standalone client:")), + codeBlockText("defaults write io.tailscale.ipn.macsys ControlURL "+url), + P( + elem.Text("Restart "), + elem.Strong(nil, elem.Text("Tailscale.app")), + elem.Text(" and log in."), + ), + warningBox("Caution", "You should always download and inspect the profile before installing it."), + P(elem.Text("For app store client:")), + codeBlockText("curl "+url+"/apple/macos-app-store"), + P(elem.Text("For standalone client:")), + codeBlockText("curl "+url+"/apple/macos-standalone"), + H1(elem.Text("tvOS configuration")), + H2(elem.Text("GUI")), + Ol( + elem.Li( + nil, + elem.Text("Install the official Tailscale tvOS client from the "), + externalLink("https://apps.apple.com/app/tailscale/id1470499037", "App Store"), + ), + elem.Li( + nil, + elem.Text("Open "), + elem.Strong(nil, elem.Text("Settings")), + elem.Text(" (the Apple tvOS settings) > "), + elem.Strong(nil, elem.Text("Apps")), + elem.Text(" > "), + elem.Strong(nil, elem.Text("Tailscale")), + ), + elem.Li( + nil, + elem.Text("Enter "), + Code(elem.Text(url)), + elem.Text(" under "), + elem.Strong(nil, elem.Text("ALTERNATE COORDINATION SERVER URL")), + ), + elem.Li( + nil, + elem.Text("Return to the tvOS "), + elem.Strong(nil, elem.Text("Home")), + elem.Text(" screen"), + ), + elem.Li( + nil, + elem.Text("Open "), + elem.Strong(nil, elem.Text("Tailscale")), + ), + elem.Li( + nil, + elem.Text("Select "), + elem.Strong(nil, elem.Text("Install VPN configuration")), + ), + elem.Li( + nil, + elem.Text("Select "), + elem.Strong(nil, elem.Text("Allow")), + ), + elem.Li( + nil, + elem.Text("Scan the QR code and follow the login procedure"), + ), + elem.Li( + nil, + elem.Text("Headscale should now be working on your tvOS device"), ), - pageFooter(), ), ) } diff --git a/hscontrol/templates/auth_error.go b/hscontrol/templates/auth_error.go index 7b48974c..8bcaa806 100644 --- a/hscontrol/templates/auth_error.go +++ b/hscontrol/templates/auth_error.go @@ -30,12 +30,8 @@ func AuthError(result AuthErrorResult) *elem.Element { elem.Text(result.Message), ) - return HtmlStructure( - elem.Title(nil, elem.Text(result.Title)), - mdTypesetBody( - headscaleLogo(), - box, - pageFooter(), - ), + return page( + result.Title, + box, ) } diff --git a/hscontrol/templates/auth_success.go b/hscontrol/templates/auth_success.go index c0f275e5..631de3b1 100644 --- a/hscontrol/templates/auth_success.go +++ b/hscontrol/templates/auth_success.go @@ -41,22 +41,20 @@ func AuthSuccess(result AuthSuccessResult) *elem.Element { elem.Text(". "+result.Message), ) - return HtmlStructure( - elem.Title(nil, elem.Text(result.Title)), - mdTypesetBody( - headscaleLogo(), - box, - H2(elem.Text("Getting started")), - P(elem.Text("Check out the documentation to learn more about headscale and Tailscale:")), - Ul( - elem.Li(nil, - externalLink("https://headscale.net/stable/", "Headscale documentation"), - ), - elem.Li(nil, - externalLink("https://tailscale.com/docs", "Tailscale docs"), - ), + return page( + result.Title, + box, + H2(elem.Text("Getting started")), + P(elem.Text("Check out the documentation to learn more about headscale and Tailscale:")), + Ul( + elem.Li( + nil, + externalLink("https://headscale.net/stable/", "Headscale documentation"), + ), + elem.Li( + nil, + externalLink("https://tailscale.com/docs", "Tailscale docs"), ), - pageFooter(), ), ) } diff --git a/hscontrol/templates/auth_web.go b/hscontrol/templates/auth_web.go index 8b6d6f97..ff97250d 100644 --- a/hscontrol/templates/auth_web.go +++ b/hscontrol/templates/auth_web.go @@ -8,14 +8,10 @@ import ( // to complete an authentication or registration flow. // It is used by both the registration and auth-approve web handlers. func AuthWeb(title, description, command string) *elem.Element { - return HtmlStructure( - elem.Title(nil, elem.Text(title+" - Headscale")), - mdTypesetBody( - headscaleLogo(), - H1(elem.Text(title)), - P(elem.Text(description)), - Pre(PreCode(command)), - pageFooter(), - ), + return page( + title+" - Headscale", + H1(elem.Text(title)), + P(elem.Text(description)), + codeBlockText(command), ) } diff --git a/hscontrol/templates/design.go b/hscontrol/templates/design.go index 7c9194b0..422d85db 100644 --- a/hscontrol/templates/design.go +++ b/hscontrol/templates/design.go @@ -78,12 +78,17 @@ func orDivider() *elem.Element { ) } -// successBox creates a green success feedback box with a checkmark icon. -// The heading is displayed as bold green text, and children are rendered below it. -// Pairs with warningBox for consistent feedback styling. +// feedbackBox creates a coloured feedback box with an icon and a bold heading. +// colorVar provides both the border and heading colour, bgVar the background; +// role and ariaLive set the accessibility attributes. Children render below the +// heading. // -//nolint:unused // Used in auth_success.go template. -func successBox(heading string, children ...elem.Node) *elem.Element { +//nolint:unused // Wrapped by successBox and errorBox. +func feedbackBox( + icon elem.Node, + colorVar, bgVar, role, ariaLive, heading string, + children ...elem.Node, +) *elem.Element { return elem.Div( attrs.Props{ attrs.Style: styles.Props{ @@ -91,22 +96,22 @@ func successBox(heading string, children ...elem.Node) *elem.Element { styles.AlignItems: cssCenter, styles.Gap: spaceM, styles.Padding: spaceL, - styles.BackgroundColor: "var(--hs-success-bg)", - styles.Border: "1px solid var(--hs-success)", + styles.BackgroundColor: bgVar, + styles.Border: "1px solid " + colorVar, styles.BorderRadius: spaceS, styles.MarginBottom: spaceXL, }.ToInline(), - attrs.Role: "status", - "aria-live": "polite", + attrs.Role: role, + "aria-live": ariaLive, }, - checkboxIcon(), + icon, elem.Div( nil, append([]elem.Node{ elem.Strong(attrs.Props{ attrs.Style: styles.Props{ styles.Display: "block", - styles.Color: "var(--hs-success)", + styles.Color: colorVar, styles.FontSize: fontSizeH3, styles.FontWeight: "700", styles.MarginBottom: spaceXS, @@ -117,6 +122,19 @@ func successBox(heading string, children ...elem.Node) *elem.Element { ) } +// successBox creates a green success feedback box with a checkmark icon. +// The heading is displayed as bold green text, and children are rendered below it. +// Pairs with warningBox for consistent feedback styling. +// +//nolint:unused // Used in auth_success.go template. +func successBox(heading string, children ...elem.Node) *elem.Element { + return feedbackBox( + checkboxIcon(), + "var(--hs-success)", "var(--hs-success-bg)", "status", "polite", + heading, children..., + ) +} + // checkboxIcon returns the success checkbox SVG icon as raw HTML. func checkboxIcon() elem.Node { return elem.Raw(`