mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-11-07 12:37:41 +01:00
filer: hide write controls in the UI on the read-only port (#12429)
* filer: hide write controls in the UI on the read-only port The read-only port accepts only GET and HEAD, but its directory page offered New Folder, Upload, drag-and-drop, rename and delete, each of which failed with 405. Requests that come through the read-only handler now render the page without those controls or the upload script. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * filer: test the directory page controls on each port Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * filer: match the rendered row buttons in the port test Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
e44c16802d
commit
878979af70
4 files changed
+84
-1
No files matched your search
@@ -194,7 +194,7 @@ func (fs *FilerServer) readonlyFilerHandler(w http.ResponseWriter, r *http.Reque
|
|||||||
|
|
||||||
switch r.Method {
|
switch r.Method {
|
||||||
case http.MethodGet, http.MethodHead:
|
case http.MethodGet, http.MethodHead:
|
||||||
fs.GetOrHeadHandler(w, r)
|
fs.GetOrHeadHandler(w, r.WithContext(context.WithValue(r.Context(), readOnlyListingKey{}, true)))
|
||||||
default:
|
default:
|
||||||
requestMethod = "INVALID"
|
requestMethod = "INVALID"
|
||||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||||
|
|||||||
@@ -13,6 +13,10 @@ import (
|
|||||||
"github.com/seaweedfs/seaweedfs/weed/util/version"
|
"github.com/seaweedfs/seaweedfs/weed/util/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// readOnlyListingKey marks a request served on the read-only port, whose
|
||||||
|
// listing page must not offer write operations.
|
||||||
|
type readOnlyListingKey struct{}
|
||||||
|
|
||||||
// listDirectoryHandler lists directories and folders under a directory
|
// listDirectoryHandler lists directories and folders under a directory
|
||||||
// files are sorted by name and paginated via "lastFileName" and "limit".
|
// files are sorted by name and paginated via "lastFileName" and "limit".
|
||||||
// sub directories are listed on the first page, when "lastFileName"
|
// sub directories are listed on the first page, when "lastFileName"
|
||||||
@@ -91,6 +95,7 @@ func (fs *FilerServer) listDirectoryHandler(w http.ResponseWriter, r *http.Reque
|
|||||||
ShouldDisplayLoadMore bool
|
ShouldDisplayLoadMore bool
|
||||||
EmptyFolder bool
|
EmptyFolder bool
|
||||||
ShowDirectoryDelete bool
|
ShowDirectoryDelete bool
|
||||||
|
ReadOnly bool
|
||||||
}{
|
}{
|
||||||
version.Version(),
|
version.Version(),
|
||||||
path,
|
path,
|
||||||
@@ -101,6 +106,7 @@ func (fs *FilerServer) listDirectoryHandler(w http.ResponseWriter, r *http.Reque
|
|||||||
shouldDisplayLoadMore,
|
shouldDisplayLoadMore,
|
||||||
emptyFolder,
|
emptyFolder,
|
||||||
fs.option.ShowUIDirectoryDelete,
|
fs.option.ShowUIDirectoryDelete,
|
||||||
|
ctx.Value(readOnlyListingKey{}) != nil,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.V(0).InfofCtx(ctx, "Template Execute Error: %v", err)
|
glog.V(0).InfofCtx(ctx, "Template Execute Error: %v", err)
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
package weed_server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/seaweedfs/seaweedfs/weed/filer"
|
||||||
|
"github.com/seaweedfs/seaweedfs/weed/security"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestListDirectoryWriteControlsFollowPort(t *testing.T) {
|
||||||
|
mtime := time.Unix(1700000000, 0)
|
||||||
|
store := newRenameTestStore()
|
||||||
|
store.entries["/docs"] = &filer.Entry{FullPath: "/docs", Attr: filer.Attr{Mtime: mtime, Mode: os.ModeDir | 0755}}
|
||||||
|
store.entries["/docs/sub"] = &filer.Entry{FullPath: "/docs/sub", Attr: filer.Attr{Mtime: mtime, Mode: os.ModeDir | 0755}}
|
||||||
|
store.entries["/docs/a.txt"] = &filer.Entry{FullPath: "/docs/a.txt", Attr: filer.Attr{Mtime: mtime, Mode: 0644}}
|
||||||
|
store.entries["/empty"] = &filer.Entry{FullPath: "/empty", Attr: filer.Attr{Mtime: mtime, Mode: os.ModeDir | 0755}}
|
||||||
|
fs := &FilerServer{
|
||||||
|
filer: newRenameTestFiler(t, store),
|
||||||
|
option: &FilerOption{ExposeDirectoryData: true, DirListingLimit: 100, ShowUIDirectoryDelete: true},
|
||||||
|
filerGuard: security.NewGuard(nil, "", 0, "", 0),
|
||||||
|
}
|
||||||
|
|
||||||
|
writeControls := []string{
|
||||||
|
`onclick="handleCreateDir()"`,
|
||||||
|
`for="fileElem"`,
|
||||||
|
`for="dirElem"`,
|
||||||
|
`onclick="handleRename(`,
|
||||||
|
`onclick="handleDelete(`,
|
||||||
|
`class="row add-files"`,
|
||||||
|
"function uploadFile(",
|
||||||
|
}
|
||||||
|
|
||||||
|
render := func(t *testing.T, handler http.HandlerFunc, path string) string {
|
||||||
|
t.Helper()
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
handler(rec, httptest.NewRequest(http.MethodGet, path, nil))
|
||||||
|
if rec.Code != http.StatusOK {
|
||||||
|
t.Fatalf("GET %s: status %d, body %s", path, rec.Code, rec.Body.String())
|
||||||
|
}
|
||||||
|
return rec.Body.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, path := range []string{"/docs/", "/empty/"} {
|
||||||
|
readOnly := render(t, fs.readonlyFilerHandler, path)
|
||||||
|
if !strings.Contains(readOnly, "<ol class=\"breadcrumb\">") {
|
||||||
|
t.Fatalf("read-only listing of %s is not the directory page:\n%s", path, readOnly)
|
||||||
|
}
|
||||||
|
for _, control := range writeControls {
|
||||||
|
if strings.Contains(readOnly, control) {
|
||||||
|
t.Errorf("read-only listing of %s offers %q", path, control)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if readOnly := render(t, fs.readonlyFilerHandler, "/docs/"); !strings.Contains(readOnly, "a.txt") || !strings.Contains(readOnly, "sub") {
|
||||||
|
t.Errorf("read-only listing lost its entries:\n%s", readOnly)
|
||||||
|
}
|
||||||
|
|
||||||
|
writable := render(t, fs.GetOrHeadHandler, "/docs/") + render(t, fs.GetOrHeadHandler, "/empty/")
|
||||||
|
for _, control := range writeControls {
|
||||||
|
if !strings.Contains(writable, control) {
|
||||||
|
t.Errorf("listing on the main port is missing %q", control)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -87,6 +87,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div>
|
<div>
|
||||||
|
{{ if not .ReadOnly }}
|
||||||
<div class="btn-group btn-group-sm pull-right" role="group" style="margin-top:3px;">
|
<div class="btn-group btn-group-sm pull-right" role="group" style="margin-top:3px;">
|
||||||
<label class="btn btn-default" onclick="handleCreateDir()">
|
<label class="btn btn-default" onclick="handleCreateDir()">
|
||||||
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span> New Folder
|
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span> New Folder
|
||||||
@@ -98,6 +99,7 @@
|
|||||||
<span class="glyphicon glyphicon-folder-close" aria-hidden="true"></span> Upload Folder
|
<span class="glyphicon glyphicon-folder-close" aria-hidden="true"></span> Upload Folder
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
{{ end }}
|
||||||
<ol class="breadcrumb">
|
<ol class="breadcrumb">
|
||||||
{{ range $entry := .Breadcrumbs }}
|
{{ range $entry := .Breadcrumbs }}
|
||||||
<li><a href="{{ printpath $entry.Link }}">
|
<li><a href="{{ printpath $entry.Link }}">
|
||||||
@@ -114,9 +116,11 @@
|
|||||||
<input type="file" id="dirElem" webkitdirectory multiple onchange="handleFiles(this.files)">
|
<input type="file" id="dirElem" webkitdirectory multiple onchange="handleFiles(this.files)">
|
||||||
|
|
||||||
{{ if .EmptyFolder }}
|
{{ if .EmptyFolder }}
|
||||||
|
{{ if not .ReadOnly }}
|
||||||
<div class="row add-files">
|
<div class="row add-files">
|
||||||
+
|
+
|
||||||
</div>
|
</div>
|
||||||
|
{{ end }}
|
||||||
{{ else }}
|
{{ else }}
|
||||||
<table width="100%" class="table table-hover">
|
<table width="100%" class="table table-hover">
|
||||||
{{ $path := .Path }}
|
{{ $path := .Path }}
|
||||||
@@ -148,6 +152,7 @@
|
|||||||
<td align="right" nowrap>
|
<td align="right" nowrap>
|
||||||
{{ $entry.Timestamp.Format "2006-01-02 15:04" }}
|
{{ $entry.Timestamp.Format "2006-01-02 15:04" }}
|
||||||
</td>
|
</td>
|
||||||
|
{{ if not $.ReadOnly }}
|
||||||
<td style="width:75px">
|
<td style="width:75px">
|
||||||
<div class="btn-group btn-group-xs pull-right operations" role="group">
|
<div class="btn-group btn-group-xs pull-right operations" role="group">
|
||||||
<label class="btn" onclick="handleRename('{{ $entry.Name }}', '{{ printpath $path "/" }}')">
|
<label class="btn" onclick="handleRename('{{ $entry.Name }}', '{{ printpath $path "/" }}')">
|
||||||
@@ -165,6 +170,7 @@
|
|||||||
{{ end }}
|
{{ end }}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
{{ end }}
|
||||||
</tr>
|
</tr>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</table>
|
</table>
|
||||||
@@ -186,6 +192,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
{{ if not .ReadOnly }}
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
// ************************ Drag and drop ***************** //
|
// ************************ Drag and drop ***************** //
|
||||||
let dropArea = document.getElementById("drop-area");
|
let dropArea = document.getElementById("drop-area");
|
||||||
@@ -442,4 +449,5 @@
|
|||||||
reloadPage();
|
reloadPage();
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
{{ end }}
|
||||||
</html>
|
</html>
|
||||||
Reference in new issue
Block a user