Fix UI prefix url encoding (#9344)
* Fix filer UI navigation for URL-sensitive object prefixes * Fix filer UI navigation for URL-sensitive object prefixes * Clarify filer UI path escaping test name Rename the legacy filer UI path test to describe the actual behavior being checked. The printpath helper preserves timestamp characters that are valid in URL path components, while the PR fix is focused on query-string escaping for path and cursor parameters.
This commit is contained in:
parent
487b93eb49
commit
a1e5eb9dad
@ -59,13 +59,13 @@ templ FileBrowser(data dash.FileBrowserData) {
|
||||
for i, crumb := range data.Breadcrumbs {
|
||||
if i == len(data.Breadcrumbs)-1 {
|
||||
<li class="breadcrumb-item active" aria-current="page">
|
||||
<a href={ dash.PUrl(ctx, fmt.Sprintf("/files?path=%s", crumb.Path)) } class="text-decoration-none">
|
||||
<a href={ dash.PUrl(ctx, fileBrowserPathURL(crumb.Path)) } class="text-decoration-none">
|
||||
{ crumb.Name }
|
||||
</a>
|
||||
</li>
|
||||
} else {
|
||||
<li class="breadcrumb-item">
|
||||
<a href={ dash.PUrl(ctx, fmt.Sprintf("/files?path=%s", crumb.Path)) } class="text-decoration-none">
|
||||
<a href={ dash.PUrl(ctx, fileBrowserPathURL(crumb.Path)) } class="text-decoration-none">
|
||||
if crumb.Name == "Root" {
|
||||
<i class="fas fa-home me-1"></i>
|
||||
}
|
||||
@ -83,14 +83,14 @@ templ FileBrowser(data dash.FileBrowserData) {
|
||||
<h6 class="m-0 font-weight-bold text-primary">
|
||||
<i class="fas fa-folder-open me-2"></i>
|
||||
if data.CurrentPath == "/" {
|
||||
<a href={ dash.PUrl(ctx, "/files?path=/") } class="text-decoration-none text-primary">Root Directory</a>
|
||||
<a href={ dash.PUrl(ctx, fileBrowserPathURL("/")) } class="text-decoration-none text-primary">Root Directory</a>
|
||||
} else if data.CurrentPath == "/buckets" {
|
||||
<a href={ dash.PUrl(ctx, "/files?path=/buckets") } class="text-decoration-none text-primary">Object Store Buckets Directory</a>
|
||||
<a href={ dash.PUrl(ctx, fileBrowserPathURL("/buckets")) } class="text-decoration-none text-primary">Object Store Buckets Directory</a>
|
||||
<a href={ dash.PUrl(ctx, "/object-store/buckets") } class="btn btn-sm btn-outline-primary ms-2">
|
||||
<i class="fas fa-cube me-1"></i>Manage Buckets
|
||||
</a>
|
||||
} else {
|
||||
<a href={ dash.PUrl(ctx, fmt.Sprintf("/files?path=%s", data.CurrentPath)) } class="text-decoration-none text-primary">{ filepath.Base(data.CurrentPath) }</a>
|
||||
<a href={ dash.PUrl(ctx, fileBrowserPathURL(data.CurrentPath)) } class="text-decoration-none text-primary">{ filepath.Base(data.CurrentPath) }</a>
|
||||
}
|
||||
</h6>
|
||||
|
||||
@ -107,7 +107,7 @@ templ FileBrowser(data dash.FileBrowserData) {
|
||||
</div>
|
||||
<div class="btn-group btn-group-sm" role="group">
|
||||
if data.HasNextPage {
|
||||
<a href={ dash.PUrl(ctx, fmt.Sprintf("/files?path=%s&lastFileName=%s&limit=%d", data.CurrentPath, data.LastFileName, data.PageSize)) } class="btn btn-outline-primary" title="Next page">
|
||||
<a href={ dash.PUrl(ctx, fileBrowserPageURL(data.CurrentPath, data.LastFileName, data.PageSize)) } class="btn btn-outline-primary" title="Next page">
|
||||
Next <i class="fas fa-angle-right"></i>
|
||||
</a>
|
||||
} else {
|
||||
@ -116,7 +116,7 @@ templ FileBrowser(data dash.FileBrowserData) {
|
||||
</button>
|
||||
}
|
||||
if data.ParentPath != data.CurrentPath {
|
||||
<a href={ dash.PUrl(ctx, fmt.Sprintf("/files?path=%s", data.ParentPath)) } class="btn btn-outline-secondary" title="Go up one directory">
|
||||
<a href={ dash.PUrl(ctx, fileBrowserPathURL(data.ParentPath)) } class="btn btn-outline-secondary" title="Go up one directory">
|
||||
<i class="fas fa-arrow-up"></i> Up
|
||||
</a>
|
||||
}
|
||||
@ -150,7 +150,7 @@ templ FileBrowser(data dash.FileBrowserData) {
|
||||
<div class="d-flex align-items-center">
|
||||
if entry.IsDirectory {
|
||||
<i class="fas fa-folder text-warning me-2"></i>
|
||||
<a href={ dash.PUrl(ctx, fmt.Sprintf("/files?path=%s", entry.FullPath)) } class="text-decoration-none">
|
||||
<a href={ dash.PUrl(ctx, fileBrowserPathURL(entry.FullPath)) } class="text-decoration-none">
|
||||
{ entry.Name }
|
||||
</a>
|
||||
} else {
|
||||
@ -238,7 +238,7 @@ templ FileBrowser(data dash.FileBrowserData) {
|
||||
<div class="btn-group btn-group-sm" role="group">
|
||||
|
||||
if data.HasNextPage {
|
||||
<a href={ dash.PUrl(ctx, fmt.Sprintf("/files?path=%s&lastFileName=%s&limit=%d", data.CurrentPath, data.LastFileName, data.PageSize)) } class="btn btn-outline-primary">
|
||||
<a href={ dash.PUrl(ctx, fileBrowserPageURL(data.CurrentPath, data.LastFileName, data.PageSize)) } class="btn btn-outline-primary">
|
||||
Next <i class="fas fa-angle-right ms-1"></i>
|
||||
</a>
|
||||
} else {
|
||||
|
||||
@ -140,7 +140,7 @@ func FileBrowser(data dash.FileBrowserData) templ.Component {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var6 templ.SafeURL
|
||||
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinURLErrs(dash.PUrl(ctx, fmt.Sprintf("/files?path=%s", crumb.Path)))
|
||||
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinURLErrs(dash.PUrl(ctx, fileBrowserPathURL(crumb.Path)))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/file_browser.templ`, Line: 62, Col: 73}
|
||||
}
|
||||
@ -171,7 +171,7 @@ func FileBrowser(data dash.FileBrowserData) templ.Component {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var8 templ.SafeURL
|
||||
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinURLErrs(dash.PUrl(ctx, fmt.Sprintf("/files?path=%s", crumb.Path)))
|
||||
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinURLErrs(dash.PUrl(ctx, fileBrowserPathURL(crumb.Path)))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/file_browser.templ`, Line: 68, Col: 73}
|
||||
}
|
||||
@ -214,7 +214,7 @@ func FileBrowser(data dash.FileBrowserData) templ.Component {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var10 templ.SafeURL
|
||||
templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinURLErrs(dash.PUrl(ctx, "/files?path=/"))
|
||||
templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinURLErrs(dash.PUrl(ctx, fileBrowserPathURL("/")))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/file_browser.templ`, Line: 86, Col: 45}
|
||||
}
|
||||
@ -232,7 +232,7 @@ func FileBrowser(data dash.FileBrowserData) templ.Component {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var11 templ.SafeURL
|
||||
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinURLErrs(dash.PUrl(ctx, "/files?path=/buckets"))
|
||||
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinURLErrs(dash.PUrl(ctx, fileBrowserPathURL("/buckets")))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/file_browser.templ`, Line: 88, Col: 58}
|
||||
}
|
||||
@ -263,7 +263,7 @@ func FileBrowser(data dash.FileBrowserData) templ.Component {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var13 templ.SafeURL
|
||||
templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinURLErrs(dash.PUrl(ctx, fmt.Sprintf("/files?path=%s", data.CurrentPath)))
|
||||
templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinURLErrs(dash.PUrl(ctx, fileBrowserPathURL(data.CurrentPath)))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/file_browser.templ`, Line: 93, Col: 77}
|
||||
}
|
||||
@ -356,7 +356,7 @@ func FileBrowser(data dash.FileBrowserData) templ.Component {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var16 templ.SafeURL
|
||||
templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinURLErrs(dash.PUrl(ctx, fmt.Sprintf("/files?path=%s&lastFileName=%s&limit=%d", data.CurrentPath, data.LastFileName, data.PageSize)))
|
||||
templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinURLErrs(dash.PUrl(ctx, fileBrowserPageURL(data.CurrentPath, data.LastFileName, data.PageSize)))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/file_browser.templ`, Line: 110, Col: 138}
|
||||
}
|
||||
@ -380,7 +380,7 @@ func FileBrowser(data dash.FileBrowserData) templ.Component {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var17 templ.SafeURL
|
||||
templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinURLErrs(dash.PUrl(ctx, fmt.Sprintf("/files?path=%s", data.ParentPath)))
|
||||
templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinURLErrs(dash.PUrl(ctx, fileBrowserPathURL(data.ParentPath)))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/file_browser.templ`, Line: 119, Col: 78}
|
||||
}
|
||||
@ -426,7 +426,7 @@ func FileBrowser(data dash.FileBrowserData) templ.Component {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var19 templ.SafeURL
|
||||
templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinURLErrs(dash.PUrl(ctx, fmt.Sprintf("/files?path=%s", entry.FullPath)))
|
||||
templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinURLErrs(dash.PUrl(ctx, fileBrowserPathURL(entry.FullPath)))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/file_browser.templ`, Line: 153, Col: 83}
|
||||
}
|
||||
@ -731,7 +731,7 @@ func FileBrowser(data dash.FileBrowserData) templ.Component {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var35 templ.SafeURL
|
||||
templ_7745c5c3_Var35, templ_7745c5c3_Err = templ.JoinURLErrs(dash.PUrl(ctx, fmt.Sprintf("/files?path=%s&lastFileName=%s&limit=%d", data.CurrentPath, data.LastFileName, data.PageSize)))
|
||||
templ_7745c5c3_Var35, templ_7745c5c3_Err = templ.JoinURLErrs(dash.PUrl(ctx, fileBrowserPageURL(data.CurrentPath, data.LastFileName, data.PageSize)))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/file_browser.templ`, Line: 241, Col: 138}
|
||||
}
|
||||
|
||||
18
weed/admin/view/app/file_browser_urls.go
Normal file
18
weed/admin/view/app/file_browser_urls.go
Normal file
@ -0,0 +1,18 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
func fileBrowserPathURL(path string) string {
|
||||
return "/files?path=" + url.QueryEscape(path)
|
||||
}
|
||||
|
||||
func fileBrowserPageURL(path, lastFileName string, limit int) string {
|
||||
return fmt.Sprintf("%s&lastFileName=%s&limit=%d",
|
||||
fileBrowserPathURL(path),
|
||||
url.QueryEscape(lastFileName),
|
||||
limit,
|
||||
)
|
||||
}
|
||||
21
weed/admin/view/app/file_browser_urls_test.go
Normal file
21
weed/admin/view/app/file_browser_urls_test.go
Normal file
@ -0,0 +1,21 @@
|
||||
package app
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestFileBrowserPathURLEscapesQueryPath(t *testing.T) {
|
||||
path := "/buckets/airflow-logs/logs/run_id=backfill__2025-01-31T15:00:00+00:00"
|
||||
want := "/files?path=%2Fbuckets%2Fairflow-logs%2Flogs%2Frun_id%3Dbackfill__2025-01-31T15%3A00%3A00%2B00%3A00"
|
||||
|
||||
if got := fileBrowserPathURL(path); got != want {
|
||||
t.Fatalf("fileBrowserPathURL() = %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFileBrowserPageURLEscapesCursor(t *testing.T) {
|
||||
got := fileBrowserPageURL("/buckets/airflow-logs/logs/run_id=a+b", "task:1+next", 50)
|
||||
want := "/files?path=%2Fbuckets%2Fairflow-logs%2Flogs%2Frun_id%3Da%2Bb&lastFileName=task%3A1%2Bnext&limit=50"
|
||||
|
||||
if got != want {
|
||||
t.Fatalf("fileBrowserPageURL() = %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
@ -84,3 +84,21 @@ func TestToBreadcrumb(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrintPathPreservesPathSafeTimestampChars(t *testing.T) {
|
||||
got := printpath("/logs/run_id=backfill__2025-01-31T15:00:00+00:00/")
|
||||
want := "/logs/run_id=backfill__2025-01-31T15:00:00+00:00/"
|
||||
|
||||
if got != want {
|
||||
t.Fatalf("printpath() = %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestQueryEscapePreservesPlusInLoadMoreCursor(t *testing.T) {
|
||||
got := funcMap["queryEscape"].(func(string) string)("task:1+next")
|
||||
want := "task%3A1%2Bnext"
|
||||
|
||||
if got != want {
|
||||
t.Fatalf("queryEscape() = %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
@ -170,7 +170,7 @@
|
||||
|
||||
{{ if .ShouldDisplayLoadMore }}
|
||||
<div class="row">
|
||||
<a href={{ print .Path "?limit=" .Limit "&lastFileName=" .LastFileName }} >
|
||||
<a href="{{ printpath .Path }}?limit={{ .Limit }}&lastFileName={{ queryEscape .LastFileName }}" >
|
||||
Load more
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@ -18,6 +18,7 @@ func printpath(parts ...string) string {
|
||||
var funcMap = template.FuncMap{
|
||||
"humanizeBytes": humanize.Bytes,
|
||||
"printpath": printpath,
|
||||
"queryEscape": url.QueryEscape,
|
||||
}
|
||||
|
||||
//go:embed filer.html
|
||||
|
||||
Loading…
Reference in New Issue
Block a user