168 lines
4.8 KiB
Go
168 lines
4.8 KiB
Go
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) {
|
|
baseURL := service.GetAppURL()
|
|
if origin := requestOrigin(c.Request); origin != "" {
|
|
baseURL = origin
|
|
}
|
|
|
|
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),
|
|
"baseUrl": baseURL,
|
|
}
|
|
|
|
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 ""
|
|
}
|
|
|
|
// 1. Try X-Forwarded-Proto (High priority for proxies)
|
|
scheme := r.Header.Get("X-Forwarded-Proto")
|
|
if scheme == "" {
|
|
// Fallback for some proxies
|
|
if r.Header.Get("X-Forwarded-Ssl") == "on" || r.Header.Get("X-Forwarded-Scheme") == "https" {
|
|
scheme = "https"
|
|
} else if r.TLS != nil {
|
|
scheme = "https"
|
|
} else {
|
|
scheme = "http"
|
|
}
|
|
}
|
|
|
|
// 2. Try X-Forwarded-Host or Host
|
|
host := r.Header.Get("X-Forwarded-Host")
|
|
if host == "" {
|
|
host = r.Host
|
|
}
|
|
|
|
// 3. Fallback to Origin header if still empty (rare but possible)
|
|
if host == "" {
|
|
if origin := r.Header.Get("Origin"); origin != "" {
|
|
return origin
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// 4. Handle Port if present in Forwarded headers
|
|
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
|
|
}
|