mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-12 11:47:11 +00:00
c704cb54d20a41329af5319b04b3c58f1b58d3f6
12 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
6688da97b1 |
fix(image-updates): match any local RepoDigest against the remote tag (#1695)
* fix(image-updates): match any local RepoDigest against the remote tag Docker can list a stale multi-arch index digest ahead of the current one on the same image. Selecting only the first RepoDigest caused false same-tag rebuilds (for example redis:8.8.0) even when another digest equaled the registry primary. Compare every matching candidate and keep fail-closed behavior for empty, unknown-platform, and classification errors. Fixes #1684 * fix(image-updates): surface digest verification failures to operators Carry comparator errors into update-preview as check_error / verification_failed, prefer failed checks over sticky has_update in Fleet and the sidebar, and keep Update Guard from claiming no pending update when verification failed. * fix(e2e): align sidebar truncation spec with check-failed precedence StackRow now shows the check-failed icon over a stale update dot, but this spec still asserted the old precedence and failed deterministically in CI on every attempt. * fix(fleet): treat verification-only previews as non-actionable Fresh update-preview wins over sticky fleet booleans: disable Apply, exclude from ready counts, and move verification-only stacks into the check-failures advisory (including remote-labeled names). * fix(fleet): move preview actionability helpers out of the view Exporting non-components from AutoUpdateReadinessView tripped react-refresh lint in CI. Keep the helpers in a shared lib module and drop an unused mock arg. * fix(fleet): drop sticky cards when fresh preview clears the update A successful no-update preview now removes the pending Fleet card instead of leaving Apply enabled. Verification-only stacks still go to the advisory, and empty-state copy no longer claims all-clear while checks remain unresolved. * fix(image-updates): hold full-stack apply for review when another image fails verification A confirmed update or rebuild on one image previously left the whole stack fully actionable even when a different image in the same stack failed digest verification: Anatomy claimed "safe to apply", Update Guard reported ready, and Fleet's full-stack Apply stayed enabled, all while showing the verification-failure text right next to those claims. isActionableUpdatePreview now requires no verification failure anywhere in the stack; a new isReviewRequiredUpdatePreview flags the mixed state so Fleet still surfaces the card (not silently cleared) with Apply now disabled and a "Review · unverified" badge. Anatomy's banner says "review required" instead of a bump-based safety claim and withholds its Apply button. Update Guard's pending-update signal downgrades from ok to attention. Per-service apply (Fleet's per-image row) is deliberately left enabled since a service-scoped update to the confirmed image does not touch the unverified one. * fix(image-updates): treat rebuild_available symmetrically with has_update in Update Guard updatePreviewSignal only downgraded to 'attention' inside the has_update branch, so a rebuild-only stack (has_update false, rebuild_available true) with a sibling verification failure fell through to the plain verification-only 'unknown' branch and never mentioned the pending rebuild, inconsistent with isReviewRequiredUpdatePreview on the frontend which treats has_update and rebuild_available the same way. Also adds desktop-card coverage for the mixed state (previously only the mobile card was exercised) and locks in blocked/major-bump precedence over the new review-required badge/banner in both Fleet and Anatomy. * fix(image-updates): derive the mixed-verification review-hold from per-image detail, not the stack aggregate has_update and check_error are independent per image: a tag-based update can be confirmed via the registry's tag list even when that same image's own digest comparison against the current tag errored (already covered by an existing update-preview-service test). The stack-level verification_failed and has_update flags can therefore both be true for the SAME single image, which the previous review-hold treated identically to a genuinely different image failing verification: Update Guard said "another image failed digest verification" and Fleet told the user to "apply the confirmed service individually" on a single-service stack where no such affordance exists. isReviewRequiredUpdatePreview (and isActionableUpdatePreview) now walk the preview's images to require a pure failure image (check_error, no has_update of its own) alongside a genuinely different confirmed image or rebuild, falling back to the old aggregate-only judgment when per-image detail is unavailable. StackAnatomyPanel now imports the shared helper instead of hand-rolling the same predicate, so Fleet and Anatomy cannot drift apart. Backend updatePreviewSignal gets the same per-image treatment via a new optional images parameter, threaded through from UpdateGuardService. * fix(image-updates): fail closed on platform-unavailable indexes and legacy previews, allow anonymous tag listing Four independent gaps from the same QA pass, all in the digest/tag verification path this PR introduced or touches: - compareLocalToRemoteTag now distinguishes a remote index with no descriptor at all for the local platform (including an empty or fully-filtered index) from a genuine mismatch: the former returns an error instead of reporting a speculative update. A node cannot pull a platform the index does not offer. - selectLocalRepoDigests no longer falls back to a sole unrelated-repository RepoDigest when nothing matches the configured repo; comparing against a registry state that has nothing to do with the declared image risks a false update. Returns unresolved instead of guessing. - isClearedUpdatePreview no longer treats a preview with verification_failed missing entirely (not merely false) as proof the stack is clean. The current backend always includes this field, so its absence identifies an older remote node's response, which cannot vouch for a clean result the way an explicit false can. - listRegistryTags (and the underlying listRegistryTagsResult) no longer short-circuits to an empty list whenever no registry credentials are configured. getAuthToken already resolves anonymous tokens for public repositories; skipping it meant tag-based update detection silently never fired for any public image without a stored credential. * fix(image-updates): correct platform-check overreach, add cache and advisory for prior fixes Addresses code-review findings on the previous commit: - The platform-unavailable check fired too eagerly: an index whose runnable descriptors legally omit platform (OCI-permitted, routed to exactDigests) has real pullable content, so it must not be confused with a genuinely empty or fully-filtered index. Now only errors when both platform-labeled descriptors and exactDigests are empty. - listRegistryTags is now cached (15 min TTL): anonymous listing has no other rate limiting, and Fleet fans this out across every image on every reload. - A legacy preview (kept rather than cleared) now also pushes a check-failure advisory entry explaining why, instead of rendering as an unexplained pending card. - Corrected docstrings that described the old sole-unmatched-digest fallback and inverted how anonymous registry auth actually resolves. Adds coverage for: nested-index and attestation-only-filtered platform unavailability, a platform-less-but-populated index staying a match, the unrelated-repo digest rejection wired through the real preview-computation path (not just the registry-api unit), and legacy-preview interaction with an actionable has_update:true. * fix(image-updates): fail closed on mixed platform indexes, stop caching tag-list failures Addresses a second review round on the previous commit, including an empirically-verified regression: - The exactDigests fallback was unconditional: an index mixing a platform-labeled descriptor for a DIFFERENT platform with an unlabeled leaf let that leaf stand in as this platform's content, reporting a speculative update for a genuinely incompatible platform. Now an unlabeled leaf is only trusted when it is the ONLY kind of descriptor in the index (nothing else claims a different platform); a mixed index errors instead. - listRegistryTags was caching failed lookups for the full 15-minute TTL (a 429, an unreachable registry, or credentials not yet configured all looked identical to a real empty tag list). The fetcher now throws on failure so only a success is ever cached; CacheService's existing stale-on-error fallback still serves the last good list when one exists. - The manual "Recheck" action now also drops the tag-list cache, so a newly published tag is visible immediately instead of waiting out the TTL. - The legacy-preview advisory no longer fires when the same preview is already actionable on its own terms (a remote's own confirmed has_update/rebuild_available): pairing a "could not be checked" banner with an enabled Apply button next to it contradicted itself. - Corrected docstrings and a self-contradictory inline comment left over from the prior fix. New coverage: the exact mixed-index regression this round found and fixed, cache hit/no-repeat-fetch and failure-not-cached behavior, and the legacy-preview-plus-already-actionable non-contradiction. * fix(image-updates): add digest_error unmasked field, reorder RiskBadge, fix actionability gates Add digest_error to UpdatePreviewImage as an always-populated field that is independent of check_status masking: a confirmed tag-based update on the same image resolves check_status to 'ok' and nulls check_error, but digest_error stays set since the image's current tag content was never verified. Switch hasUnverifiedOtherImage to read digest_error. Reorder RiskBadge precedence so reviewRequired is checked before uncertain (both derive from check_error, so uncertain was unreachable). Fix self-contradiction in test fixtures (digest_update:true + check_error). Add masked-tag regression test with two-image fixture. Simplify hasUnverifiedOtherImage per code review feedback. |
||
|
|
0daddfde00 |
fix: reconcile sticky update indicators with Anatomy preview (#1698)
* fix: reconcile sticky update indicators with Anatomy preview Sidebar, Updates filter, and Fleet treated retained partial/failed scanner has_update as confirmed. Keep raw state for retention/notifications, project confirmed-only to APIs, show distinct incomplete indicators, and clear sticky rows only after an authoritative-negative preview. Closes #1685 * test: align sidebar truncate E2E with failed-over-retained precedence Purple update indicators are confirmed-only; hasUpdate with a failed check correctly shows the failed trailing icon. * fix: clear confirmed update rows on authoritative-negative preview Address audit SF-1/SF-2/SF-3: observation-watermark clears for older ok+has_update rows (DB + memory gens), Fleet checkability parity with backend not_checkable, and Updates chip confirmed-only regressions. * fix: tombstone equal-generation writers on preview clear Advance the per-stack write generation when clearing at the observation watermark so a scanner reserved before preview cannot recreate the row after an authoritative-negative reconcile. * fix: clear sticky updates with digest and tag preview parity Share detection across scanner and preview, keep GET read-only with POST reconcile, gate Apply to digest and rebuild updates, and invalidate the hub fleet cache on clear. * test: set digestUpdate on auto-update checkImage mocks Scheduler and execute routes now gate Compose on digest drift; fixtures that expect an apply need digestUpdate so they exercise the update path. * fix: clear unused lint errors on sticky update branch Drop unused partial helper and fleet invalidate import; keep the CacheService inflight self-ref as let with an eslint exception so tsc stays green. * fix: use inflight holder for CacheService prefer-const Keep generation-aware ownership without a let self-reference that fights ESLint and tsc. |
||
|
|
e2001fed72 |
feat: open compose editor in edit mode with one click (#1592)
* feat: open compose editor in edit mode with one click Remove the desktop double edit gate so Anatomy "Edit compose" and empty-stack create land in an immediately editable workspace. Close discards both compose and env buffers; deferred loads keep startInComposeEdit through unsaved confirm. * docs: align compose editor close behavior and Edit compose labels Update stack-management and editor docs for one-click edit mode. Close discards unsaved compose/env edits. Anatomy shortcuts use Edit compose. * fix(e2e): update routing test to use anatomy-edit-compose-btn test id The compose editor env tab routing test was written against the old 'edit' button name. The single-compose-edit-gate branch renamed that button to 'Edit compose' with test id 'anatomy-edit-compose-btn'. Update the selector to match. |
||
|
|
f30a65ee08 |
fix(anatomy): name each image with a pending update in the update banner (#1575)
Show each updated image and tag transition in the Anatomy banner. Uses the per-image data the update-preview API already returns. |
||
|
|
f2b5c68d84 |
feat: add build-aware compose stack updates (#1561)
Detect services with build: in the update preview and run compose build --pull plus pull --ignore-buildable when Update is triggered on those stacks, while keeping the existing pull-only path for image-only stacks. |
||
|
|
9ea2864d60 |
feat: per-stack storage inventory and portability guardrails (#1399)
* feat: per-stack storage inventory and portability guardrails Add a Storage tab to the stack Anatomy panel that derives a per-stack mount inventory (bind mounts, named/anonymous volumes, tmpfs, docker socket; read-only vs read-write; host-path existence, type, and owner) from the effective Compose model, and classifies the stack as Portable, Partially portable, Node-bound, or Unknown with the reasons behind it. - New GET /api/stacks/:stackName/storage route (stack:read, Community), served by an on-demand, non-persisted service that renders the effective model, probes within-stack bind sources (symlink-escape aware), and runs the deterministic portability classifier. - Extend the effective-model parser additively with a full per-mount inventory and service-level tmpfs, leaving the rule-facing binds/namedVolumes byte-identical for the existing preflight rules. - New anonymous-volume preflight finding. - Admin-visible "no recent snapshot" warning that reuses the existing hub-local snapshot-coverage endpoint, plus a static note distinguishing config snapshots from application-data backups. - Surface storage assumptions in the Stack Dossier markdown export. - Gate the tab behind a new compose-storage capability on both sides. * docs: phrase the Storage tab availability as current behavior Replace the "older Sencho version / until it is updated" wording in the Storage feature page with present-tense, capability-based phrasing. |
||
|
|
ba09e6f69e |
fix: base Git multi-file Compose deploy env and dossier on the effective config (#1391)
* fix: resolve the root .env at deploy and render time for Git context-dir stacks A Git multi-file source with a context dir set --project-directory to that dir, so Docker Compose looked for .env there and missed the root .env Sencho writes. Validation already passed the root .env with --env-file, so a stack could validate with one effective config but deploy or render another. Add authoredComposeEnvFileArgs, which appends --env-file <stackDir>/.env when the applied deploy spec has a context dir and a root .env exists, and wire it into the deploy/update, image-scan, render, and container-listing compose invocations so they all resolve env from the same file the validator used. A non-ENOENT access error surfaces instead of silently dropping the flag. * fix: base multi-file Git dossier and doc-drift on the effective Compose model The Stack Dossier and its documentation-drift check parsed only the stored root compose file. For a multi-file Git source, services, ports, networks, or volumes that an override file adds were invisible, so the dossier showed incomplete facts and doc-drift could falsely warn that a documented port is unpublished when an override actually publishes it. Add a secret-safe GET /stacks/:name/effective-anatomy that renders the merged effective model and extracts only structural facts (services, ports, volumes, networks, restart), never env, label, or command values. StackAnatomyPanel fetches it for multi-file Git stacks and feeds those facts into the dossier and doc-drift, falling back to the root-only parse for single-file or non-git stacks and whenever the render is unavailable. * fix: add an inline path-injection barrier to the Git env-file resolver CodeQL js/path-injection flagged the fs.access in authoredComposeEnvFileArgs because the env path derives from the route-supplied stack name and the only containment check lived in the callers, not at the sink. Resolve the stack dir against the compose base and assert containment with startsWith inline, then derive the .env path from the validated dir, mirroring the existing inline guards in renderConfig and validateCompose. Valid stack names are unaffected; a name that escapes the base now yields no --env-file. * test: stabilize the dossier doc-drift e2e against the dossier-load race The first assertion filled the access_urls field as soon as the Dossier panel was visible, but the panel's GET /stacks/:name/dossier resolves by overwriting the fields from the server (empty access_urls) and only then flips the doc-drift gate on. When the GET landed after the fill, it clobbered the typed value and the warning never rendered, so the test failed intermittently under CI timing. Wait for that GET to land before typing, mirroring the spec's openStack helper. |
||
|
|
77f1611971 |
feat: Compose Network Inspector and exposure intent guard (#1360)
* feat: add Compose Network Inspector facts engine Render a stack's authored effective model and pair it with the live Docker snapshot to derive per-stack networking facts: project networks with external and internal flags, service-to-network membership and aliases, published ports with host-binding scope, network_mode, and extra_hosts, plus runtime drift (runtime-only attachments, foreign networks, and declared-but-unused or missing networks). Extend the effective-model parser with service network membership, extra_hosts, and label keys (key names only, never values), and add a key-space normalized network model with adapters from both the rendered model and the raw declared compose so the Inspector and drift share one comparison. Expose GET /api/stacks/:stackName/networking: advisory and read-only, it renders the authored model only and never returns or logs raw stderr, env values, or label values. * feat: store and edit per-stack and per-service exposure intent Add a stack_exposure_intent table (intent values constrained by a CHECK, unique per node, stack, and service) with DAO methods to read, upsert, clear one row, and clear all rows for a stack. The classification is stored independently of the generated networking facts so a later mismatch stays detectable; service rows are kept separately from the stack-level row (service ''). Expose GET and PUT /api/stacks/:stackName/exposure: GET requires read access, PUT requires edit access and validates the intent against the allowed set. Sending intent null clears that row, returning the scope to unset so a service inherits the stack intent again. Intent rows are cleared when the stack is deleted and when the owning node is removed, so a later same-named stack never picks up stale classification. * feat: add exposure-aware Compose Doctor findings Feed the Compose Doctor's effective-model context with the stored exposure intent (resolved into a stack-level value plus per-service overrides) and the dossier's documented access-URL ports, read fail-soft so a metadata read error skips these checks rather than failing the preflight. Add five deterministic findings on top of that context: - a service classified internal or same-node that publishes a host port (same-node tolerates a loopback bind), - a sensitive database or admin image published on all interfaces, - a port-publishing stack with no exposure intent set, - a published port not reflected in the documented access URLs, - reverse-proxy labels with no documented URL or reverse-proxy intent. The rules stay pure functions over the preflight context; the registry completeness test pins the new rule set. * feat: detect compose network drift in the drift ledger Extend the spatial drift engine with two network-level findings: a running container attached to a stack-owned or foreign network that compose does not declare (one finding per service), and a declared network that no running service uses or that is absent from the runtime (one stack-level finding, every network named by its resolved runtime name). The comparison reuses the same helper the Network Inspector uses, so the two surfaces never disagree. Network drift runs only when the stack has running containers and the runtime is reachable, preserving the existing missing-runtime, parse-error, and unreachable behavior. The findings persist through the existing drift ledger and surface on the Drift tab, which now labels the two new kinds. * feat: link a Docker network back to its owning stack Add a cross-component open-stack event and make the owning-stack badge on a managed network in Resources a link: clicking it loads that stack on its node and opens the editor, reusing the existing fleet navigation. A latest-ref keeps the window listener current without re-subscribing each render. Image and volume badges are unchanged; only a managed network opts in via the new optional handler. * feat: add the Networking tab to the stack detail panel Add a capability-gated Networking tab that reads the per-stack networking facts and exposure intent. It shows the project networks (with external, internal, and created-by-stack flags), per-service network membership and aliases, published ports with their host-binding scope, network_mode and extra_hosts, and runtime drift, degrading to the declared model when the runtime is unavailable. Users can classify the stack and each service (internal, LAN, reverse proxy, public, and so on) or clear a row to inherit; the controls are read-only when the user cannot edit, and a broken exposure response never tears down the facts view. A new compose-networking capability is added to both registries so older nodes hide the tab, and the tab cross-links to the Doctor for the deploy and security findings. * docs: document the Compose Networking tab Add a feature page covering the Networking tab: the network facts, published ports and host bindings, the exposure-intent classification and inheritance, the exposure-aware Doctor findings, runtime drift, and a troubleshooting section. Register it in the docs navigation next to Compose Doctor. * feat: add a redacted network summary to the Stack Dossier export Append a network exposure section to the dossier Markdown: the stack and per-service exposure intents, the networks with their external and internal flags, and each service's published ports with their binding scope. It carries only names, intents, port numbers, and scope, never an env value or a label value. The summary is fetched only when the user exports (copy or download), so opening the panel costs nothing, and it degrades to omitting the section when the data is unavailable. The whole-fleet dossier export collects the same summary per stack, rethrowing the unauthorized sentinel like the sibling loaders. * feat: add a Fleet networking filter for exposure and drift Add a per-node networking summary that classifies a node's stacks as exposed (a host port published beyond loopback), unknown-exposure (publishes ports with no exposure intent set), or network-drift. It reads each stack's compose with the light dependency parser and one Docker snapshot, so it stays cheap across a node's full stack set, and it skips drift when the runtime is unreachable rather than inventing it. Serve it node-locally at GET /api/networking/summary, and aggregate it fleet-wide at GET /api/fleet/networking-summary: the hub computes its own summary in-process and reaches each remote through its node-local route, degrading an unreachable or older node to a skip. Because the aggregate lives under the proxy-exempt /api/fleet prefix it is never wrongly proxied. The Fleet overview gains a networking filter chip backed by that aggregate, fetched fail-soft and detached so it never gates the grid. * fix: spin the Networking refresh button while it reloads The refresh button silently refetched the same data, so a click gave no feedback. Track a refreshing state and spin the icon while the load is in flight, disabling the button, matching the Compose Doctor preflight button. * fix: apply effective per-service exposure intent to unclassified checks The "unclassified exposure" decisions only consulted the stack-level intent row, so a service classified directly (with no stack row) was still reported as unclassified, and a service explicitly marked unknown over a classified stack was missed. Both the exposure-unclassified preflight rule and the networking summary's unknown-exposure bucket now resolve the effective intent per publishing service (service row overrides stack row), matching the precedence already used by the exposure-internal-published rule. * fix: resolve drift network names via the compose top-level name When a compose file sets a top-level name:, Docker prefixes resource names with that project name instead of the stack directory. The light dependency parser dropped name:, so network-drift normalization compared runtime networks against directory-prefixed names and reported false network-undeclared / network-missing findings. Carry the parsed project name through DeclaredCompose and use it when normalizing declared networks for drift, while still filtering containers by the stack directory. |
||
|
|
f253276303 |
fix: make published port links open reliably (#1359)
* fix: make published port links open reliably Container published-port links now render as real anchors that open on desktop and mobile, replacing ad hoc window.open calls. A shared service URL builder centralizes host resolution (configured host, remote node API host, or the browser host, with no browser fallback for unreachable remote nodes), protocol selection (HTTPS for port 443), and known app sub-paths (Plex opens its web path). The container port mapping itself is the link, with a Copy URL action beside it. The stack Open App menu and the anatomy panel footer use the same builder, and the menu only offers Open App when a reachable URL can be built. * fix: skip UDP ports and scope known-app paths to the container port Two follow-ups to the published-port links: - The known-app path (Plex web sub-path) was borrowed from the published host port even when the container port was known and unregistered, so a non-Plex service published on host port 32400 wrongly inherited it. The container-port lookup now wins when known; the published-port lookup stays a fallback for the menu and anatomy footer, which only have the host port. - UDP ports could surface as HTTP links. The backend now carries the port protocol through both container-mapping paths and skips UDP when choosing the main web port (extracted as selectMainWebPort), and the container card filters UDP before selecting a port to link. |
||
|
|
52ff0725f4 |
feat: add Compose Doctor preflight checks for stacks (#1348)
* feat: add Compose Doctor preflight checks for stacks Add an on-demand, advisory preflight that renders a stack's effective Compose model with `docker compose config` and runs a registry of deterministic checks before deploy, surfacing findings grouped by severity (blocker, high, warning, info) with a remediation for each. Findings cover unset env vars, host-port conflicts on the node, broad 0.0.0.0 exposure, missing bind-mount paths, a mounted Docker socket, privileged and host networking, moving image tags, missing restart policy and healthcheck, Swarm-only deploy fields, missing external networks or volumes, and container_name collisions. The report is node-scoped and stored as the last run per stack, and the route auto-proxies to the active node so a remote stack is checked on the node that owns it. A new Doctor tab on the stack detail panel runs preflight and shows the grouped findings, with a severity dot on the tab when the last run has blocker or high findings. The tab is gated on a compose-doctor capability so older nodes hide it. No environment value is ever stored, returned, or logged: only env key names and structural facts are read, and render failures surface a generic message or the missing required-variable names, never raw stderr. * fix: scroll the stack tab strip when its tabs overflow Adding the Doctor tab can push the per-stack Anatomy tab strip past the panel width on narrower layouts. Make the tab row scroll horizontally with subtle edge fades that appear only while there is more to scroll in that direction, so a panel wide enough to show every tab is unchanged. * fix: add clickable arrows and wheel scroll to the stack tab strip Hiding the scrollbar left mouse users with no way to scroll the overflowing tab row: a vertical wheel does not move a horizontal overflow and native rows do not drag-scroll. Replace the passive edge fades with clickable chevron arrows shown only when the row overflows that edge, and translate a vertical wheel over the row into horizontal scroll. * fix: inline the path-injection barrier in renderConfig CodeQL's path-injection check does not credit the wrapped isPathWithinBase helper as a sanitizer, so move the containment check inline at the spawn cwd sink, matching the canonical barrier used elsewhere in the codebase. Behavior is unchanged: the resolved stack directory must be contained in the compose base and may not be the base itself. * fix: hoist the compose-config spawn into the path-barrier scope The earlier inline barrier sat in a different scope than the spawn cwd sink (separated by the Promise-executor closure) and used a compound guard, so CodeQL did not credit it. Use the exact canonical startsWith barrier and hoist the spawn into the same scope as the check. Behavior is unchanged: the executor runs synchronously in the same tick as the spawn, so handlers still attach before any event can fire. |
||
|
|
ae0b9d166c |
fix(git-sources): return 200 for stacks without a Git source (#1294)
The dashboard probes GET /api/stacks/<name>/git-source for every stack to
decide whether to show a Git badge. For a stack with no Git source attached
the endpoint answered 404, so a fleet of unlinked stacks painted a red 404
per stack in the browser console.
Return 200 { linked: false } when the stack exists but has no Git source,
and reserve 404 for the genuine "stack does not exist" case (mirroring the
existence guard the PUT handler already uses). The two consumers that read
this endpoint now treat the { linked: false } sentinel as unlinked rather
than as a configured source.
|
||
|
|
2dd0660491 |
fix(stacks): reflect apply progress and clear the anatomy update banner (#1279)
* fix(stacks): reflect apply progress and clear the anatomy update banner The "Update available" banner in the stack Anatomy panel ran its apply in the background but gave no feedback: the apply button stayed idle and the banner never went away once the update finished. Pass an in-flight flag to the panel so the apply button disables and shows progress while the update runs, and re-check the update preview when the apply finishes so the banner clears on success and stays in place when the update did not take effect. * fix(stacks): scope the update re-check to its stack and keep the banner on refresh failure The post-apply re-check of the Anatomy update banner tracked only the in-flight flag, so switching stacks while the first was still updating looked like a completion for the newly selected stack and triggered a stray preview request that could clear that stack's valid banner. Track the stack name alongside the flag and only re-check when the completion belongs to the stack on screen. Also keep the banner when the re-check itself fails (non-OK response or network error) instead of clearing it, so a transient read failure can no longer hide an update that may still be pending. Both failure paths now log for diagnosis. |