Commit Graph

629 Commits

Author SHA1 Message Date
Anso c09673d8bb docs: refresh screenshots (#471) 2026-04-10 10:07:49 -04:00
Anso 8bf3a524d9 chore(main): release 0.42.6 (#469) 2026-04-10 10:05:56 -04:00
Anso c0c321227b perf: unify caching behind a single CacheService and enable HTTP compression (#468)
Replaces five ad-hoc in-process caches (project name map, templates, latest
version, fleet update status, remote node meta) with a single internal
CacheService that provides TTL, inflight-promise deduplication to protect
against thundering herd, stale-on-error fallback, and per-namespace
hit/miss/stale/size counters for observability.

Wraps the hot-path dashboard endpoints in the cache with write-path
invalidation: /api/stats (2s), /api/system/stats (3s), and
/api/stacks/statuses (3s). Keys are namespaced by nodeId so switching nodes
never serves another node's data. Every route that mutates container or
stack state calls invalidateNodeCaches(nodeId), which also drops the global
project-name-map, so user actions stay instantly reflected in the UI.

For /api/system/stats the cheap per-request network rx/tx block is kept
outside the cache so live-updating charts stay smooth while the expensive
systeminformation.currentLoad() CPU sample (~200ms) is reused across the
TTL.

Adds admin-only GET /api/system/cache-stats returning per-namespace
counters for operators who want to observe cache effectiveness.

Enables the compression middleware site-wide for JSON responses. Large
payloads like /api/templates shrink roughly 5x on the wire. SSE endpoints
are explicitly excluded via a Content-Type filter so live log tails and
metric streams are not buffered.

Bumps vitest hookTimeout to match testTimeout (15s) so parallel fork
workers do not hit the default 10s hook limit under CPU contention.

Adds 35 new tests (26 unit for CacheService, 9 integration for cached
endpoints) covering TTL expiry, inflight dedup, stale-on-error,
namespace invalidation, entry-cap safety guard, and write-path
invalidation end-to-end through Express routes.
2026-04-10 10:05:05 -04:00
dependabot[bot] 7321267f28 chore(deps-dev): bump @types/node from 25.5.0 to 25.5.2 in /backend (#439)
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 25.5.0 to 25.5.2.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-version: 25.5.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: SaelixCode <dev@saelix.com>
2026-04-09 20:55:40 -04:00
dependabot[bot] 39c63461d5 chore(deps-dev): bump eslint from 10.1.0 to 10.2.0 in /frontend (#438)
Bumps [eslint](https://github.com/eslint/eslint) from 10.1.0 to 10.2.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Commits](https://github.com/eslint/eslint/compare/v10.1.0...v10.2.0)

---
updated-dependencies:
- dependency-name: eslint
  dependency-version: 10.2.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: SaelixCode <dev@saelix.com>
2026-04-09 20:55:18 -04:00
dependabot[bot] c5e972d244 chore(deps-dev): bump typescript-eslint in /frontend (#436)
Bumps [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) from 8.58.0 to 8.58.1.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.58.1/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: typescript-eslint
  dependency-version: 8.58.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: SaelixCode <dev@saelix.com>
2026-04-09 20:54:56 -04:00
dependabot[bot] 1d90583a6e chore(deps-dev): bump eslint from 10.1.0 to 10.2.0 in /backend (#437)
Bumps [eslint](https://github.com/eslint/eslint) from 10.1.0 to 10.2.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Commits](https://github.com/eslint/eslint/compare/v10.1.0...v10.2.0)

---
updated-dependencies:
- dependency-name: eslint
  dependency-version: 10.2.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-09 20:51:57 -04:00
dependabot[bot] 0f95f30430 chore(deps): bump @aws-sdk/client-ecr in /backend (#435)
Bumps [@aws-sdk/client-ecr](https://github.com/aws/aws-sdk-js-v3/tree/HEAD/clients/client-ecr) from 3.1022.0 to 3.1026.0.
- [Release notes](https://github.com/aws/aws-sdk-js-v3/releases)
- [Changelog](https://github.com/aws/aws-sdk-js-v3/blob/main/clients/client-ecr/CHANGELOG.md)
- [Commits](https://github.com/aws/aws-sdk-js-v3/commits/v3.1026.0/clients/client-ecr)

---
updated-dependencies:
- dependency-name: "@aws-sdk/client-ecr"
  dependency-version: 3.1026.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-09 20:51:30 -04:00
dependabot[bot] 9a45dc0cd8 chore(deps-dev): bump vite from 8.0.5 to 8.0.7 in /frontend (#434)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 8.0.5 to 8.0.7.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v8.0.7/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 8.0.7
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-09 20:51:21 -04:00
dependabot[bot] 87fb71ae9b chore(deps-dev): bump vitest from 4.1.2 to 4.1.3 in /backend (#432)
Bumps [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest) from 4.1.2 to 4.1.3.
- [Release notes](https://github.com/vitest-dev/vitest/releases)
- [Commits](https://github.com/vitest-dev/vitest/commits/v4.1.3/packages/vitest)

---
updated-dependencies:
- dependency-name: vitest
  dependency-version: 4.1.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-09 20:51:08 -04:00
dependabot[bot] d515611640 chore(deps-dev): bump @types/node from 25.5.0 to 25.5.2 in /frontend (#431)
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 25.5.0 to 25.5.2.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-version: 25.5.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-09 20:50:52 -04:00
dependabot[bot] 4773b4ef92 chore(deps-dev): bump typescript-eslint in /backend (#440)
Bumps [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) from 8.58.0 to 8.58.1.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.58.1/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: typescript-eslint
  dependency-version: 8.58.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-09 20:50:38 -04:00
Anso 8d3cae78e1 ci: optimize workflows to cut redundant runs (#465)
* ci: optimize workflows to cut redundant runs and add timeout protection

Skip validation jobs (build, test, lint, Docker, E2E) on push-to-main
since code was already validated on the PR. Skip CI entirely for
release-please PRs (version bump only). Add timeout-minutes to all jobs
to prevent hung runners from burning 360 min (GitHub default). Add npm
caching to the start-app composite action. Add concurrency control to
docker-publish. Group Dependabot updates to batch PRs per ecosystem.

Estimated savings: ~10 min per release cycle, ~30-40 min/day.

* chore: add tsbuildinfo to gitignore
2026-04-09 20:48:26 -04:00
Anso 0e4a8e85e2 chore(main): release 0.42.5 (#464) 2026-04-09 20:19:28 -04:00
Anso 8adcef8e47 fix(fleet): add Docker Hub fallback for version detection on private repos (#463)
* fix(fleet): add Docker Hub fallback for version detection on private repos

The GitHub Releases API returns 404 for private repos, causing the
latest version fetch to silently fail and fall back to the gateway's
own version (defeating the update detection fix from PR #454).

Now tries GitHub first, then falls back to Docker Hub tags API which
is always public. Adds console.warn logging on fetch failures per
Directive 7.

* ci: trigger CI re-run
2026-04-09 20:10:55 -04:00
Anso 9b4b6c2f1e docs: refresh screenshots (#462) 2026-04-09 19:17:27 -04:00
Anso 746cc9eb2c chore(main): release 0.42.4 (#461) 2026-04-09 19:14:39 -04:00
Anso 8e1b9826cf fix(api): add tiered rate limiting to prevent polling lockouts (#460)
* fix(api): add tiered rate limiting to prevent polling lockouts

Replace the single global rate limiter (100 req/min/IP) with a tiered
system that separates high-frequency polling endpoints from standard
API traffic:

- Polling tier (300/min): /stats, /system/stats, /stacks/statuses,
  /metrics/historical, /health, /meta, /auth/status, /auth/sso/providers,
  /license. Exempt from the global limiter but governed by their own
  safety net to prevent resource exhaustion.
- Standard tier (200/min): All other endpoints, raised from 100.
- Webhook tier (500/min): POST /webhooks/:id/trigger, dedicated limiter
  for CI/CD platforms sharing datacenter IPs.
- Auth tier: Unchanged (5-10 attempts / 15 min).

Enterprise adaptations:
- Authenticated requests keyed by user session (JWT sub/username) instead
  of IP, preventing shared NAT/VPN environments from pooling budgets.
- Internal node-to-node traffic (node_proxy tokens) bypasses all rate
  limiters entirely.

Includes comprehensive stress tests (21 cases) validating tier
separation, node proxy bypass, and per-user keying.

* chore(deps): bump axios to 1.15.0 to fix SSRF vulnerability

Addresses GHSA-3p68-rc4w-qgx5 (NO_PROXY hostname normalization bypass).
2026-04-09 17:56:58 -04:00
Anso 7b6cd79e92 docs: refresh screenshots (#459) 2026-04-09 13:30:56 -04:00
Anso c812a79acc chore(main): release 0.42.3 (#458) 2026-04-09 13:15:30 -04:00
Anso 8de82ed81f fix(fleet): prevent modal flash when clicking Recheck button (#457)
Use a separate recheckingUpdates state for the Recheck button so the
modal content stays visible while refreshing. The full-screen loading
spinner (checkingUpdates) is now only used for the initial "Check Updates"
open from the header.
2026-04-09 13:10:50 -04:00
Anso 1d883f4675 docs: refresh screenshots (#456) 2026-04-09 12:55:56 -04:00
Anso 1799fca77e chore(main): release 0.42.2 (#455) 2026-04-09 12:53:09 -04:00
Anso 368bef20d3 fix(fleet): detect updates via GitHub Releases instead of gateway self-comparison (#454)
* fix(fleet): detect updates via GitHub Releases instead of gateway self-comparison

The fleet update check compared each node's version against the gateway's
own version, so the local node could never appear outdated. Now fetches
the actual latest release from GitHub Releases API with a 30-minute
in-memory cache and thundering-herd protection. The Recheck button
invalidates this cache via ?recheck=true to force a fresh lookup.

* docs(fleet): update docs to reflect GitHub Releases version detection

Replace "Gateway version" references with "Latest version" to match the
new label. Document that version comparison uses the latest GitHub release
rather than the gateway's own version, and that Recheck refreshes the
cached latest version.
2026-04-09 12:21:37 -04:00
Anso 53d9316f20 docs: refresh screenshots (#453) 2026-04-09 10:06:17 -04:00
Anso 58eaabd8c2 chore(main): release 0.42.1 (#452) 2026-04-09 10:03:16 -04:00
Anso 089d43b855 fix(ui): standardize toast background to match floating overlay glass style (#451)
Replace bg-card/80 with bg-popover/95 and match the backdrop-blur/saturate
values used by dialogs, dropdowns, and popovers. Remove per-type colored
gradient overlays so color differentiation comes from icons only. Unify the
determinate progress bar gradient with the loading bar's brand gradient.
2026-04-09 09:57:13 -04:00
Anso cc23732727 docs(topology): update network topology docs with new features and screenshots (#450)
Document dagre auto-layout, enriched container nodes, click-to-logs,
and system network toggle. Refresh screenshots to show current UI.
2026-04-08 21:42:40 -04:00
Anso 5d6c87f3b0 docs: refresh screenshots (#449) 2026-04-08 20:17:55 -04:00
Anso 7f17ada531 chore(main): release 0.42.0 (#448) 2026-04-08 20:14:26 -04:00
Anso 3ee4fe6e44 feat(topology): overhaul network topology with dagre layout, enriched nodes, and click-to-logs (#447)
Replace N+1 Docker API calls (inspectNetwork per network) with a
container-centric approach that fetches all networks and containers
in 2 parallel calls, then maps relationships in memory.

Add dagre auto-layout algorithm for hierarchical DAG visualization,
replacing the static two-row layout that caused edge spaghetti at scale.

Add "Show system networks" toggle, enrich container nodes with running
state indicators, stack badges, and base image names. Clicking a
running container opens its log viewer directly from the topology graph.
2026-04-08 20:05:22 -04:00
Anso 1524b64847 docs: refresh screenshots (#446) 2026-04-08 17:16:08 -04:00
Anso ea03406c6b chore(main): release 0.41.2 (#445) 2026-04-08 17:13:15 -04:00
Anso 8080540881 fix(fleet): strip trailing slash in fetchRemoteMeta URL construction (#444)
Nodes with a trailing slash in api_url (e.g. http://host:3000/) caused
fetchRemoteMeta to construct a double-slash URL (http://host:3000//api/meta),
which failed silently. The update-status endpoint then returned version: null
for the remote node, triggering a false "Update available" badge.

Every other URL construction in the codebase already strips the trailing
slash; this was the one call site that was missed.
2026-04-08 17:08:39 -04:00
Anso 8d5bde6a8c docs: refresh screenshots (#443) 2026-04-08 15:11:58 -04:00
Anso d632a6a207 chore(main): release 0.41.1 (#442) 2026-04-08 15:05:02 -04:00
Anso 6fff2c2d35 fix(fleet): resolve self-update compose file access and improve completion detection (#441)
The self-update feature failed on remote nodes because SelfUpdateService
ran `docker compose -f <host_path>` inside the container, where the host
compose file path does not exist. The fix splits the update into two
steps: (1) pull the latest image directly via `docker pull`, and (2)
spawn a short-lived helper container that mounts the compose directory
from the host and runs `docker compose up --force-recreate`.

Additional changes:
- Use execFileSync/execFile with argument arrays instead of shell strings
  to eliminate shell injection surface from Docker label values
- Add Signal 4 completion detection: mark update as completed when the
  remote version matches the gateway version (with 15s elapsed guard)
- Extend early failure heuristic from 90s to 3 minutes for slow pulls
- Distinguish "node unreachable" from "node lacks self-update capability"
  in error messages; use silent skip in update-all to avoid res crashes
- Add requireAdmin guard to POST /api/system/update
- Handle comma-separated compose config file paths (multiple -f flags)
- Update fleet docs with self-update mechanism, troubleshooting entries
2026-04-08 14:59:11 -04:00
Anso 24011aea9e docs: refresh screenshots (#430) 2026-04-08 10:58:38 -04:00
Anso f41f3a1639 chore(main): release 0.41.0 (#429) 2026-04-08 10:55:38 -04:00
Anso 8e0857e7f6 feat(contact): add official contact emails throughout app and docs (#428)
Surface six dedicated email channels (support, contact, licensing,
security, privacy, conduct) across contextually appropriate locations:

- security.txt: create .well-known/security.txt for app (RFC 9116)
- SECURITY.md: add security@sencho.io as alternative to GitHub reporting
- PaidGate/AdmiralGate: add licensing@sencho.io to upgrade prompts
- docs: new Contact & Support reference page listing all channels
- CHANGELOG: document additions under [Unreleased]

Website changes (separate repo): privacy@sencho.io in Privacy/Terms
pages, licensing@sencho.io in Refund page and pricing footer,
contact@sencho.io in website footer.
2026-04-08 09:52:42 -04:00
Anso be7eda85f1 fix(billing): hide billing portal for lifetime licenses (#427)
Lifetime licenses have no recurring subscription, so the Lemon Squeezy
customer portal cannot generate a URL. The Manage Subscription button in
Settings already had the isLifetime guard, but the Billing button in the
profile dropdown did not, causing a confusing "No billing portal
available" error.

- Add !license.isLifetime guard to UserProfileDropdown (matches
  LicenseSection pattern)
- Move lifetime detection into getBillingPortalUrl() so the service owns
  all billing eligibility logic
- Change return type to { url } | { error } discriminated union for
  clear error propagation
2026-04-08 09:51:27 -04:00
Anso f6d2199978 feat(resources): add loading toast for prune, delete, and purge operations (#426)
Show a loading notification with spinner and indeterminate progress bar
while Resource Hub operations are in progress, replacing the dead moment
between confirmation and result.
2026-04-08 09:46:27 -04:00
Anso 662bc1a210 fix(resources): unify container/resource classification with multi-fallback resolution (#425)
* fix(sidebar): add service name and config_files fallbacks for container-to-stack matching

Containers that predate Sencho's reorganization of compose files into
subdirectories carry stale Docker labels where the project name is set
to the COMPOSE_DIR basename (e.g. "compose") rather than the stack
directory name. The existing project name map and working_dir fallbacks
from PR #416 did not cover this case.

Added two new fallback strategies to getBulkStackStatuses:
- Match by com.docker.compose.service label against known stack names
- Extract stack name from com.docker.compose.project.config_files path

Also reused the existing isPathWithinBase utility for path containment
checks and hoisted path.resolve(COMPOSE_DIR) out of the per-container
loop.

* fix(resources): unify container/resource classification with multi-fallback resolution

Extract shared helpers (resolveContainerStack, resolveProjectLabel,
buildAbsDirMap) and apply them consistently across getClassifiedResources,
pruneManagedOnly, getDiskUsageClassified, and getBulkStackStatuses.

Fixes incorrect "External" tagging in Resources Hub for images, volumes,
and networks belonging to stacks that predate Sencho's compose file
reorganization. Also fixes the "active" plural on the dashboard
Containers card.
2026-04-08 09:46:21 -04:00
Anso 2354beed02 fix(console): send proxy tier headers for remote node console-token requests (#424)
The gateway's console-token fetch to remote nodes was missing the
x-sencho-tier and x-sencho-variant headers. Without them, the remote's
requireAdmiral guard fell back to its own local license (Community),
rejecting the request with 403 and surfacing as a 502 "Connection error"
in the browser.

Additionally, the remote's WS upgrade handler for host-console checked
only the local LicenseService instead of respecting proxy tier headers
on console_session tokens. Both paths now propagate and honour proxy
tier headers consistently.
2026-04-08 09:46:14 -04:00
Anso a6849aedab feat(dashboard): add node badge to Recent Alerts for remote node alerts (#423)
Alerts from remote nodes now display a node name badge in the Recent
Alerts card, matching the existing badge style in the notification
panel. This helps users identify which node generated each alert.
2026-04-08 09:46:08 -04:00
Anso 0d6e5367b1 docs: refresh screenshots (#422) 2026-04-07 01:57:04 -04:00
Anso ce6d435dc1 chore(main): release 0.40.0 (#421) 2026-04-07 01:54:15 -04:00
Anso 455bfa8734 fix(schedules): filter auto-update policies from Scheduled Operations view (#420)
* fix(schedules): filter auto-update policies from Scheduled Operations view

Auto-update policies (action=update) were appearing in both the
Auto-Update tab and the Scheduled Operations tab. Added server-side
action/exclude_action query params to GET /api/scheduled-tasks.
ScheduledOperationsView now requests ?exclude_action=update and
AutoUpdatePoliciesView requests ?action=update, so each view only
shows its relevant tasks.

* fix(schedules): use typeof guard for query param type safety

Express can parse ?action[]=x as an array. Use typeof === 'string'
guard instead of unsafe type assertion.
2026-04-07 01:49:22 -04:00
Anso ca8f22734d fix(auto-update): proxy update execution to remote nodes via Distributed API (#419)
* fix(auto-update): proxy update execution to remote nodes via Distributed API

Remote auto-update policies previously failed because the scheduler tried
to access the Docker daemon directly on remote nodes. Now the scheduler
detects remote nodes and proxies the update execution via HTTP to the
remote Sencho instance's new /api/auto-update/execute endpoint, which
runs image checks and compose updates locally on the remote machine.

* test(auto-update): add getNode mock to NodeRegistry in scheduler tests

The executeUpdate method now calls NodeRegistry.getNode() to detect
remote nodes. The test mock for NodeRegistry was missing this method,
causing the two executeUpdate tests to fail.
2026-04-07 01:44:39 -04:00
Anso 8920d8c55a feat(nav): add pulsing animation to local node status dot (#418)
* feat(nav): add pulsing animation to local node status dot

Apply the same animate-ping pattern used by the notification bell badge
to the local node context pill in the top header bar. The green dot
now pulses to give a stronger "live connection" signal.

* fix(nav): use animate-pulse instead of animate-ping for node status dot

Switch the local node pill dot from the ping pattern (expanding ring)
to animate-pulse for consistency with the remote node pill. Only the
notification bell badge should use animate-ping.
2026-04-07 01:44:33 -04:00