perf(panel): speed up file browsing and preserve loading forms
This commit is contained in:
27 files changed
+1387
-145
No files matched your search
@@ -99,6 +99,7 @@ type API struct {
|
||||
// InternalBaseURL is where that Job reaches felis-api's internal face to do so
|
||||
// (handleUploadFile). Uploads report 503 unless both are set.
|
||||
FileStage *fileedit.Stage
|
||||
FileBrowser *fileedit.Browser
|
||||
InternalBaseURL string
|
||||
|
||||
// Exporter starts the Job behind a world or backup download (exports.go),
|
||||
@@ -476,6 +477,8 @@ func (a *API) internalAPIRoutes() []apiRoute {
|
||||
// with the upload is the check (handlers_files.go).
|
||||
{Method: "GET", Pattern: "/api/v1/internal/file-uploads/{id}", Public: true, h: a.handleInternalFileUpload},
|
||||
{Method: "DELETE", Pattern: "/api/v1/internal/file-uploads/{id}", Public: true, h: a.handleInternalFileUploadLanded},
|
||||
// Read-only file Jobs pull commands with their own scoped bearer token.
|
||||
{Method: "POST", Pattern: "/api/v1/internal/file-browser/{id}", Public: true, h: a.handleInternalFileBrowser},
|
||||
|
||||
// An export Job's archive, held open until the owner's browser downloads
|
||||
// it. Public for the same reason as file uploads: the Job holds no service
|
||||
|
||||
@@ -30,7 +30,7 @@ import (
|
||||
// internal/fileedit, which explains why that transport needs no RBAC felis-api
|
||||
// does not already hold. Unlike Restorer and Backuper these calls are
|
||||
// SYNCHRONOUS: the caller wants the listing or the bytes, so the handler blocks on
|
||||
// the Job (seconds, dominated by Pod scheduling) rather than answering 202.
|
||||
// the result rather than answering 202.
|
||||
//
|
||||
// It is an interface so the handlers are unit-tested against a fake; the
|
||||
// production implementation is *fileedit.Editor. Using fileedit.Entry directly
|
||||
@@ -61,6 +61,14 @@ type FileEditor interface {
|
||||
Ops(ctx context.Context, server string) ([]fileedit.OpState, error)
|
||||
}
|
||||
|
||||
func (a *API) handleInternalFileBrowser(w http.ResponseWriter, r *http.Request) {
|
||||
if a.FileBrowser == nil {
|
||||
writeError(w, r, newError(http.StatusServiceUnavailable, "files_unavailable", "file browser is not configured"))
|
||||
return
|
||||
}
|
||||
a.FileBrowser.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
// writeFileRequest is the PUT /servers/{name}/file body. Content is []byte, so
|
||||
// encoding/json requires it to be base64 — which is what makes the write path
|
||||
// binary-safe: a config file with CRLF line endings, a UTF-8 BOM, or a stray
|
||||
|
||||
@@ -53,6 +53,24 @@ type fakeFileEditor struct {
|
||||
ops []fileedit.OpState
|
||||
}
|
||||
|
||||
func TestFileBrowserStaysOnInternalFaceWithScopedAuthentication(t *testing.T) {
|
||||
a := newTestAPI(newFakeRepo(), newFakeCluster())
|
||||
a.Internal = CallerTokens{CallerVelocity: "service-token"}
|
||||
a.External = staticExternal{p: &Principal{UserID: "owner", Role: "owner", ViaAdminAccess: true}}
|
||||
a.FileBrowser = &fileedit.Browser{}
|
||||
for _, h := range []http.Handler{a.InternalHandler(), a.ExternalHandler()} {
|
||||
w := do(h, "POST", fileedit.BrowserRoute+"unknown", `{}`, map[string]string{"Authorization": "Bearer service-token"})
|
||||
if w.Code != http.StatusNotFound {
|
||||
t.Fatalf("service token opened a browser: status %d (%s)", w.Code, w.Body.String())
|
||||
}
|
||||
}
|
||||
a.FileBrowser = nil
|
||||
w := do(a.InternalHandler(), "POST", fileedit.BrowserRoute+"unknown", `{}`, nil)
|
||||
if w.Code != http.StatusServiceUnavailable || decodeErr(t, w) != "files_unavailable" {
|
||||
t.Fatalf("unconfigured browser: status %d (%s)", w.Code, w.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func (f *fakeFileEditor) List(_ context.Context, server, path string) (fileedit.Listing, error) {
|
||||
f.calls++
|
||||
f.gotServer, f.gotPath = server, path
|
||||
|
||||
Reference in new issue
Block a user