File size: 7,417 Bytes
619f93d | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 | package static
import (
"encoding/json"
"errors"
"fmt"
"io"
"io/fs"
"net/http"
"os"
"strings"
"github.com/OpenListTeam/OpenList/v4/drivers/base"
"github.com/OpenListTeam/OpenList/v4/internal/conf"
"github.com/OpenListTeam/OpenList/v4/internal/setting"
"github.com/OpenListTeam/OpenList/v4/pkg/utils"
"github.com/OpenListTeam/OpenList/v4/public"
"github.com/gin-gonic/gin"
)
type ManifestIcon struct {
Src string `json:"src"`
Sizes string `json:"sizes"`
Type string `json:"type"`
}
type Manifest struct {
Display string `json:"display"`
Scope string `json:"scope"`
StartURL string `json:"start_url"`
Name string `json:"name"`
Icons []ManifestIcon `json:"icons"`
}
var static fs.FS
func initStatic() {
utils.Log.Debug("Initializing static file system...")
if conf.Conf.DistDir == "" {
dist, err := fs.Sub(public.Public, "dist")
if err != nil {
utils.Log.Fatalf("failed to read dist dir: %v", err)
}
static = dist
utils.Log.Debug("Using embedded dist directory")
return
}
static = os.DirFS(conf.Conf.DistDir)
utils.Log.Infof("Using custom dist directory: %s", conf.Conf.DistDir)
}
func replaceStrings(content string, replacements map[string]string) string {
for old, new := range replacements {
content = strings.Replace(content, old, new, 1)
}
return content
}
func initIndex(siteConfig SiteConfig) {
utils.Log.Debug("Initializing index.html...")
// dist_dir is empty and cdn is not empty, and web_version is empty or beta or dev or rolling
if conf.Conf.DistDir == "" && conf.Conf.Cdn != "" && (conf.WebVersion == "" || conf.WebVersion == "beta" || conf.WebVersion == "dev" || conf.WebVersion == "rolling") {
utils.Log.Infof("Fetching index.html from CDN: %s/index.html...", siteConfig.Cdn)
resp, err := base.RestyClient.R().
SetHeader("Accept", "text/html").
Get(fmt.Sprintf("%s/index.html", siteConfig.Cdn))
if err != nil {
utils.Log.Fatalf("failed to fetch index.html from CDN: %v", err)
}
if resp.StatusCode() != http.StatusOK {
utils.Log.Fatalf("failed to fetch index.html from CDN, status code: %d", resp.StatusCode())
}
conf.RawIndexHtml = string(resp.Body())
utils.Log.Info("Successfully fetched index.html from CDN")
} else {
utils.Log.Debug("Reading index.html from static files system...")
indexFile, err := static.Open("index.html")
if err != nil {
if errors.Is(err, fs.ErrNotExist) {
utils.Log.Fatalf("index.html not exist, you may forget to put dist of frontend to public/dist")
}
utils.Log.Fatalf("failed to read index.html: %v", err)
}
defer func() {
_ = indexFile.Close()
}()
index, err := io.ReadAll(indexFile)
if err != nil {
utils.Log.Fatalf("failed to read dist/index.html")
}
conf.RawIndexHtml = string(index)
utils.Log.Debug("Successfully read index.html from static files system")
}
utils.Log.Debug("Replacing placeholders in index.html...")
// Construct the correct manifest path based on basePath
manifestPath := "/manifest.json"
if siteConfig.BasePath != "/" {
manifestPath = siteConfig.BasePath + "/manifest.json"
}
replaceMap := map[string]string{
"cdn: undefined": fmt.Sprintf("cdn: '%s'", siteConfig.Cdn),
"base_path: undefined": fmt.Sprintf("base_path: '%s'", siteConfig.BasePath),
`href="/manifest.json"`: fmt.Sprintf(`href="%s"`, manifestPath),
}
conf.RawIndexHtml = replaceStrings(conf.RawIndexHtml, replaceMap)
UpdateIndex()
}
func UpdateIndex() {
utils.Log.Debug("Updating index.html with settings...")
favicon := setting.GetStr(conf.Favicon)
logo := strings.Split(setting.GetStr(conf.Logo), "\n")[0]
title := setting.GetStr(conf.SiteTitle)
customizeHead := setting.GetStr(conf.CustomizeHead)
customizeBody := setting.GetStr(conf.CustomizeBody)
mainColor := setting.GetStr(conf.MainColor)
utils.Log.Debug("Applying replacements for default pages...")
replaceMap1 := map[string]string{
"https://res.oplist.org/logo/logo.svg": favicon,
"https://res.oplist.org/logo/logo.png": logo,
"Loading...": title,
"main_color: undefined": fmt.Sprintf("main_color: '%s'", mainColor),
}
conf.ManageHtml = replaceStrings(conf.RawIndexHtml, replaceMap1)
utils.Log.Debug("Applying replacements for manage pages...")
replaceMap2 := map[string]string{
"<!-- customize head -->": customizeHead,
"<!-- customize body -->": customizeBody,
}
conf.IndexHtml = replaceStrings(conf.ManageHtml, replaceMap2)
utils.Log.Debug("Index.html update completed")
}
func ManifestJSON(c *gin.Context) {
// Get site configuration to ensure consistent base path handling
siteConfig := getSiteConfig()
// Get site title from settings
siteTitle := setting.GetStr(conf.SiteTitle)
// Get logo from settings, use the first line (light theme logo)
logoSetting := setting.GetStr(conf.Logo)
logoUrl := strings.Split(logoSetting, "\n")[0]
// Use base path from site config for consistency
basePath := siteConfig.BasePath
// Determine scope and start_url
// PWA scope and start_url should always point to our application's base path
// regardless of whether static resources come from CDN or local server
scope := basePath
startURL := basePath
manifest := Manifest{
Display: "standalone",
Scope: scope,
StartURL: startURL,
Name: siteTitle,
Icons: []ManifestIcon{
{
Src: logoUrl,
Sizes: "512x512",
Type: "image/png",
},
},
}
c.Header("Content-Type", "application/json")
c.Header("Cache-Control", "public, max-age=3600") // cache for 1 hour
if err := json.NewEncoder(c.Writer).Encode(manifest); err != nil {
utils.Log.Errorf("Failed to encode manifest.json: %v", err)
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to generate manifest"})
return
}
}
func Static(r *gin.RouterGroup, noRoute func(handlers ...gin.HandlerFunc)) {
utils.Log.Debug("Setting up static routes...")
siteConfig := getSiteConfig()
initStatic()
initIndex(siteConfig)
folders := []string{"assets", "images", "streamer", "static"}
if conf.Conf.Cdn == "" {
utils.Log.Debug("Setting up static file serving...")
r.Use(func(c *gin.Context) {
for _, folder := range folders {
if strings.HasPrefix(c.Request.RequestURI, fmt.Sprintf("/%s/", folder)) {
c.Header("Cache-Control", "public, max-age=15552000")
}
}
})
for _, folder := range folders {
sub, err := fs.Sub(static, folder)
if err != nil {
utils.Log.Fatalf("can't find folder: %s", folder)
}
utils.Log.Debugf("Setting up route for folder: %s", folder)
r.StaticFS(fmt.Sprintf("/%s/", folder), http.FS(sub))
}
} else {
// Ensure static file redirected to CDN
for _, folder := range folders {
r.GET(fmt.Sprintf("/%s/*filepath", folder), func(c *gin.Context) {
filepath := c.Param("filepath")
c.Redirect(http.StatusFound, fmt.Sprintf("%s/%s%s", siteConfig.Cdn, folder, filepath))
})
}
}
utils.Log.Debug("Setting up catch-all route...")
noRoute(func(c *gin.Context) {
if c.Request.Method != "GET" && c.Request.Method != "POST" {
c.Status(405)
return
}
c.Header("Content-Type", "text/html")
c.Status(200)
if strings.HasPrefix(c.Request.URL.Path, "/@manage") {
_, _ = c.Writer.WriteString(conf.ManageHtml)
} else {
_, _ = c.Writer.WriteString(conf.IndexHtml)
}
c.Writer.Flush()
c.Writer.WriteHeaderNow()
})
}
|