Skip to content

feat: image maturity indicator and image update indicators#181

Merged
kmendell merged 10 commits intomainfrom
feat/image-maturity
May 14, 2025
Merged

feat: image maturity indicator and image update indicators#181
kmendell merged 10 commits intomainfrom
feat/image-maturity

Conversation

@kmendell
Copy link
Member

@kmendell kmendell commented May 14, 2025

fixes: #180
fixes: #149

image image

Summary by CodeRabbit

  • New Features

    • Added the ability to check and display the maturity status of Docker images, including a "Check Updates" button and visual maturity indicators with tooltips in the image list.
    • Introduced a polling system to periodically check image maturity in the background.
    • Added a configurable image maturity threshold setting to application settings.
  • Enhancements

    • Improved image list with explicit "In Use" or "Unused" status badges.
    • Refined settings page inputs with enhanced form components for better usability and validation feedback.
  • Bug Fixes

    • Improved error handling and validation for registry access and settings updates.
  • Chores

    • Refactored internal logic for image maturity checks and error handling for robustness and maintainability.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented May 14, 2025

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

📝 Walkthrough

Walkthrough

This update introduces a comprehensive image maturity checking and indicator system. It adds backend logic for polling and checking Docker image maturity, exposes maturity data via new API endpoints, and updates the UI to display maturity status and update availability for each image. Supporting types, error handling, and store management are also added or extended.

Changes

Files / Areas Changed Change Summary
src/lib/services/docker/image-service.ts, src/lib/services/api/image-api-service.ts, src/lib/services/settings-service.ts, src/lib/types/docker/image.type.ts, src/lib/types/settings.type.ts, src/lib/types/errors.type.ts Adds image maturity polling and checking logic, new API methods, error classes, and supporting types. Introduces initMaturityPollingScheduler, checkImageMaturity, and related helpers. Updates settings and types to include a maturity threshold.
src/lib/stores/maturity-store.ts, src/lib/stores/settings-store.ts Adds a new Svelte store for image maturity state and extends the settings store to include the maturity threshold.
src/lib/types/form.type.ts Adds a generic FormInput<T> type for form state handling.
src/lib/components/form/form-input.svelte Introduces a reusable form input Svelte component supporting labels, descriptions, help/warning/error texts, and input binding.
src/lib/components/ui/tooltip/index.ts, src/lib/components/ui/tooltip/tooltip-content.svelte Adds a tooltip UI component system for displaying detailed maturity info and other tooltips.
src/routes/api/images/[id]/maturity/+server.ts, src/routes/api/settings/+server.ts Adds an API route for image maturity checks and enforces validation for the maturity threshold in settings.
src/routes/images/+page.server.ts, src/routes/images/+page.svelte Updates the images page to fetch and display maturity info, including a new column with visual maturity indicators and tooltips. Adds a "Check Updates" button to trigger maturity checks for all images.
src/routes/images/pull-image-dialog.svelte Removes unused registry label and debug code.
src/routes/settings/tabs/app-settings.svelte Refactors settings form to use the new FormInput component and local state, including the image maturity threshold setting.
src/hooks.server.ts Adds initialization of the maturity polling scheduler to the server startup sequence.

Assessment against linked issues

Objective Addressed Explanation
Indicate in the UI when a new image version is available (#180)
Show image maturity indicator in the UI, including maturity status, update availability, and relevant details (#149)
Add backend logic for checking/polling image maturity, including error handling and threshold configuration (#149)
Add settings for configuring image maturity threshold (#149)

Possibly related PRs

  • feat: auto update containers and stacks #83: Modifies src/hooks.server.ts to concurrently initialize services at server startup, similar to the addition of the maturity polling scheduler in this PR. Both PRs change the initialization sequence for backend services.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@kmendell kmendell marked this pull request as ready for review May 14, 2025 05:31
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🔭 Outside diff range comments (1)
src/lib/types/errors.type.ts (1)

3-16: ⚠️ Potential issue

Enum diverges from legacy errors.ts – expect import collisions

ApiErrorCode here re-defines several constants that already exist in src/lib/types/errors.ts (e.g. VALIDATION_ERROR, NOT_FOUND) but also renames UNAUTHENTICATEDUNAUTHORIZED.
Because other modules still import from both errors.ts and this new file (see image-service.ts), the two enums will never be === equal and switch/if comparisons will silently fail.

Action:

  1. Keep a single source-of-truth for the enum (delete the obsolete file or re-export from one place).
  2. Preserve the original constant names to avoid breaking API contracts (UNAUTHENTICATED vs UNAUTHORIZED).
  3. Add a unit-test that verifies Object.values(ApiErrorCode) contains no duplicates.
🧹 Nitpick comments (10)
src/routes/api/settings/+server.ts (1)

118-129: LGTM: Added proper validation for the maturity threshold setting.

The validation ensures the maturity threshold is between 1 and 365 days, which is a reasonable range. The parsing and error handling are consistent with the validation patterns used for other settings.

However, there's no code to restart the maturity polling scheduler when settings are updated, unlike how the auto-update scheduler is handled in lines 161-164.

Consider adding maturity polling scheduler reinitialization on settings update:

// Re-initialize services that depend on settings
await tryCatch(initComposeService());
await tryCatch(stopAutoUpdateScheduler());
if (newSettingsData.autoUpdate) {
  await tryCatch(initAutoUpdateScheduler());
}
+// Reset maturity polling scheduler to apply new settings
+await tryCatch(initMaturityPollingScheduler());
src/lib/types/docker/image.type.ts (1)

13-18: LGTM: Well-defined ImageMaturity interface.

The interface properly represents maturity-related metadata for Docker images with clear property types. The use of a union type for the status field provides good type safety.

Consider adding documentation comments to improve code maintainability:

+/**
+ * Represents maturity and update information for a Docker image
+ */
export interface ImageMaturity {
  version: string;
  date: string;
  status: 'Matured' | 'Not Matured' | 'Unknown';
  updatesAvailable: boolean;
}
src/routes/settings/tabs/app-settings.svelte (1)

24-38: $state initialisation runs before the store is populated

Because the $settingsStore subscription happens asynchronously,
$settingsStore.stacksDirectory may still be undefined when these states are
created, causing the input to flash empty before the $effect copies the real
settings.

To avoid the flicker initialise from data.settings (already available) or
lazy-initialise inside the $effect once the store is ready.

src/lib/components/form/form-input.svelte (1)

34-34: id generation is not guaranteed unique or safe

label?.toLowerCase().replace(/ /g, '-') collides when:

  • Two different forms use the same label.
  • The label contains characters that are not valid in an HTML id.
  • The label is changed dynamically.

Generate a stable unique ID once instead:

-const id = label?.toLowerCase().replace(/ /g, '-');
+const id = (label
+	? label.toLowerCase().replace(/[^a-z0-9-_:.]/g, '-')
+	: '') + '-' + crypto.randomUUID();
src/lib/stores/maturity-store.ts (2)

37-42: Race-condition risk when several maturity checks run in parallel

If two concurrent checks overlap:

  1. setMaturityChecking(true)isChecking = true
  2. First check finishes → setMaturityChecking(false) sets
    lastChecked = new Date()
  3. Second check still running, but isChecking is already false; when it
    finishes it will overwrite lastChecked with an older timestamp.

Consider using a counter or a request-ID stack so isChecking only resets when
all running checks are done, or pass the timestamp as an argument.


18-34: updateImageMaturity mutates through a shallow copy – prefer immutable merge

Although you copy maturityData, the outer state object is still reused. To
avoid accidental reactive-comparison issues adopt a fully-immutable update:

return {
-	...state,
-	maturityData: newData
+	...state,
+	maturityData: { ...state.maturityData, ...(maturity ? { [imageId]: maturity } : {}) }
};

Or, if deleting, build the new object with Object.fromEntries.

src/routes/images/+page.svelte (2)

426-439: Icon dimensions break layout & accessibility

Icons are rendered in a w-4 h-4 container but use w-10 h-10, overflowing the cell and confusing screen-reader hit-targets.

-<CircleCheck class="w-10 h-10 text-green-500" … />
+<CircleCheck class="w-4 h-4 text-green-500" … />

Repeat for the other two variants.


584-635: Generated CSS arrows leak global scope

Selectors like :global(.tooltip-with-arrow[data-side='left']::before) are fine, but the un-scoped variables --popover and --border assume Tailwind/CM CSS variables exist everywhere.

If this component is consumed in isolation (e.g. storybook) the background may render transparent.
Consider fallback colors:

background-color: var(--popover, #fff);
border: 1px solid var(--border, #d1d5db);
src/lib/services/docker/image-service.ts (2)

67-86: Sequential await loop is IO-bound hot-spot

runMaturityChecks awaits each checkImageMaturity then sleeps 200 ms. On servers with hundreds of images this will run for minutes.

Reuse the concurrency pool approach suggested for the frontend, but keep a low limit (e.g. 5) to respect registry rate limits. Logically identical update counts can then be aggregated after Promise.all.


399-474: Registry API fetch lacks timeout / abort handling

A slow or hung registry call can stall the whole maturity check. Consider an AbortController with a sensible timeout (10-15 s) and surface a RegistryApiTimeoutError.

const ctrl = new AbortController();
const t = setTimeout(() => ctrl.abort(), 15_000);
try {
  const res = await fetch(tagsUrl, { headers, signal: ctrl.signal });
  
} finally {
  clearTimeout(t);
}
🧰 Tools
🪛 Biome (1.9.4)

[error] 421-421: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5aa5f04 and e64f907.

📒 Files selected for processing (19)
  • src/hooks.server.ts (1 hunks)
  • src/lib/components/form/form-input.svelte (1 hunks)
  • src/lib/components/ui/tooltip/index.ts (1 hunks)
  • src/lib/components/ui/tooltip/tooltip-content.svelte (1 hunks)
  • src/lib/services/api/image-api-service.ts (1 hunks)
  • src/lib/services/docker/image-service.ts (4 hunks)
  • src/lib/services/settings-service.ts (1 hunks)
  • src/lib/stores/maturity-store.ts (1 hunks)
  • src/lib/stores/settings-store.ts (1 hunks)
  • src/lib/types/docker/image.type.ts (1 hunks)
  • src/lib/types/errors.type.ts (2 hunks)
  • src/lib/types/form.type.ts (1 hunks)
  • src/lib/types/settings.type.ts (1 hunks)
  • src/routes/api/images/[id]/maturity/+server.ts (1 hunks)
  • src/routes/api/settings/+server.ts (1 hunks)
  • src/routes/images/+page.server.ts (2 hunks)
  • src/routes/images/+page.svelte (7 hunks)
  • src/routes/images/pull-image-dialog.svelte (0 hunks)
  • src/routes/settings/tabs/app-settings.svelte (2 hunks)
💤 Files with no reviewable changes (1)
  • src/routes/images/pull-image-dialog.svelte
🧰 Additional context used
🧬 Code Graph Analysis (5)
src/routes/api/settings/+server.ts (2)
src/lib/types/errors.type.ts (1)
  • ApiErrorResponse (18-24)
src/lib/types/errors.ts (1)
  • ApiErrorCode (4-17)
src/hooks.server.ts (1)
src/lib/services/docker/image-service.ts (1)
  • initMaturityPollingScheduler (15-38)
src/routes/images/+page.server.ts (1)
src/lib/services/docker/image-service.ts (1)
  • checkImageMaturity (285-328)
src/lib/stores/maturity-store.ts (1)
src/lib/types/docker/image.type.ts (1)
  • ImageMaturity (13-18)
src/lib/types/errors.type.ts (1)
src/lib/types/errors.ts (1)
  • ApiErrorCode (4-17)
🪛 Biome (1.9.4)
src/lib/services/docker/image-service.ts

[error] 421-421: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


[error] 605-605: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


[error] 626-626: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


[error] 707-707: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

🔇 Additional comments (21)
src/lib/types/form.type.ts (1)

1-7: Well-structured generic form input type

This is a clean implementation of a generic form input state type that covers the essential aspects of form input management: value, validation state, interaction tracking, and error handling. The generic approach allows for type-safe usage across different input types.

The separation of error (string | null) and errors (string[]) provides flexibility for both simple and complex validation scenarios.

src/lib/services/settings-service.ts (1)

26-27: Adding maturity threshold setting with appropriate default value

The addition of maturityThresholdDays: 30 as a default setting is appropriate for implementing the image maturity indicator feature. The 30-day threshold is a reasonable default that balances caution with practicality for container image maturity assessment.

src/lib/types/settings.type.ts (1)

48-48: Proper type definition for maturity threshold

Adding the maturityThresholdDays property to the Settings interface maintains type safety throughout the application. Making it a required property (not optional) is the right choice since it has a default value in the settings service.

src/lib/services/api/image-api-service.ts (1)

20-23: Consistent API method implementation for maturity checking

The checkMaturity method follows the established patterns in the class, making a POST request to the appropriate endpoint and returning the response data. This implementation maintains consistency with other API methods in the service.

src/lib/stores/settings-store.ts (1)

42-43: LGTM: Added new maturity threshold setting.

The new maturityThresholdDays property with a default value of 30 is a good addition to support the image maturity checking feature.

src/hooks.server.ts (2)

9-9: LGTM: Import for new maturity polling service.

Good addition of the import for the maturity polling scheduler initialization function.


16-16: LGTM: Initialized maturity polling scheduler.

Correctly added the maturity polling scheduler initialization to the parallel startup sequence alongside other critical services.

src/lib/types/docker/image.type.ts (1)

22-22: LGTM: Properly enhanced image info with maturity data.

Good addition of the optional maturity property to the EnhancedImageInfo type to support the new image maturity feature.

src/routes/images/+page.server.ts (3)

2-2: Import updated to include maturity checking function.

The import statement now correctly includes the checkImageMaturity function that will be used to enhance image data.


21-31: Well-implemented maturity check with proper error handling.

The maturity check implementation is robust as it:

  • Only checks images with valid repository and tag values
  • Uses try-catch to prevent maturity errors from affecting the main flow
  • Logs errors appropriately
  • Maintains backward compatibility with existing code

This approach ensures the application remains stable even if maturity checks fail for some images.


35-36: Clean integration of maturity data with existing image properties.

The maturity information is properly added to the enhanced image object alongside the existing inUse property without disrupting the original structure.

src/routes/api/images/[id]/maturity/+server.ts (4)

1-7: Well-structured imports for the new API endpoint.

The imports include all necessary dependencies for JSON responses, type definitions, error handling, and the core maturity checking functionality.


8-19: Strong parameter validation with appropriate error responses.

The endpoint properly validates the image ID parameter and returns a well-structured 400 Bad Request response when validation fails, following API best practices.


20-32: Comprehensive error handling with detailed logging.

The implementation:

  • Uses the tryCatch utility for consistent error handling
  • Logs errors with appropriate context
  • Extracts Docker-specific error messages for better user experience
  • Returns typed error responses with status code 500
  • Includes detailed error information for debugging

This approach ensures robust error handling and good developer/user experience.


34-38: Clean success response format.

The success response follows a consistent pattern with a success flag and the result data, making it easy for clients to process.

src/lib/components/ui/tooltip/tooltip-content.svelte (2)

1-11: Well-structured component with typed props.

The component correctly:

  • Imports the necessary Tooltip primitive
  • Uses utility function for class merging
  • Defines typed props with sensible defaults
  • Implements bindable reference
  • Uses Svelte's modern syntax for props and bindings

This provides a type-safe and flexible foundation for tooltip content.


13-21: Comprehensive styling with Tailwind CSS.

The component wraps the primitive with extensive Tailwind CSS classes that handle:

  • Background and text colors
  • Animations for showing/hiding
  • Positioning transitions based on tooltip side
  • Visual styling (border, shadow, padding)
  • Proper z-index and overflow handling

This ensures consistent tooltip styling throughout the application while still allowing customization via the class prop.

src/lib/components/ui/tooltip/index.ts (2)

1-7: Clean extraction of tooltip components.

The code correctly imports the tooltip primitive and local content component, then extracts individual components from the primitive for easier consumption.


8-18: Well-structured exports with alternative naming.

The export structure:

  • Provides the base components (Root, Trigger, Content, Provider)
  • Also exports them with more descriptive names (Tooltip, TooltipTrigger, etc.)
  • Uses clear commenting to separate the export groups

This gives consumers flexibility in how they import and use the components while maintaining a consistent interface.

src/lib/services/docker/image-service.ts (2)

90-98: Browser-only window reference in server context

runMaturityChecks executes on the server yet references window behind a typeof guard to dispatch an event. This block will never execute in Node, so consumers on the client will not receive an update.

If you intend to push results to the UI, emit a custom event over a websocket or use SvelteKit’s event-stream endpoint instead.


817-848: findNewerVersionsOfSameTag – inaccurate when tags contain multiple numeric chunks

Current regex grabs only the first numeric group, so 2023.04.01-rc1 turns into 2023.
Consider using semver parsing or at least splitting on non-digit boundaries to compare full versions.

@kmendell kmendell merged commit 2eff068 into main May 14, 2025
5 checks passed
@kmendell kmendell deleted the feat/image-maturity branch May 14, 2025 13:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

⚡️ Feature: indication when new image is available Feature: Image maturity indicator

1 participant