✨ feat(rc): v1.5.0-rc.10 — security scan digest + notification UX rework#304
Merged
Conversation
…estations Adds a user-facing "Verify Your Drydock Install" guide under content/docs/current/guides/verifying-releases/. Covers cosign keyless verification for container images (GHCR/Docker Hub/Quay), cosign verify-blob for the release tarball, and SLSA provenance verification via gh attestation verify and slsa-verifier. Uses the current release-from-tag.yml identity regex and notes the legacy release.yml regex for v1.4-and-older tags. Wires the new page into guides/meta.json and adds a top-of-page callout from the existing Security Hardening Guide so readers verify before hardening.
… event Adds the notification-foundation for the security-scan-digest feature (discussion #300). Scheduled scans now emit per-container security alerts (gated by a new opt-in DD_SECURITY_SCAN_NOTIFICATIONS flag to preserve backwards compatibility) and fire a scan-cycle-complete event once the batch finishes. No trigger-side consumer yet — that lands in a follow-up that introduces digest buffering and the DRY'd flush path. - app/store/notification-history.ts: add 'security-alert-digest' NotificationEventKind alongside 'update-available-digest' - app/event/index.ts: new SecurityScanCycleCompleteEventPayload and emit/register ordered-handler pair - app/security/scheduler.ts: emit emitSecurityAlert per container with critical/high findings during scheduled scans (behind the new DD_SECURITY_SCAN_NOTIFICATIONS flag), track alertCount, and emit emitSecurityScanCycleComplete in the finally block so the signal fires even on abort/throw - app/configuration/index.ts: add scan.notifications field (default false) to SecurityConfiguration - tests: round-trip coverage for the new event kind, emit/register coverage for the new event, gated-emission coverage in scheduler
…cycleId Introduces a stable correlation id that threads from scheduled-scan start through every emitted security alert to the cycle-complete signal. Consumers can now group a batch of security alerts into one digest by matching the cycleId. Uses an inline RFC 9562 UUID v7 implementation (time-ordered, string-sort matches chronology) with no new npm dependency. - app/util/uuid.ts: inline uuidv7() — 48-bit unix ms timestamp + version/variant bits + crypto random. ~25 LOC. - app/util/uuid.test.ts: shape, monotonic ordering under a fixed clock, uniqueness across 5000 calls, version nibble, variant bits. - app/event/index.ts: SecurityAlertEventPayload.cycleId (optional — legacy agents without it are treated as single-alert cycles); SecurityScanCycleCompleteEventPayload.cycleId is now required; scope enum widened to include 'on-demand-single' / 'on-demand-bulk' / 'agent-forwarded' for the upcoming consumers. - app/security/scheduler.ts: generate cycleId via uuidv7() at top of runScheduledScans(); thread through runScheduledBatchDigestWorkers → scanDigestGroup → emitPerContainerSecurityAlerts; include on cycle-complete payload. - tests: UUID v7 shape assertion on emitted alerts; same-cycle sharing across alert + cycle-complete; distinct ids across successive runs.
…ty-alert SSE - Agent→controller SSE payload now includes optional cycleId on dd:security-alert. - New dd:security-scan-cycle-complete event forwards scheduled-scan cycle metadata from agents (scannedCount, alertCount, startedAt, completedAt) with scope='agent-forwarded' on the controller side. - Backward compat: controller synthesizes a cycleId + 1-alert cycle-complete when a legacy agent sends a dd:security-alert without cycleId, so digest buffers on the controller side never orphan alerts from older agents.
Implements sections 6.5 (security alert digest), 6.6 (DRY refactor), and 6.7 (SECURITYMODE config + templates) from discussion #300 plan. 6.6 — DRY refactor (behavior-preserving): - Add DigestEventKind type, SecurityDigestEntry, SecurityDigestContext, DigestContext - Extract formatDigestTitle + formatDigestBody pure helpers dispatching on eventKind - Refactor flushDigestBuffer to accept { eventKind, cycleId?, cyclePayload? } options; update-digest path is unchanged, security-digest path uses flushSecurityDigestBuffer - Parameterize shouldHandleDigestContainerReport on eventKind (default = update-available-digest) - Update cron callback to pass { eventKind: 'update-available-digest' } explicitly 6.5 — Security alert digest buffering + cycle-complete handler: - Add securityDigestBuffer: Map<cycleId, Map<containerKey, SecurityDigestEntry>> - handleSecurityAlertEvent: digest path buffers by cycleId; no-cycleId falls through to immediate dispatch; simple/batch modes unchanged - Add handleSecurityScanCycleCompleteEvent: no-op for simple mode; flushes by cycleId for digest-capable modes; idempotent (second call on drained cycle = no-op) - Register/deregister via registerSecurityScanCycleComplete in init/deregisterComponent - Extend seedNotificationHistoryFromStore to include 'security-alert-digest' when securitymode is digest-capable 6.7 — SECURITYMODE config + default severity-grouped digest template: - Add securitymode, securitydigesttitle, securitydigestbody to TriggerConfiguration - validateConfiguration joi schema: securitymode enum (simple|batch|digest|batch+digest, default simple); securitydigesttitle/body optional strings - Add static normalizeSecurityMode + isSecurityDigestCapableMode helpers - Default title: "Security scan complete: N container(s) with findings" - Default body: severity-grouped markdown (critical → high → medium → low per section 7.4) - renderSecurityDigestTemplate evaluates ${scan.*} template literals via Function constructor Buffer design choice: separate securityDigestBuffer (Map<cycleId, Map<containerKey, entry>>) rather than unifying with digestBuffer — keeps update and security paths cleanly separated with no shared state, avoids discriminated-union complexity in the existing Container-keyed map. 28 new tests: securitymode validation, buffer accumulation, last-write-wins, overlapping cycles, zero-alert suppression, idempotent cycle-complete, flush+drain, seed coverage, template rendering + override, formatDigestTitle/Body dispatch. 342 total tests pass (314 pre-existing + 28 new).
- Add `emitSecurityScanCycleComplete` to `SecurityHandlerDependencies` and wire in `container.ts` - Generate `cycleId` (UUID v7) and `startedAt` at entry of `handleScanContainer` - Thread `cycleId` onto `emitSecurityAlert` call via updated `SecurityAlertPayload` - Emit `emitSecurityScanCycleComplete` unconditionally in `finally` block with scope `on-demand-single`, `scannedCount: 1`, and correct `alertCount` - Add 5 new tests: clean scan, alert+cycle correlation, throw resilience, scope/count invariants, UUID v7 shape
…aggregation
- New file `app/api/container/bulk-security.ts` — `createBulkSecurityHandlers` with `scanAll` handler
- Accepts optional body `{ containerIds?, severity? }` with manual validation (no external dep)
- Derives container set server-side; validates each id when containerIds provided
- Generates UUID v7 `cycleId`, responds 202 `{ cycleId, scheduledCount }` immediately
- Runs scans async in background with `MAX_CONCURRENT_BULK_SCANS=4` concurrency pool
- Emits `emitSecurityAlert` (with `cycleId`) per container when critical/high threshold met
- Emits single `emitSecurityScanCycleComplete` with scope `on-demand-bulk` in `finally`
- Fires `broadcastScanStarted`/`broadcastScanCompleted` SSE on each iteration
- Honors AbortSignal tied to `req.on('close')` to stop queueing on disconnect
- Catches and logs per-scan errors; remaining containers complete normally
- New file `app/api/container/bulk-security.test.ts` — 33 tests covering validation, happy-path,
iteration, concurrency limiting, cycleId correlation, SSE, error resilience, abort
- `app/api/container.ts` — wire `createBulkSecurityHandlers`; mount `POST /scan-all` with
1-req/60s rate limiter using identity-aware key generator
- `app/api/openapi/paths/containers.ts` — document `/api/containers/scan-all` (202, 400, 401, 429)
…c.10 - SECURITYMODE / SECURITYDIGESTTITLE / SECURITYDIGESTBODY trigger configuration table rows added; notes expanded to explain that SECURITYMODE governs security-alert delivery independently of MODE, and that scheduled scans opt in via DD_SECURITY_SCAN_NOTIFICATIONS=true. - Security guide scheduled-scans block now documents the opt-in notifications flag and a new "Quiet scan notifications with digest mode" subsection showing SECURITYMODE=digest end-to-end (scheduled cron / Scan All / cycleId grouping). - CHANGELOG entry under [Unreleased] describes the scan-cycle event contract, the bulk POST /containers/scan-all endpoint, per-channel dedup via the new 'security-alert-digest' NotificationEventKind, and the shared flushDigestBuffer refactor.
….5.0 The security scan digest feature (discussion #300) ships in v1.5.0-rc.10: SECURITYMODE trigger config, POST /containers/scan-all bulk endpoint, UUID v7 cycleId event correlation, and per-channel dedup via security-alert-digest. Moves the entry out of the v1.6.0 "Notifications" theme and into v1.5.0 "Observability & User-Requested Features".
…oadmap Pulled forward into v1.5.0-rc.10: adds three bullets to the v1.5.0 roadmap block in page.tsx covering SECURITYMODE=digest, the bulk scan-all endpoint, and UUID v7 cycleId cycle correlation.
…ndpoint Replace the per-container HTTP fan-out in useScanProgress with a single POST to /api/v1/containers/scan-all. Previously, a 40-container inventory would fire 40 individual scan requests — each its own cycle — producing up to 40 notification emails when digest mode was active. Now one POST triggers one server-side cycle, one cycle-complete event, and one digest email. Progress tracking shifts from return-value polling to SSE: on receiving the 202 response the composable sets total = scheduledCount and subscribes to dd:sse-scan-completed CustomEvents (dispatched by AppLayout from the existing dd:scan-completed SSE channel) to increment done on each container finish. The cycleId from the 202 response is stored in a new currentCycleId ref so downstream UI can correlate events if needed. Rate-limit handling: a 429 from the bulk endpoint surfaces immediately as a thrown ApiError — no retry loop. The caller is responsible for showing a toast. AbortSignal threads through the POST fetch and the SSE-wait promise; cancelScan() aborts both, leaving progress at whatever count was reached. Tests updated: old per-container mock suite replaced with bulk-endpoint mocks + SSE event dispatch helpers. New cases cover happy path (N SSE events → done=N), empty inventory (scheduledCount=0 completes immediately), 429 rate limit (throws, no loop), abort mid-POST, abort after partial SSE progress, pre-aborted signal path, and singleton state sharing. service/container.spec.ts extended with scanAllContainersApi tests covering success, signal threading, 429, error detail parsing, and JSON parse failure.
Adds ProjectLink.vue that renders a clickable link to a container's source project (github.com/gitlab.com/other) when the OCI source-repo label or dd.source.repo label is present. Wired into container detail panels (side tab, full-page overview/tabs) and the grouped list card. Uses the sourceRepo field already surfaced by the release-notes enrichment pipeline; no backend changes required. Closes: #295
Add CHANGELOG entry under rc.10 Added section and append the feature to the v1.5.0 row of the README roadmap table. Feature shipped in d3f3ad8.
Adds formatAbsoluteTime helper and v-tooltip.top binding to the Watchers
view next-run column (table, card grid, detail panel). The primary
display remains the relative countdown ("in 14m"); the absolute local
timestamp now shows on hover, matching the existing Last-seen / Created
pattern elsewhere in the UI.
Closes: #288
Adds CHANGELOG and README entries for the dual-display tooltip improvement on the Watchers view next-run column, shipped in 6310b60.
…214) Rewrites the 5 deprecation banners (legacy env vars, legacy API paths, curl healthcheck, legacy hash, OIDC HTTP discovery) to include the concrete migration action inline and a "View migration guide" link pointing at the relevant anchored section of /docs/deprecations. Adds deep-link anchors to the deprecations docs page so each banner jumps directly to the right migration instructions instead of the page root. Defaults AnnouncementBanner's linkLabel to "View migration guide". Closes: #214
Adds CHANGELOG and README entries for the deprecation-banner rewrite shipped in 54fadee: inline migration paths + "View migration guide" deep-links across all 5 banners, plus 7 new anchored sections on the deprecations docs page.
- **Drop crowded header actions.** The pair "Mark all read" + "Clear all" in the dropdown header has been removed. Header now carries only the "Notifications" title, matching the GitHub / Linear / Slack consensus. - **Per-row dismiss affordance.** Each entry grows an ✕ icon button that hover-reveals on desktop and stays visible on touch devices (`@media (pointer: coarse)`). Dismissed entry IDs persist to localStorage under `dd-bell-dismissed-ids`; the audit log is unaffected because the bell is a client-side read model. - **Split footer.** "Mark all as read" and "Open audit log" share the footer as equal-weight actions. "Mark all as read" only shows when there are unread items; "Open audit log" is always visible. The old "View all" label is replaced with the more descriptive "Open audit log". - **Themeable zebra stripes.** New `--dd-zebra-stripe` token derived via `color-mix(in srgb, var(--dd-bg-card) 92%, var(--dd-text) 8%)` adapts automatically to every theme. The previous `--dd-bg-inset` fallback was visually identical to `--dd-bg-card` on GitHub Dark, GitHub Light, and Ayu Light, which is what reporter flagged. Closes discussion #267.
`test/qa-compose.demo.yml` is a local QA-only overlay that injects env vars and healthcheck overrides to exercise deprecation banners during manual QA. It has no production value and should not land in git.
…/footer Follow-up to 9bccd45 based on live review. The borders separating header / body / footer and the divider between the two footer buttons were all reading as distracting "lines" on the dropdown. Replaced with a background-color contrast instead: - Header, footer: now render on `var(--dd-bg-sidebar)`, which is the per-theme chrome color (noticeably darker than `--dd-bg-card` on dark themes, slightly off-white on light themes) — so they recede visually from the body rows without needing a rule line. - Footer buttons: bumped from `dd-text-secondary` to `dd-text` with `hover:dd-text-primary` so the split actions are more readable against the new chrome bg. - Header label: `dd-text-secondary` instead of `dd-text-muted` for the same reason. - Removed: `border-bottom` on header, `border-top` on footer, and the 1px vertical `w-px` divider between the two footer buttons.
Discussion #267 asked for a bulk Clear action alongside the per-row ✕ dismiss. The notification dropdown header now grows a small "Clear" button in the top-right that dismisses every visible entry at once (same local-only mechanism as the per-row ✕ — does not touch audit history). The button hides when there are no entries to clear.
Mechanical update — adds securitymode: 'simple' to the configurationValid fixtures across all trigger providers (apprise, command, docker, googlechat, gotify, ifttt, kafka, matrix, mattermost, mqtt, ntfy, pushover, slack, smtp, teams, telegram) so they align with the SECURITYMODE config field introduced by the v1.5.0 security-digest feature. No product-code behavior change.
Covers the remaining branches across the v1.5.0 security-digest and bulk scan-all feature set: - Trigger.test.ts (+437): security-alert-digest callback registration, fallback paths when container lookup or summary are missing, overlapping-cycle buffering, batch-failure warn logging, cycle-complete time fallback. - api/container.test.ts (+96) and api/container/bulk-security.test.ts (+35): POST /containers/scan-all cycleId emission, filtered subsets, client-disconnect aborts, rate-limit path. - agent/AgentClient.test.ts (+16): security-scan-cycle-complete SSE forwarding, cycleId thread-through on remote security-alert events. - security/scheduler.test.ts (+11): scheduled-scan notification flag gating and zero-alert cycle emission.
…es again (#305) Restore rc.8 behavior: Hide Pinned hides every container whose tag is a specific/pinned version, regardless of whether an update is pending. #293 had made Hide Pinned pass pinned rows through when they had a `newTag`. That conflated two different needs: "declutter infra pins" and "surface actionable pins I'm watching." Users combining Hide Pinned with Has Update suddenly saw their pinned rows again, which is exactly the opposite of what Hide Pinned means to them. The pin-to-wait-out-a-regression scenario from #293 is now solved the simpler way: uncheck Hide Pinned when you want to see pinned containers with pending updates. Filter semantics stay predictable. Tests in hide-pinned.spec.ts, DashboardView.spec.ts, and useDashboardComputed.spec.ts flip their assertions to match the restored behavior (pinned-with-update rows are now hidden again, totals + breakdown buckets reflect only the non-pinned rows).
Close #282 follow-up (rc.9 regression). `458030b7` split the digest channel into its own `'update-available-digest'` NotificationEventKind so batch and digest dedup are independent. That part was correct. The same commit also taught `seedNotificationHistoryFromStore` to seed the new digest kind from store state at init, which is wrong: a digest-history entry semantically means "a digest email was sent for this hash", and seeding it from "update existed in the store at startup" is a different claim entirely. For an operator upgrading from rc.7/rc.8 (where batch history and store state could diverge after any transient SMTP failure), seeding locked that divergence into the brand-new digest channel on first init. On every subsequent scan, `shouldHandleDigestContainerReport` matched the seeded hash, silently short-circuited, the digest buffer stayed empty, and the morning cron logged `nothing to send` — exactly the behavior the reporter saw on rc.9. Fix: - Remove the `update-available-digest` entry from `kindsToSeed`. The digest history is now populated exclusively by a successful `flushUpdateDigestBuffer`, which is the only code path where "a digest email was sent" can be truthfully recorded. - Keep the batch seed (`update-available`) intact — still correct, still prevents spurious re-batch after restart. - Leave security-digest seeding untouched: security-alert-digest is populated by `flushDigestBuffer(security-alert-digest)` and not currently read as a dedup key, so removing it is out-of-scope for this fix. Trade-off: the first cron after a restart or upgrade sends a catch-up digest covering everything currently pending. This is consistent with the "periodic summary" semantics of digest mode and is the correct failure mode for a bounded in-memory buffer that gets re-populated each scan. Tests: - Flip the existing `seedNotificationHistoryFromStore seeds both ...` assertion so it now asserts the digest channel is NOT seeded. - Add a regression test that proves the reporter's scenario is fixed: container exists in store at init, first scan cycle emits the same container unchanged, verify it lands in the digest buffer.
`GET /api/agents` used to do `O(agents × containers)` work per request: - `storeContainer.getContainers()` cloned the full collection. - `groupContainersByAgent` built a per-agent `Map<string, Container[]>`. - For every agent row, `getAgentContainerStats` ran two `.filter()` passes via `getContainerStatusSummary` (running, updatesAvailable) plus a `new Set(containers.map(...))` for the image fingerprint count. On the reporter's setup (3 agents, 60+ containers per #301) that's 540+ per-row predicate evaluations and multiple intermediate arrays per request. The endpoint is called by the Dashboard, Hosts, and Agents views so the cost compounds across the three pages begunfx called out as slow in rc.9. This commit replaces the entire pattern with a single pass: - `buildStatsByAgent` pre-allocates one counter bucket per agent, then iterates `storeContainer.getContainersRaw({})` once. Each row increments its agent's `total` / `running` / `updatesAvailable` and adds its image fingerprint to that bucket's Set. - `getAgentsList` maps agents to response rows using the pre-computed bucket — no filter fan-out, no per-agent subset arrays. - `getContainersRaw` replaces `getContainers` because agent stats never need the runtime-env redaction pass and we don't want to pay for it here. Removed the now-dead `getAgentContainerStats` and `groupContainersByAgent` helpers and dropped the `getContainerStatusSummary` import from this module (still used by `/api/container` and `agent/api/event` which aren't hot enough to need the same refactor). Tests: - Swapped `getContainers` → `getContainersRaw` in the test mock; existing expectations unchanged because the response shape is identical. - Added a 10-agent × 60-container regression that asserts the store is hit exactly once and every agent's stats are correct regardless of agent count — the structural guard against the `O(agents × containers)` pattern coming back. Refs: #301 (rc.9 multi-endpoint slowdown hotspot 2 of 4)
GET /api/watchers and /api/watchers/:type/:name now compute per-watcher container totals, running/stopped/updatesAvailable counts, and distinct image fingerprints in a single pass over the raw container store and attach them as `metadata.containers` + `metadata.images`. Reuses the buildContainerStatsByKey helper that /api/agents now shares, so both endpoints walk the container collection exactly once per request instead of O(keys × containers). ServersView and WatchersView previously fired a parallel GET /api/containers to compute these same counts on the client — that call is gone, along with countContainersByWatcher / countImagesByWatcher and the WatchersView containerCounts ref. First render of the Hosts and Watchers pages is now one round-trip per view instead of two. Agent-backed watchers keep their existing fallback-then-refresh path (getWatcher RPC merges remote metadata), with the local stats fields preserved across the remote-metadata merge so the UI sees watcher totals regardless of whether the agent is reachable. Backend: 25 watcher.test.ts + 11 container-summary.test.ts assertions, including populated-store regression coverage and fingerprint fallback (image.id → image.name → container.id) tests. UI: ServersView + WatchersView specs updated to provide the new metadata shape; one regression test added per view asserting the counts flow end-to-end from payload to rendered row.
AgentsView used to iterate every connected agent in onMounted and fire a parallel GET /api/agent/:name/log/entries?tail=50 for each one. The entries were silently cached and only surfaced if the operator opened that agent's detail panel and switched to the Logs tab — effectively a precomputed view that almost nobody looks at, scaled with agent count. Remove the eager fan-out. The existing watch() on [agentDetailTab, selectedAgent.name] already triggers fetchAgentLogs the first time the Logs tab is selected for a given agent, so lazy loading is a no-op change for the detail-panel UX. First render of the Agents page now issues one GET /api/agents and nothing else. Called out as the third of four rc.9 Agents-page hotspots in #301 reporter feedback. Tests: replaced the broken "fetched only for connected agents" eager assertion with two lazy-fetch assertions — one that logs are NOT fetched on mount, and one that logs ARE fetched when the Logs tab is selected in the detail panel.
) GET /api/watchers used to do one HTTP RPC per agent-backed watcher on every UI page load — `resolveWatcherItem` awaited `agentClient.getWatcher(type, name)` for each registered watcher, then Promise.all'd the bunch. For the reporter's topology (Synology LAN, 3 agent-backed watchers) that's 3 serialized WAN-ish RTTs on every Dashboard / Hosts / Watchers render. Replace the per-request fan-out with a push-on-change cache on the controller: - Extend WatcherSnapshotEventPayload to carry watcher.configuration and watcher.metadata. Docker.watch() now populates both from maskConfiguration() and getMetadata() right before emitting the snapshot event (lastRunAt is set inline so the payload reflects the cycle that just ran). - Add watcherSnapshotCache on AgentClient. Seed from the handshake's existing GET /api/watchers response on connect/reconnect, refresh on every dd:watcher-snapshot event. updateWatcherSnapshotCache merges instead of overwriting so partial events don't clobber seeded values. - resolveWatcherItem reads agentClient.getWatcherSnapshot(type, name) synchronously. If cache is cold (agent not yet handshaked), returns the local fallback — same behavior as when an agent is offline. No protocol version bump needed — older agents that emit the pre-rc.11 payload (no watcher.configuration / watcher.metadata) fall through to the cache's existing configuration/metadata via the merge path. Tests: - 6 new AgentClient cache tests covering handshake seed, SSE update, partial-merge semantics, and cold-cache undefined lookup. - watcher.test.ts: getWatcher mocks replaced with getWatcherSnapshot (now a synchronous Map lookup), RPC-failure test repurposed to exercise the empty-cache fallback. - Docker.watch.test.ts: emitWatcherSnapshot assertion extended to match the new configuration+metadata fields on the watcher block. Closes the last of 4 rc.9 perf hotspots called out by begunfx. Fixes: #301
Reproducible synthetic benchmark that models the rc.9 reporter's Synology LAN topology (3 agents × 5 watchers × 4 containers, 30ms simulated LAN RTT per HTTP RPC) and compares the before/after wall clock for each of the four hotspot fixes: - GET /api/watchers: 31ms → 0.02ms (watcher state cached on the controller, seeded from handshake + refreshed via dd:watcher-snapshot) - GET /api/agents stats: 0.06ms → 0.006ms (single-pass reducer replaces O(agents × containers) filter fan-out) - AgentsView mount logs fetch: 31ms → 0ms (eager per-agent prefetch removed; logs load lazily on Logs-tab activation) - ServersView mount: 62ms → 31ms (redundant /api/containers call dropped; watcher metadata carries counts) Run with: node scripts/bench-301-watcher-api.mjs Not wired into CI — this is a one-off reference that can be deleted once the perf regression risk has aged out. Check in now so the numbers in #301 are reproducible by any maintainer.
…s filter (#299) - Extract ContainerUpdateDialog.vue as a standalone reusable component with v-model:containerId, confirm/cancel, keyboard nav, error display, and update-kind-aware confirm message - Security view: show "Update" action button inline on image rows with pending updates; single container opens dialog directly; multiple containers open a chooser popover to pick which instance; secondary "View in Containers" link navigates to Containers view filtered to the update containers - Containers view: accept ?containerIds=<csv> query param to pre-filter to specific containers, with a filter chip showing count and a dismiss button - useVulnerabilities: annotate ImageSummary with hasUpdate / containersWithUpdate cross-referencing the containers list - ContainersViewTemplateContext: expose filterContainerIds and clearContainerIdsFilter - Full test coverage: ContainerUpdateDialog.spec.ts (17 tests), useVulnerabilities cross-reference tests, ContainersView containerIds query tests, SecurityView update affordance tests - CHANGELOG and docs changelog updated Fixes: #299
…299) Closes the gap vs. Snyk/Dependabot/Docker Scout pattern by rendering the existing ReleaseNotesLink next to the inline Update action on the Security view (table row, card, and detail panel). ImageSummary now carries releaseNotes / releaseLink, pulled from the first container with release info in containersWithUpdate.
- Ignore invalid watcher descriptors when seeding the snapshot cache from handshake (null, non-string type, non-string name) - Skip cache updates on dd:watcher-snapshot events with non-string watcher type - Extract mergedMetadata in resolveWatcherItem so the partial-metadata merge keeps type narrowing
- bulk-security.test.ts: inline abortHandler declaration (single-assignment → const) - bench-301-watcher-api.mjs: split long rows.push() / console.log() calls across lines
e0f2293
Web components default to display:inline, so when notification-bell rows scrolled above the fold the icon's intrinsic box could collapse and the text column lost its reserved flex space. Pin width/height/display/flex via inline style so the slot stays reserved regardless of layout state.
The POST /containers/scan-all handler ran Trivy scans, emitted alerts, and broadcast completion — but never wrote scanResult back to the container. The Security page reads from container.security, so after a bulk cycle the UI stayed on "No vulnerability data yet" even though real CVEs were discovered. Mirror the single-container persistence path: call storeContainer.updateContainer with a merged security patch and populate the digest scan cache when a digest is available and the scan succeeded. Persistence and cache updates are wrapped so either failing does not abort the rest of the cycle.
Add a Fixed subsection to the 1.5.0-rc.10 entry covering the two fixes landed today (notification bell iconify-icon collapse on scroll, bulk security scan persistence to container.security.scan) and bump the section date to 2026-04-19.
ALARGECOMPANY
approved these changes
Apr 20, 2026
biggest-littlest
approved these changes
Apr 20, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
SECURITYMODE=digest(andbatch+digest) emits one severity-grouped summary per scan cycle instead of one notification per vulnerable container. NewPOST /api/v1/containers/scan-allscans the whole fleet server-side and emits a singlesecurity-scan-cycle-completewith stable UUID v7cycleId. The UI Scan All button now uses the bulk endpoint so a 40-container inventory produces one email instead of forty.--dd-zebra-stripecolor-mix()token keeps alternate rows legible on every stock theme.#legacy-env-vars,#legacy-labels,#legacy-trigger-prefix,#legacy-password-hashes,#curl-healthcheck-override,#oidc-http-discovery,#unversioned-api-paths.org.opencontainers.image.source,dd.source.repo, or GHCR-derived source URL is available.flushDigestBuffer/shouldHandleDigestContainerReportare now parameterized on event kind, so update-digest and security-digest share one implementation while preserving the rc.9 GMAIL: Batch and Digest Email Issues/Inconsistencies #282 dedup invariants.securitymode: 'simple'baseline across all trigger-provider test fixtures. 100% coverage thresholds hold.Full change list: see
CHANGELOG.md[1.5.0-rc.10]section.Test plan
useConstinbulk-security.test.ts:641— biome classifies let→const as unsafe auto-fix; fix queued as follow-up)