package handler import ( "bytes" "encoding/json" "html/template" "net" "net/http" "path/filepath" "strconv" "strings" "time" "xboard-go/internal/service" "github.com/gin-gonic/gin" ) type userThemeViewData struct { Title string Description string Version string Theme string Logo string AssetsPath string CustomHTML template.HTML ThemeConfigJSON template.JS } type adminAppViewData struct { Title string SettingsJS template.JS AssetNonce string } func UserThemePage(c *gin.Context) { config := map[string]any{ "accent": service.MustGetString("nebula_theme_color", "aurora"), "slogan": service.MustGetString("nebula_hero_slogan", "One control center for login, subscriptions, sessions, and device visibility."), "backgroundUrl": service.MustGetString("nebula_background_url", ""), "metricsBaseUrl": service.MustGetString("nebula_metrics_base_url", ""), "defaultThemeMode": service.MustGetString("nebula_default_theme_mode", "system"), "lightLogoUrl": service.MustGetString("nebula_light_logo_url", ""), "darkLogoUrl": service.MustGetString("nebula_dark_logo_url", ""), "welcomeTarget": service.MustGetString("nebula_welcome_target", service.MustGetString("app_name", "XBoard")), "registerTitle": service.MustGetString("nebula_register_title", "Create your access."), "icpNo": service.MustGetString("icp_no", ""), "psbNo": service.MustGetString("psb_no", ""), "staticCdnUrl": service.MustGetString("nebula_static_cdn_url", ""), "isRegisterEnabled": !service.MustGetBool("stop_register", false), } payload := userThemeViewData{ Title: service.MustGetString("app_name", "XBoard"), Description: service.MustGetString("app_description", "Go rebuilt control panel"), Version: service.MustGetString("app_version", "2.0.0"), Theme: "Nebula", Logo: service.MustGetString("logo", ""), AssetsPath: "/theme/Nebula/assets", CustomHTML: template.HTML(service.MustGetString("nebula_custom_html", "")), ThemeConfigJSON: mustJSON(config), } renderPageTemplate(c, filepath.Join("frontend", "templates", "user_nebula.html"), payload) } func AdminAppPage(c *gin.Context) { securePath := service.GetAdminSecurePath() baseURL := service.GetAppURL() if origin := requestOrigin(c.Request); origin != "" { baseURL = origin } title := service.MustGetString("app_name", "XBoard") + " Admin" settings := map[string]string{ "base_url": baseURL, "title": title, "version": service.MustGetString("app_version", "1.0.0"), "logo": service.MustGetString("logo", ""), "secure_path": securePath, } settingsJSON, _ := json.Marshal(settings) payload := adminAppViewData{ Title: title, SettingsJS: template.JS(settingsJSON), AssetNonce: strconv.FormatInt(time.Now().UnixNano(), 10), } renderPageTemplate(c, filepath.Join("frontend", "templates", "admin_app.html"), payload) } func renderPageTemplate(c *gin.Context, templatePath string, data any) { tpl, err := template.ParseFiles(templatePath) if err != nil { c.String(http.StatusInternalServerError, "template parse error: %v", err) return } var buf bytes.Buffer if err := tpl.Execute(&buf, data); err != nil { c.String(http.StatusInternalServerError, "template render error: %v", err) return } c.Data(http.StatusOK, "text/html; charset=utf-8", buf.Bytes()) } func mustJSON(value any) template.JS { payload, err := json.Marshal(value) if err != nil { return template.JS("{}") } return template.JS(payload) } func requestOrigin(r *http.Request) string { if r == nil { return "" } scheme := r.Header.Get("X-Forwarded-Proto") if scheme == "" { if r.TLS != nil { scheme = "https" } else { scheme = "http" } } host := r.Header.Get("X-Forwarded-Host") if host == "" { host = r.Host } if host == "" { return "" } if forwardedPort := r.Header.Get("X-Forwarded-Port"); forwardedPort != "" && !strings.Contains(host, ":") { defaultPort := map[string]string{ "http": "80", "https": "443", }[scheme] if forwardedPort != "" && forwardedPort != defaultPort { host = net.JoinHostPort(host, forwardedPort) } } return scheme + "://" + host }