Files
sencho/docs/docs.json
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

290 lines
8.6 KiB
JSON

{
"$schema": "https://mintlify.com/docs.json",
"theme": "mint",
"name": "Sencho",
"colors": {
"primary": "#00BEC7",
"light": "#007982",
"dark": "#00BEC7"
},
"logo": {
"light": "/images/logo/logo-light.png",
"dark": "/images/logo/logo-dark.png",
"href": "https://sencho.io"
},
"favicon": "/images/logo/favicon.ico",
"appearance": {
"default": "dark"
},
"fonts": {
"family": "Geist"
},
"background": {
"decoration": "gradient",
"color": {
"dark": "#090909"
}
},
"navbar": {
"links": [
{
"type": "github",
"href": "https://github.com/AnsoCode/Sencho"
}
],
"primary": {
"type": "button",
"label": "Get Started",
"href": "https://sencho.io/#pricing"
}
},
"footer": {
"socials": {
"github": "https://github.com/AnsoCode/Sencho"
},
"links": [
{
"header": "Product",
"items": [
{ "label": "Website", "href": "https://sencho.io" },
{ "label": "Pricing", "href": "https://sencho.io/#pricing" },
{ "label": "Changelog", "href": "https://github.com/AnsoCode/Sencho/releases" }
]
},
{
"header": "Community",
"items": [
{ "label": "GitHub", "href": "https://github.com/AnsoCode/Sencho" },
{ "label": "Contributing", "href": "https://github.com/AnsoCode/Sencho/blob/main/CONTRIBUTING.md" }
]
},
{
"header": "Legal",
"items": [
{ "label": "Terms of Service", "href": "https://sencho.io/terms" },
{ "label": "Privacy Policy", "href": "https://sencho.io/privacy" }
]
}
]
},
"api": {
"auth": {
"method": "bearer",
"name": "Authorization"
},
"playground": {
"display": "simple"
}
},
"navigation": {
"tabs": [
{
"tab": "Documentation",
"groups": [
{
"group": "Getting Started",
"pages": [
"getting-started/introduction",
"getting-started/quickstart",
"getting-started/configuration",
"getting-started/sso-quickstart"
]
},
{
"group": "Features",
"pages": [
"features/overview",
"features/dashboard",
"features/global-search",
"features/sidebar",
"features/stack-management",
"features/editor",
"features/stack-file-explorer",
"features/deploy-progress",
"features/resources",
"features/app-store",
"features/global-observability",
"features/host-console",
"features/multi-node",
"features/pilot-agent",
"features/fleet-view",
"features/fleet-sync",
"features/remote-updates",
"features/stack-labels",
"features/alerts-notifications",
"features/notification-routing",
"features/webhooks",
"features/git-sources",
"features/rbac",
"features/atomic-deployments",
"features/fleet-backups",
"features/audit-log",
"features/api-tokens",
"features/private-registries",
"features/vulnerability-scanning",
"features/deploy-enforcement",
"features/cve-suppressions",
"features/auto-update-policies",
"features/auto-heal-policies",
"features/scheduled-operations",
"features/sso",
"features/two-factor-authentication",
"features/licensing"
]
},
{
"group": "Reference",
"pages": [
"security",
"features/node-compatibility",
"reference/settings",
"reference/verifying-images",
"reference/contact",
"reference/security-advisories"
]
},
{
"group": "Operations",
"pages": [
"operations/troubleshooting",
"operations/backup",
"operations/upgrade",
"operations/self-hosting",
"operations/trivy-setup",
"operations/two-factor-admin"
]
}
]
},
{
"tab": "API Reference",
"openapi": "openapi.yaml",
"pages": [
"api-reference/overview",
"api-reference/security",
{
"group": "Health & Meta",
"pages": [
"GET /api/health",
"GET /api/meta",
"POST /api/system/update"
]
},
{
"group": "Stacks",
"pages": [
"GET /api/stacks",
"POST /api/stacks",
"GET /api/stacks/{stackName}",
"PUT /api/stacks/{stackName}",
"DELETE /api/stacks/{stackName}",
"GET /api/stacks/{stackName}/envs",
"GET /api/stacks/{stackName}/env",
"PUT /api/stacks/{stackName}/env",
"GET /api/stacks/{stackName}/containers",
"GET /api/stacks/{stackName}/services",
"POST /api/stacks/{stackName}/deploy",
"POST /api/stacks/{stackName}/down",
"POST /api/stacks/{stackName}/start",
"POST /api/stacks/{stackName}/stop",
"POST /api/stacks/{stackName}/restart",
"POST /api/stacks/{stackName}/update",
"POST /api/stacks/{stackName}/rollback",
"GET /api/stacks/{stackName}/backup"
]
},
{
"group": "Containers",
"pages": [
"GET /api/containers",
"GET /api/containers/{id}/logs",
"POST /api/containers/{id}/start",
"POST /api/containers/{id}/stop",
"POST /api/containers/{id}/restart"
]
},
{
"group": "API Tokens",
"pages": [
"POST /api/api-tokens",
"GET /api/api-tokens",
"DELETE /api/api-tokens/{id}"
]
},
{
"group": "Webhooks",
"pages": [
"GET /api/webhooks",
"POST /api/webhooks",
"PUT /api/webhooks/{id}",
"DELETE /api/webhooks/{id}",
"GET /api/webhooks/{id}/history",
"POST /api/webhooks/{id}/trigger"
]
},
{
"group": "Nodes",
"pages": [
"GET /api/nodes",
"POST /api/nodes",
"GET /api/nodes/{id}",
"PUT /api/nodes/{id}",
"DELETE /api/nodes/{id}",
"POST /api/nodes/{id}/test",
"GET /api/nodes/{id}/meta"
]
},
{
"group": "Fleet",
"pages": [
"GET /api/fleet/overview",
"GET /api/fleet/node/{nodeId}/stacks",
"GET /api/fleet/node/{nodeId}/stacks/{stackName}/containers",
"GET /api/fleet/update-status",
"POST /api/fleet/nodes/{nodeId}/update",
"POST /api/fleet/update-all",
"POST /api/fleet/snapshots",
"GET /api/fleet/snapshots",
"GET /api/fleet/snapshots/{id}",
"POST /api/fleet/snapshots/{id}/restore",
"DELETE /api/fleet/snapshots/{id}"
]
},
{
"group": "Scheduled Tasks",
"pages": [
"GET /api/scheduled-tasks",
"POST /api/scheduled-tasks",
"GET /api/scheduled-tasks/{id}",
"PUT /api/scheduled-tasks/{id}",
"DELETE /api/scheduled-tasks/{id}",
"PATCH /api/scheduled-tasks/{id}/toggle",
"POST /api/scheduled-tasks/{id}/run",
"GET /api/scheduled-tasks/{id}/runs",
"GET /api/scheduled-tasks/{id}/runs/export"
]
},
{
"group": "Registries",
"pages": [
"GET /api/registries",
"POST /api/registries",
"PUT /api/registries/{id}",
"DELETE /api/registries/{id}",
"POST /api/registries/{id}/test"
]
},
{
"group": "Image Updates",
"pages": [
"GET /api/image-updates",
"POST /api/image-updates/refresh",
"GET /api/image-updates/status"
]
}
]
}
]
}
}