Files
sencho/docs/features/stack-file-explorer.mdx
T
Anso 801a098a5b feat(files): per-stack file explorer (#780)
* feat(files): backend foundation for stack file explorer

Install multer for multipart file upload handling. Add
isValidRelativeStackPath to validation.ts to guard client-supplied
relative paths against traversal, absolute paths, NUL bytes, backslash
injection, and double-slash segments. Add isBinaryBuffer to a new
binaryDetect.ts utility for heuristic text/binary detection via
NUL-byte fast exit and non-printable byte ratio sampling.

* fix(files): reject bare dot segments in isValidRelativeStackPath

* feat(files): add safe stack-scoped file I/O methods to FileSystemService

Adds FileEntry interface and seven new public methods to FileSystemService
for stack-scoped file operations: listStackDirectory, readStackFile,
streamStackFile, writeStackFile, deleteStackPath, mkdirStackPath, and
statStackEntry.

Each method routes through a private resolveSafeStackPath helper that
enforces two-phase path containment: a pre-realpath lexical check plus a
post-realpath symlink-escape check. ENOENT targets are handled by walking
up to the deepest existing ancestor, realpaths that ancestor, and
reattaching the remaining suffix.

Binary detection delegates to isBinaryBuffer; path safety delegates to
isPathWithinBase. Protected file names and the MIME map are module-level
constants to avoid repeated allocation.

* feat(files): frontend API wrappers and Monaco language helper

* fix(files): tighten stackFilesApi error handling and localOnly support

* fix(files): FileSystemService safety and correctness fixes

* feat(files): add file explorer API endpoints to stacks router

* feat(files): FileTree and FileTreeNode components

* fix(files): route security hardening and stream cleanup

* fix(files): FileTree accessibility, icon stroke, stale fetch guard

Add strokeWidth={1.5} to all Lucide icons in FileTreeNode to match the
design system. Add aria-expanded to directory rows for accessibility.
Guard handleDirClick .then() callbacks against stale stack name
references when the component re-renders with a new stack. Add
toast.info fallbacks when compose.yaml or .env is clicked without a
navigation callback registered.

* feat(files): FileViewer, FileUploadDropzone, NewFolderDialog, DeleteFileConfirm

* fix(files): resolve code quality findings in file explorer components

- Move editorOptions useMemo above conditional returns in FileViewer (Rules of Hooks fix)
- Fix blob download: append anchor to DOM before click, defer URL revoke 100ms
- Keep protected-file confirm input visible during NOT_EMPTY recursive retry in DeleteFileConfirm
- Remove non-functional cursor-pointer/onClick from Community upgrade pill in FileUploadDropzone
- Add success toast on folder creation in NewFolderDialog
- Switch all (e as Error).message casts to instanceof Error narrowing

* test(files): unit tests for binary detection, stack path safety, and file explorer routes

- binary-detection.test.ts: covers isBinaryBuffer edge cases (empty, NUL,
  PNG header, threshold boundary, sampleBytes parameter)
- filesystem-stack-paths.test.ts: covers isValidRelativeStackPath (accepts/
  rejects matrix) and FileSystemService stack methods against a real temp dir
  (listStackDirectory sort and protection flags, readStackFile text/binary/
  oversized paths, writeStackFile/Buffer, deleteStackPath, mkdirStackPath,
  traversal guard); platform-specific empty-dir/NOT_EMPTY cases skip on Windows
- stack-files-routes.test.ts: route-level integration tests for all seven
  file explorer endpoints; covers auth gating, Community-tier 403 gates,
  input validation, 413 TOO_LARGE upload limit, and 204/200 happy paths

* feat(files): StackFileExplorer container with lazy tree, viewer, and action bar

* fix(files): add Download button to explorer toolbar, fix Community upgrade pill, reset state on stack change

* test(files): add missing test coverage for file explorer routes and service

* feat(files): add Files tab to EditorLayout with StackFileExplorer integration

* fix(files): add defensive activeTab guard to saveFile and discardChanges

* test(files): unit tests for FileTree expand/collapse and FileViewer render modes

Covers the three FileViewer content modes (text/Monaco, binary panel,
oversized panel) and the FileTree expand/collapse/cache cycle: first
expand fetches the subdirectory, second click collapses without a fetch,
third click re-expands from the in-memory cache without a second fetch.

* test(e2e): file explorer community and skipper+ flows

Covers the full file-explorer feature surface in two describe blocks:

Community (read-only): intercepts /api/license to simulate community
tier, confirms the upgrade pill is visible in the left pane, and
asserts that the Save button is absent after opening a text file.

Skipper+ (full CRUD): uploads a text file and confirms it appears in
the tree; edits config/app.conf and saves via Monaco; deletes an
uploaded file and asserts the tree entry is gone; issues a raw HTTP
request to the download endpoint and checks for status 200 and the
content-disposition: attachment header.

Also adds data-testid="file-action-delete" to the action bar Delete
button in StackFileExplorer for stable targeting, and exports
waitForStacksLoaded from e2e/helpers.ts to eliminate the three
identical local copies in stacks, deploy-log-panel, and stack-files
spec files.

* fix(e2e): improve test isolation and selector stability in stack-files spec

Move beforeEach seed to beforeAll/afterAll so fixtures are created once per
suite, not before every test. Extract shared seedSuite/teardownSuite helpers
to eliminate the duplicate beforeAll/afterAll blocks. Wrap teardown in
try/catch so failures log a warning rather than masking test results.

Replace waitForTimeout(500) with a deterministic expect on the file tree
sentinel. Add data-testid="anatomy-files-btn" and data-testid="delete-confirm-btn"
to replace the fragile button text/positional selectors. Assert Save button
starts disabled before editing.

* docs(files): add stack file explorer documentation

Add user-facing guide for the stack file explorer feature covering
tier access (Community read-only, Skipper+ read-write), viewing
limits, upload/download caps, protected file routing, and
troubleshooting. Update the editor page to reference the new guide
and register the page in the navigation.

* fix(docs): use canonical Skipper tier name in file explorer overview card

* fix(files): resolve lint errors blocking CI

Remove unnecessary backslash escape before double-quote in the
Content-Disposition regex (no-useless-escape). Replace five synchronous
setState resets at the top of the FileTree mount effect with a React key
prop on the FileTree element in StackFileExplorer so remounting resets
state automatically, eliminating the react-hooks/set-state-in-effect
violation.

* test(files): fix e2e seeding to work on community-tier CI

Replace the browser-side paid upload/mkdir API calls in seedTestStack with
direct Node fs writes. The upload and folder endpoints require Skipper+ so
they returned 403 on CI, which runs with no license set. Stack creation
via POST /api/stacks stays as an API call since it is community-allowed and
keeps the backend registry in sync.

Add a per-test tier check in the Skipper+ beforeEach that skips gracefully
when the instance is community, matching the pattern in auto-heal-policies.
2026-04-26 13:05:19 -04:00

105 lines
5.8 KiB
Plaintext

---
title: Stack File Explorer
description: Browse, edit, upload, and manage files inside a stack's directory from the dashboard.
---
The file explorer gives you direct access to everything inside a stack's directory: configuration files, certificates, scripts, static assets, and any other files your containers depend on. It lives on the **Files** tab inside the stack editor.
<Note>
The explorer is scoped to the stack's own directory. You cannot browse other stacks or navigate above the stack root.
</Note>
## Opening the file explorer
Select any stack in the sidebar, then click the **Files** tab in the editor. The left panel shows the directory tree; clicking a file opens it in the viewer on the right.
## Tiers at a glance
<CardGroup cols={2}>
<Card title="Community" icon="eye">
Browse the directory tree and view text files in read-only mode.
</Card>
<Card title="Skipper" icon="pencil">
Full read/write access: upload, download, edit and save, create folders, and delete files or directories. Admiral tier includes the same access.
</Card>
</CardGroup>
## Browsing the directory tree
The tree lists all files and folders in the stack directory. Folders are sorted before files, and items within each group are sorted alphabetically.
Click a folder to expand or collapse it. Click a file to open it in the viewer panel on the right.
### Protected files
`compose.yaml`, `docker-compose.yaml`, `docker-compose.yml`, `.env`, and any `.env.*` variants are shown in the tree, but clicking them does not open the generic file viewer. Instead, Sencho navigates to the dedicated **Compose** or **Env** tab editor for that file. This ensures you always use the purpose-built editor with save-and-deploy controls for your main stack files.
## Viewing files
When you click a text file, its contents appear in the editor panel on the right.
**Viewing limits:**
| File type | Behaviour |
|-----------|-----------|
| Text file up to 2 MB | Displayed inline with syntax highlighting. |
| Text file over 2 MB | "File is too large to preview" message with a **Download** button (Skipper+). |
| Binary file | "Binary file detected" message with a **Download** button (Skipper+). |
On Community, the Download button is hidden for files that exceed the preview limit. To access large or binary files from the Community tier, use the host terminal or `docker cp` from your server.
## Editing and saving (Skipper+)
Text files open in read-only mode by default. Click **Edit** in the toolbar to enable editing. The editor accepts plain text input.
When you're done, click **Save** to write the file to disk. Unsaved changes are discarded when you navigate away or close the tab.
<Warning>
Editing a file does not restart any containers. If your stack reads the file at runtime (e.g. a config file mounted as a volume), restart the relevant service after saving for the change to take effect.
</Warning>
## Uploading files (Skipper+)
Click **Upload** in the file explorer toolbar, then select one or more files from your local machine. Files are uploaded into the currently selected directory.
**Limits:**
- Maximum 25 MB per file.
- Files larger than 25 MB are rejected with an error. Split large archives or use `scp` / `rsync` for bulk transfers.
If a file with the same name already exists in the target directory, it is overwritten.
## Downloading files (Skipper+)
Click the **Download** button in the file toolbar (for the currently open file) or right-click a file in the tree and choose **Download**. Files are streamed directly to your browser. The download limit is 100 MB per file.
<Tip>
For files larger than 100 MB, use `docker cp`, `scp`, or mount an export volume and copy from there.
</Tip>
## Creating folders (Skipper+)
Click the **New Folder** button in the tree toolbar, or right-click an existing folder and choose **New Folder**. Enter a name and confirm. The new folder is created immediately and appears in the tree.
Folder names follow the same rules as filenames on your host OS. Avoid leading dots unless you intend a hidden directory.
## Deleting files and folders (Skipper+)
Right-click a file or folder in the tree and choose **Delete**. A confirmation prompt appears before anything is removed.
**Deleting a folder** removes the folder and all of its contents recursively. The confirmation dialog lists the path so you can verify before proceeding.
<Warning>
Deletions are permanent. Sencho does not keep a trash bin or undo history for file operations.
</Warning>
## Troubleshooting
| Symptom | Cause | Resolution |
|---------|-------|------------|
| File shows as "Binary file detected" but it is a text file | The binary detection heuristic found null bytes or a high proportion of non-printable characters, which can happen with certain encodings or line endings | Download the file, edit it locally or via an SSH session, then re-upload. Alternatively, open a host terminal and edit with `nano` or `vim`. |
| Upload fails with a 413 error | The file exceeds the 25 MB per-file limit | Compress or split the file before uploading, or transfer it via `scp` / `rsync` directly to the stack directory on the host. |
| Cannot delete a folder | The UI requires confirmation before a recursive delete | Confirm the deletion prompt. If the error persists, check whether a running container has an open file handle inside that directory, which can block the delete on some OS configurations. Stop the relevant service and retry. |
| File saved but container still reads old content | The container caches the file at startup or the mount is read-only inside the container | Restart the service after saving so the container picks up the new file. |
| Download button not visible | You are on the Community tier, and the file exceeds the preview limit or is binary | Upgrade to Skipper to enable downloads, or access the file via the host terminal or `docker cp`. |