From 10aaa986785bfaa4c52052dacd6e8c2622fb8cfa Mon Sep 17 00:00:00 2001 From: mojatter Date: Fri, 10 Apr 2026 17:56:15 +0900 Subject: [PATCH 1/2] refactor(console): Replace JS preview modal with server-rendered htmx fragment Move preview content generation from client-side JavaScript DOM construction to a server-side Go template (buckets/preview.html). The new GET /buckets/{name}/preview/{object...} endpoint returns an HTML fragment with the appropriate media element and metadata table, loaded via htmx.ajax(). This removes ~50 lines of JS (formatBytes, escapeHtml, file-type classification, fetch-based DOM building) while keeping client-only concerns (modal show/hide, view toggle, gallery lazy load) in JavaScript. --- .../handlers/console/buckets/objects/view.go | 72 +++++++++++++++ server/templates.go | 31 +++++++ server/templates/buckets/objects.html | 6 +- server/templates/buckets/preview.html | 43 +++++++++ server/templates/index.html | 90 ++----------------- 5 files changed, 155 insertions(+), 87 deletions(-) create mode 100644 server/templates/buckets/preview.html diff --git a/server/handlers/console/buckets/objects/view.go b/server/handlers/console/buckets/objects/view.go index a7659a5..c8b874e 100644 --- a/server/handlers/console/buckets/objects/view.go +++ b/server/handlers/console/buckets/objects/view.go @@ -1,6 +1,7 @@ package objects import ( + "bytes" "encoding/json" "fmt" "io" @@ -106,7 +107,78 @@ func handleMeta(s *server.Server, w http.ResponseWriter, r *http.Request) { _ = json.NewEncoder(w).Encode(resp) } +func handlePreview(s *server.Server, w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + bucketName := r.PathValue("name") + objectName := r.PathValue("object") + + strg, err := s.Buckets.Get(ctx, bucketName) + if err != nil { + http.Error(w, err.Error(), http.StatusNotFound) + return + } + + obj, err := strg.Get(ctx, objectName) + if err != nil { + http.Error(w, err.Error(), http.StatusNotFound) + return + } + + ext := strings.ToLower(path.Ext(objectName)) + viewURL := fmt.Sprintf("/buckets/%s/view/%s", bucketName, objectName) + previewType := server.PreviewType(ext) + + var textContent string + if previewType == "text" { + rc, err := obj.Open() + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + defer func() { _ = rc.Close() }() + + b, err := io.ReadAll(rc) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + textContent = string(b) + } + + data := struct { + Filename string + ViewURL string + ContentType string + Size uint64 + LastModified string + Metadata map[string]string + PreviewType string + TextContent string + }{ + Filename: path.Base(objectName), + ViewURL: viewURL, + ContentType: contentTypeByExt(ext), + Size: obj.Length(), + LastModified: obj.LastModified().Format("2006-01-02 15:04:05"), + PreviewType: previewType, + TextContent: textContent, + } + if md := obj.Metadata(); len(md) > 0 { + data.Metadata = md + } + + var buf bytes.Buffer + if err := s.Template.ExecuteTemplate(&buf, "buckets/preview.html", data); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "text/html; charset=utf-8") + _, _ = buf.WriteTo(w) +} + func init() { server.RegisterConsoleHandleFunc("GET /buckets/{name}/view/{object...}", middleware.BasicAuth(handleView)) server.RegisterConsoleHandleFunc("GET /buckets/{name}/meta/{object...}", middleware.BasicAuth(handleMeta)) + server.RegisterConsoleHandleFunc("GET /buckets/{name}/preview/{object...}", middleware.BasicAuth(handlePreview)) } diff --git a/server/templates.go b/server/templates.go index 946f8b9..c0f297f 100644 --- a/server/templates.go +++ b/server/templates.go @@ -22,6 +22,7 @@ var ( templateNames = []string{ "index.html", "buckets/objects.html", + "buckets/preview.html", } ) @@ -61,6 +62,36 @@ var imageExts = map[string]bool{ ".png": true, ".jpg": true, ".jpeg": true, ".gif": true, ".webp": true, ".svg": true, ".bmp": true, ".ico": true, } +// videoExts is the set of file extensions recognized as video for preview. +var videoExts = map[string]bool{ + ".mp4": true, ".webm": true, ".ogg": true, +} + +// audioExts is the set of file extensions recognized as audio for preview. +var audioExts = map[string]bool{ + ".mp3": true, ".wav": true, ".aac": true, ".flac": true, +} + +// PreviewType returns the preview category for the given file extension: +// "image", "video", "audio", "pdf", "text", or "" (unsupported). +func PreviewType(ext string) string { + ext = strings.ToLower(ext) + switch { + case imageExts[ext]: + return "image" + case videoExts[ext]: + return "video" + case audioExts[ext]: + return "audio" + case ext == ".pdf": + return "pdf" + case textPreviewExts[ext]: + return "text" + default: + return "" + } +} + // previewableExts is the set of file extensions that can be previewed in the Web Console. var previewableExts = map[string]bool{ // Images diff --git a/server/templates/buckets/objects.html b/server/templates/buckets/objects.html index 3af1ca3..25c778d 100644 --- a/server/templates/buckets/objects.html +++ b/server/templates/buckets/objects.html @@ -129,7 +129,7 @@

{{.BucketName}}

{{if isPreviewable .Name .Length}} - +
+ + + + + + diff --git a/server/templates/index.html b/server/templates/index.html index 7386741..c2df6ac 100644 --- a/server/templates/index.html +++ b/server/templates/index.html @@ -103,86 +103,21 @@

No Bucket Selected

+{{end}} + diff --git a/server/templates/empty.html b/server/templates/empty.html new file mode 100644 index 0000000..9753291 --- /dev/null +++ b/server/templates/empty.html @@ -0,0 +1,21 @@ +
+
+

Storage Overview

+

Select a bucket from the sidebar to view its objects.

+
+
+ +
+
+
+ + + + + +
+

No Bucket Selected

+

Navigate through your cloud storage buckets using the sidebar on the left.

+
+
diff --git a/server/templates/index.html b/server/templates/index.html index c2df6ac..cbbe138 100644 --- a/server/templates/index.html +++ b/server/templates/index.html @@ -38,62 +38,19 @@

Buckets

-
+
- + {{template "buckets/list.html" .}}
-
-
-

Storage Overview

-

Select a bucket from the sidebar to view its objects.

-
-
- -
-
-
- - - - - -
-

No Bucket Selected

-

Navigate through your cloud storage buckets using the sidebar on the left.

-
-
+ {{template "empty.html" .}}
@@ -105,88 +62,7 @@

No Bucket Selected

- +
\ No newline at end of file