Commit Graph

112 Commits

Author SHA1 Message Date
Anso 4fc363301a feat(webhooks): add CI/CD webhook integration for triggering stack actions (Pro) (#177)
Add custom webhooks allowing external CI/CD systems (GitHub Actions, GitLab CI, etc.)
to trigger stack actions via HTTP POST with HMAC-SHA256 signature authentication.
Includes webhook CRUD management UI in Settings, execution history tracking,
one-time secret reveal, enable/disable toggle, and comprehensive documentation.
2026-03-26 09:23:23 -04:00
Anso 95f46c9d15 chore(main): release 0.6.0 (#175) 2026-03-26 09:20:56 -04:00
Anso 0630f57ca8 feat(fleet): add Pro fleet management features and container drill-down (#174)
Add fleet health summary cards, container-level drill-down, node sorting
and filtering, fleet-wide search, and critical node detection to the
Fleet View. Fix ProGate to wrap placeholder content and add error toasts.
2026-03-25 23:09:53 -04:00
Anso ed78dbd02d chore(main): release 0.5.0 (#170) 2026-03-25 21:52:39 -04:00
Anso f80190d926 feat(auth): redesign Login and Setup pages with split-panel branding layout (#168)
* feat(auth): redesign Login and Setup pages with split-panel branding layout

Replace plain Card-based Login and Setup forms with a professional
split-panel layout: always-dark branding panel (dot grid texture, logo,
tagline, cyan accent line) on the left, theme-aware form on the right.
Mobile collapses to single column with compact logo header.

- Add optional admin email field on Setup for license recovery
- Backend accepts and stores admin_email in setup endpoint
- Fix data-stacks-loaded attribute forwarding (wrap in div, use string values)

* docs: add license-settings and profile-dropdown screenshots

* fix(docs): correct logo paths from .svg to .png

* fix(deps): resolve picomatch high-severity vulnerability
2026-03-25 19:47:48 -04:00
Anso bb46ab728c chore(main): release 0.4.0 (#154) 2026-03-25 10:46:31 -04:00
Anso e0319b5dae feat(auth): redesign Login and Setup pages with split-panel branding layout (#153)
Replace plain Card-based Login and Setup forms with a professional
split-panel layout: always-dark branding panel (dot grid texture, logo,
tagline, cyan accent line) on the left, theme-aware form on the right.
Mobile collapses to single column with compact logo header.

- Add optional admin email field on Setup for license recovery
- Backend accepts and stores admin_email in setup endpoint
- Fix data-stacks-loaded attribute forwarding (wrap in div, use string values)
2026-03-25 10:27:31 -04:00
Anso 7d20f2e3c5 chore(main): release 0.3.1 (#150) 2026-03-25 09:10:10 -04:00
Anso fda9679632 chore(main): release 0.3.0 (#146) 2026-03-25 08:40:13 -04:00
Anso 4f26f22cce feat: add Community/Pro licensing, fleet view, and UI reorganization (#145)
* feat: add license gating system with Lemon Squeezy integration

Add Community/Pro tier infrastructure:
- LicenseService singleton with Lemon Squeezy license API integration
- /api/license endpoints (GET info, POST activate/deactivate/validate)
- 14-day Pro trial activated automatically on first boot
- 72-hour periodic validation with 30-day offline grace period
- LicenseContext provider for frontend tier awareness
- License settings tab with activation UI and status display
- ProBadge and ProGate reusable components for feature gating
- requirePro per-route guard for backend Pro-only endpoints
- Proxy bypass for /api/license routes (local-only, never proxied)

* feat: add user profile dropdown and reorganize top navigation

- Create UserProfileDropdown component with settings, billing, theme
  toggle (System/Light/Dark), documentation links, and logout button
- Remove logout button from sidebar header
- Remove standalone settings button from top bar
- Move theme toggle from Settings modal to profile dropdown
- Inject app version via Vite define from root package.json
- Add globals.d.ts for __APP_VERSION__ type declaration

* refactor(settings): remove appearance tab from settings modal

Theme toggle was moved to the User Profile Dropdown in the previous
commit. Remove the now-redundant Appearance section, its nav button,
and the unused theme/setTheme props from SettingsModal.

* feat: add fleet view dashboard and about settings section

Fleet Overview: aggregates all nodes into a card grid showing status,
container counts, CPU/RAM/disk usage bars. Pro tier unlocks stack
drill-down with auto-refresh (30s). Backend endpoints /api/fleet/overview
and /api/fleet/node/:nodeId/stacks query nodes in parallel.

About section in Settings: displays version, license tier, status,
instance ID, and links to docs/changelog/issues.

Sidebar perf fix: stack status fetches now run in parallel via
Promise.allSettled instead of sequential for-loop, significantly
reducing load time for nodes with many stacks.

Also removes version number from User Profile Dropdown (now in About).

* fix(ci): resolve Docker build and E2E test failures

- Copy root package.json into frontend build stage so vite.config.ts
  can read the app version during Docker multi-stage build.
- Update auth E2E test: logout button moved into User Profile Dropdown.
- Update nodes E2E test: Settings button moved into User Profile Dropdown.
2026-03-25 08:32:07 -04:00
Anso 2f39becfab chore(main): release 0.2.5 (#142) 2026-03-25 02:35:51 -04:00
Anso c6633b0245 fix(charts): suppress Recharts dimension warnings on initial render (#141)
Add minHeight={0} alongside minWidth={0} on ResponsiveContainer and
remove default aspect-video class that conflicts with explicit h-full
sizing, causing -1 dimension measurements during initial layout.
2026-03-25 02:30:31 -04:00
Anso e8110bf7af chore(main): release 0.2.4 (#139) 2026-03-25 01:51:06 -04:00
Anso c5217cd96d fix(csp): allow external images in App Store and suppress console warnings (#138)
Add https: to img-src CSP directive so App Store template icons from
external registries (raw.githubusercontent.com) load correctly. Disable
Origin-Agent-Cluster header (only meaningful over HTTPS). Add minWidth={0}
to Recharts ResponsiveContainer to suppress dimension warnings.
2026-03-25 01:47:59 -04:00
Anso 4fe51ec17a chore(main): release 0.2.3 (#135) 2026-03-25 01:15:13 -04:00
Anso 1e6367a147 fix(env): resolve 404 when loading env files and CSP inline script violation (#134)
The /envs endpoint now filters to only return env files that actually exist
on disk, and absolute env_file paths from compose files (e.g. shared
globals.env in a sibling directory) are no longer rejected. The inline
theme-detection script is moved to an external file to comply with CSP.
2026-03-25 01:03:22 -04:00
Anso 6f1b2cfa9c chore(main): release 0.2.2 (#98) 2026-03-24 21:28:19 -04:00
Anso 7aac99abdf chore(main): release 0.2.1 2026-03-24 19:46:44 -04:00
Anso 36a9bf3109 fix(editor): Monaco CSP fix + release pipeline fixes
fix(editor): Monaco CSP fix + release pipeline fixes — v0.2.1
2026-03-24 19:46:33 -04:00
SaelixCode 79fde6e2bd fix(editor): bundle Monaco locally to fix stuck Loading state and CSP block
- Add monaco-editor to frontend deps and configure @monaco-editor/react
  to use the local bundle via loader.config({ monaco }) instead of
  fetching from cdn.jsdelivr.net (blocked by CSP scriptSrc: self)
- Wire editorWorker via Vite ?worker syntax — blob: URLs already
  permitted by existing workerSrc CSP directive, no CSP changes needed
- fix(ci): use DOCS_REPO_TOKEN in release-please so tag creation
  cascades to docker-publish.yml (GITHUB_TOKEN cannot trigger workflows)
- fix(ci): auto-merge screenshots PR via gh pr merge --auto --squash
2026-03-24 19:43:55 -04:00
github-actions[bot] 8f7419f710 chore(main): release 0.2.0 2026-03-24 23:06:51 +00:00
SaelixCode c294def7cc feat(ci): add release-please automated versioning workflow 2026-03-24 19:01:09 -04:00
SaelixCode 6c911fd67f fix(ci): trigger docker-publish on v* tag push instead of GitHub Release event 2026-03-24 18:48:21 -04:00
SaelixCode a1b944ca14 chore(release): structure CHANGELOG for v0.1.0 2026-03-24 18:00:44 -04:00
Anso 1e0014e183 fix(docker): replace QEMU npm execution with tonistiigi/xx cross-compilation (#78)
Node.js v20 uses ARMv8.1 LSE atomic instructions that the GitHub Actions
QEMU version does not support, causing SIGILL / "Illegal instruction" core
dumps when npm ci runs inside the arm64 emulated layer.

Solution: add a dedicated prod-deps build stage that runs on $BUILDPLATFORM
(amd64, native) and uses tonistiigi/xx + xx-clang to cross-compile native
modules (bcrypt, better-sqlite3, node-pty) for the TARGET architecture
without ever executing Node.js under QEMU. The final runtime stage simply
copies the pre-compiled node_modules from prod-deps.

Stage breakdown:
  xx            - cross-compilation helper (provides xx-clang, xx-apk)
  frontend-builder  - $BUILDPLATFORM, vite build (no native modules)
  backend-builder   - $BUILDPLATFORM, tsc compilation
  prod-deps         - $BUILDPLATFORM + xx-clang cross-compiles for $TARGET
  final             - TARGET platform, no compilation at all
2026-03-24 08:29:52 -04:00
Anso 278f7f18d9 fix(ci): add linux/arm64 Docker build support with QEMU optimization (#76)
- Add docker/setup-qemu-action@v3 to docker-publish.yml (was missing,
  causing multi-platform builds to hang indefinitely)
- Add platforms: linux/amd64,linux/arm64 to build-push-action step
- Optimize Dockerfile with --platform=$BUILDPLATFORM on builder stages
  so TypeScript compilation runs at native amd64 speed; only the lean
  npm ci --omit=dev step runs under QEMU in the final stage, compiling
  the three native modules (bcrypt, better-sqlite3, node-pty) for the
  correct target architecture — reduces arm64 build time from 6+ hours
  to ~15-30 minutes
2026-03-24 07:50:33 -04:00
Anso b25574a427 feat(ui): theme-aware sidebar logo with dark/light variants (#74)
Replace single sencho-logo.png with dark and light variants that
auto-switch based on the active theme (dark/light/auto system pref).
2026-03-23 12:11:23 -04:00
SaelixCode 2e69c8ec75 chore(docs): migrate from mint.json to docs.json
Mintlify v2 requires docs.json; mint.json is deprecated and causes
"docs.json not found" errors in the Mintlify dashboard editor.

Changes:
- Add docs/docs.json with v2 schema: navigation wrapped in { groups: [] },
  theme field, logo/favicon placeholder paths at /images/logo/
- Delete docs/mint.json
2026-03-23 08:36:15 -04:00
SaelixCode 8902f6fb4c fix(ci): exclude .git from rsync --delete in sync-docs
rsync --delete removes everything in the destination that is absent from
the source. sencho/docs/ has no .git directory, so rsync was deleting
sencho-docs/.git/ on every run. The commit step then had no git repo to
work with, giving 'fatal: not in a git directory'.

Fix: add --exclude='.git' to the rsync invocation.
2026-03-23 08:02:42 -04:00
SaelixCode b24863db66 fix(ci): fix update-screenshots token and sync-docs empty-repo handling
update-screenshots:
- Add pull-requests: write to job permissions (required by
  peter-evans/create-pull-request to open a PR)
- Switch checkout and create-pull-request from GITHUB_TOKEN to
  DOCS_REPO_TOKEN (classic PAT with repo scope); GITHUB_TOKEN is
  blocked from creating PRs against protected branches

sync-docs:
- Replace actions/checkout + init fallback with a single bash step that
  runs git clone and falls back to git init on failure; actions/checkout
  on an empty repo creates a .git in a broken state (no HEAD/branch),
  which the previous [ ! -d .git ] guard never caught, leaving the
  commit step with no valid working tree
- Drop --global safe.directory config (no longer needed once we own
  the git setup ourselves)
2026-03-23 00:28:47 -04:00
SaelixCode 9505132aaf fix(ci): fix sync-docs empty-repo crash and update-screenshots protected-branch push
update-screenshots: replace direct git push to develop (blocked by branch
protection) with peter-evans/create-pull-request@v6, which opens or
updates a chore/refresh-screenshots PR instead.

sync-docs: add continue-on-error on the sencho-docs checkout so an empty
repo doesn't abort the job. A new init step runs git init -b main and
re-adds the remote when .git is missing. Final push uses
git push origin HEAD:main to work on both first-run (empty repo) and
subsequent runs (existing branch).
2026-03-23 00:09:31 -04:00
SaelixCode 932e7780ea docs: add Tier 3 reference and operations pages
- reference/settings: complete settings hub reference (all 7 tabs —
  Account, System Limits, Notifications, Appearance, Developer, Nodes,
  App Store)
- operations/troubleshooting: 1:1 path rule failures, Docker socket
  permissions, login errors, WebSocket proxy config, offline remote
  nodes, password reset, health endpoint, container logs
- operations/backup: what to back up (DATA_DIR SQLite + COMPOSE_DIR),
  hot backup via sqlite3, cron example, restore steps, host migration
  walkthrough
- mint.json: populate Reference and Operations nav groups
2026-03-22 23:25:15 -04:00
SaelixCode 7c120c671c docs: add Tier 2 feature pages
- dashboard: container stats, system stats, historical charts, docker run converter
- resources: disk footprint widget, images/volumes/networks/unmanaged tabs, prune scopes
- app-store: template grid, category filter, deploy sheet (env vars/volumes/ports), custom registry
- global-observability: unified log stream, standard vs SSE mode, filters, capacity limits
- host-console: PTY terminal, shell type, security model, common uses
- mint.json: add all 5 new pages to Features nav group
2026-03-22 22:43:53 -04:00
SaelixCode 98910c4117 docs: bootstrap user-facing documentation from codebase audit
- Add 5 new Tier 1 doc pages: configuration, stack-management, editor,
  multi-node, and alerts-notifications
- Update introduction, quickstart, and features/overview to reflect
  current feature set and link to new pages
- Restructure mint.json with Getting Started / Features / Reference /
  Operations navigation groups
- Add Playwright-captured screenshots for all major UI screens
2026-03-22 22:39:06 -04:00
SaelixCode dc79683b01 fix(ci): YAML syntax error in if condition and safe.directory for sync-docs 2026-03-22 22:00:57 -04:00
SaelixCode ed8b8e33b6 feat: add update-screenshots CI job and screenshot capture spec 2026-03-22 21:28:20 -04:00
SaelixCode 9496b14f72 feat: add automated docs pipeline and scaffold /docs folder
- Add sync-docs CI job: runs on push to main, copies /docs into sencho-docs repo via DOCS_REPO_TOKEN
- Scaffold /docs with mint.json, getting-started/introduction.mdx, getting-started/quickstart.mdx, features/overview.mdx
- Update CHANGELOG
2026-03-22 21:08:16 -04:00
SaelixCode 9f0257e94f fix(merge): resolve CHANGELOG conflict with develop 2026-03-22 19:48:37 -04:00
SaelixCode 35a57e5fa7 fix(security): disable COOP header and Vite module-preload polyfill
Two console errors on HTTP deployments with no functional impact:

1. Helmet's default Cross-Origin-Opener-Policy: same-origin is ignored
   by browsers over HTTP but logged as a console error. Disabled via
   crossOriginOpenerPolicy: false (same rationale as HSTS/COEP).

2. Vite's production build injects an inline module-preload polyfill
   script blocked by script-src 'self'. Disabled via
   build.modulePreload.polyfill: false — all modern browsers support
   link rel="modulepreload" natively.
2026-03-22 19:41:13 -04:00
SaelixCode d62ac09503 fix(stats): classify managed containers by working_dir instead of project name
Containers launched from the COMPOSE_DIR root (not individual subdirs)
all share the root folder's name as their com.docker.compose.project
label, making them appear as "external" despite being in COMPOSE_DIR.

Switch to com.docker.compose.project.working_dir: a container is
managed if its working directory is within COMPOSE_DIR, regardless
of what project name Docker Compose assigned to it.
2026-03-22 18:36:17 -04:00
SaelixCode 50df5b3c02 fix(security): explicitly disable upgrade-insecure-requests via Helmet 8 API
Helmet 8 merges custom directives with its built-in defaults, which
include upgrade-insecure-requests. Simply omitting the directive from
the custom object (PR #59) was insufficient — Helmet silently re-adds
it from defaults. Setting upgradeInsecureRequests: null is the correct
Helmet 8 API to remove a default directive.

This was the root cause of the persistent blank page on plain-HTTP
self-hosted deployments: the directive tells browsers to upgrade all
HTTP sub-resource fetches to HTTPS, producing ERR_SSL_PROTOCOL_ERROR
on every JS/CSS asset.
2026-03-22 17:26:08 -04:00
SaelixCode aeefd79b50 fix(proxy): prevent remote 401 from triggering local session logout
- Add x-sencho-proxy sentinel header to all proxied responses so
  the frontend can distinguish remote auth failures from local session
  expiry, breaking the logout loop when a node's api_token expires
- Add authMiddleware to all 5 /api/notifications endpoints that were
  missing protection (default-deny policy enforcement)
- Expand CSP to include connectSrc ws:/wss: and workerSrc blob:
  for WebSocket and Monaco editor worker support
2026-03-22 16:43:06 -04:00
Anso 5ddf85d364 Merge branch 'develop' into fix/helmet-csp-and-docker-socket-diagnosis 2026-03-22 15:51:41 -04:00
SaelixCode cf2946cfa6 fix(security): remove CSP upgrade-insecure-requests and HSTS over HTTP
Helmet's defaults are designed for HTTPS-only deployments. Two directives
were actively breaking plain-HTTP self-hosted instances:

- upgrade-insecure-requests: causes browsers to upgrade all JS/CSS/asset
  fetches to HTTPS → ERR_SSL_PROTOCOL_ERROR → completely blank page
- Strict-Transport-Security: permanently instructs browsers to refuse HTTP
  for 1 year, even after the header is removed from the server

Also handle docker socket GID=0 (root:root) edge case in entrypoint and
add [entrypoint] diagnostic log lines for easier debugging.
2026-03-22 15:42:48 -04:00
SaelixCode 593a709197 fix(docker): add entrypoint for volume permission handling
Add docker-entrypoint.sh that runs as root at startup, fixes ownership
of the DATA_DIR volume (only files with wrong user or group), then drops
to the non-root sencho user via su-exec before starting Node.

This eliminates the SQLITE_READONLY crash that occurs when a host-mounted
data volume was created by root or chowned to the wrong UID. The pattern
mirrors the official PostgreSQL, Redis, and MariaDB Docker images.

- Install su-exec in Stage 3 (10KB Alpine tool, idiomatic alternative to gosu)
- Remove USER directive; entrypoint handles the privilege drop instead
- Use ENTRYPOINT + CMD so Node becomes PID 1 (correct SIGTERM handling)
- Add .gitattributes to enforce LF line endings for *.sh files
2026-03-22 15:16:34 -04:00
SaelixCode 5191737d53 feat(resources): managed/unmanaged resource separation across Resources Hub
- Classify all Docker images, volumes, and networks as managed (Sencho
  stack), external (other Compose project), or unused/system via a new
  getClassifiedResources() method and GET /api/system/resources endpoint
- Add pruneManagedOnly() + getDiskUsageClassified() to DockerController
- Prune buttons now default to Sencho-managed scope; "All Docker" is
  hidden in a ⋮ dropdown with a distinct destructive confirm dialog
- Replace Reclaimable Space donut with interactive Docker Disk Footprint
  widget (stacked bar with clickable segments that filter resource tabs)
- Add managed/external filter toggles and classification badges per tab
- GET /api/stats now returns managed + unmanaged container counts; Home
  Dashboard Active Containers card subtitle shows "N managed · N external"
- Rename "Ghost Containers" tab/copy to "Unmanaged Containers" throughout
2026-03-22 03:17:54 -04:00
SaelixCode 707a5e81c1 fix(e2e): fill api_token in nodes tests so submit button is enabled
The Add Node button requires both api_url AND api_token to be non-empty
before it enables. Both validation tests were only filling api_url,
leaving the button permanently disabled and timing out after 30s.
Add a dummy api_token fill in each test so the button enables and the
form submits — the backend then correctly rejects the invalid URL.
2026-03-22 01:58:47 -04:00
SaelixCode c8a54a988b fix(lint): resolve all ESLint errors to pass CI lint step
- Replace catch (error: any) with catch (error) + (error as Error).message cast
  in EditorLayout, NodeManager, HomeDashboard, AppStoreView
- Define TemplateVolume interface in AppStoreView; replace volumes any[] with typed array
- Define MetricPoint interface in HomeDashboard; replace metrics any[] with MetricPoint[]
- Define TerminalContainer type in BashExecModal; replace as any DOM property casts
- Define NodeTestInfo interface in NodeManager; replace info: any with typed shape
- Fix DockerNetworkStats cast in EditorLayout container stats WebSocket handler
- Remove unused catch variable (e) in api.ts and other components
- Cast streamFilter onValueChange val to union type in GlobalObservabilityView
- Add eslint-disable-next-line react-refresh/only-export-components to badge.tsx,
  button.tsx, AuthContext, NodeContext, use-data-state, use-is-in-view
- Add eslint-disable-next-line react-hooks/set-state-in-effect in LogViewer
- Add /* eslint-disable */ to animate-ui third-party primitive files
2026-03-22 00:27:43 -04:00
SaelixCode f7471a1a18 fix(e2e): get all E2E tests passing and fix AlertDialog crash on delete
- Fix rate limiter to allow 100 attempts in dev mode so E2E tests are
  not blocked by failed-login attempts during test development
- Simplify logout button selector to use Lucide icon class (lucide-log-out)
  instead of the fragile Tooltip-content locator chain that broke on navigation
- Rewrite stacks E2E spec: use waitForFunction to wait for sidebar to load,
  delete leftover stacks via browser-context fetch before creating, and use
  page.reload() to get a clean sidebar state
- Fix AlertDialogContent: remove asChild+motion.div pattern that triggered a
  React.Children.only crash — Radix AlertDialog.Content injects a second
  DescriptionWarning child internally, breaking Slot when asChild=true; replace
  with CSS keyframe animations (data-[state=open]:animate-in)
- Fix final assertion in delete test to use exact text + listbox scope to avoid
  false positives from similarly-named stacks like e2e-test-stack-*
- All 6 E2E tests pass (4 auth + 2 stacks); node tests skip gracefully
2026-03-21 23:58:46 -04:00
SaelixCode ce50db0fde security: pre-release hardening, automated testing, and production readiness
SECURITY (critical fixes):
- Add authMiddleware to /api/system/console-token (was publicly accessible)
- Validate api_url on node create/update to prevent SSRF (rejects localhost/loopback)
- Add rate limiting (5 req/15 min/IP) to /api/auth/login and /api/auth/setup
- Fix path traversal in env_file resolution — absolute/escaping paths rejected
- Add stack name validation to GET routes (was only on PUT/POST)
- Add helmet security headers middleware
- Restrict CORS to FRONTEND_URL in production

PRODUCTION READINESS:
- Add GET /api/health public endpoint + HEALTHCHECK in Dockerfile
- Add SIGTERM/SIGINT graceful shutdown handler (drains connections, closes DB)
- Run container as non-root sencho user in Dockerfile

QUALITY:
- Fix 4 silent empty catch{} blocks in EditorLayout (now show toast.error)
- Connect ErrorBoundary to root App in main.tsx
- Replace WebSocket.Server with named WebSocketServer import (ESM compat)

TESTING (new automated test suite):
- Install Vitest; 38 backend tests across 4 suites covering validation utilities,
  health endpoint, auth middleware, login flows, SSRF protection, and path traversal
- Extract isValidStackName/isValidRemoteUrl/isPathWithinBase to utils/validation.ts
- Playwright E2E scaffolding: auth, stacks, nodes specs + shared login helper
- CI: run Vitest + ESLint on every PR
2026-03-21 21:59:44 -04:00