Skip to content

Conversation

@celdrake
Copy link
Collaborator

@celdrake celdrake commented Jan 27, 2026

  • Allows viewing the logs for Image Builds and Image Exports
  • Allows configuring a user + public SSH key to log in to the device
  • Adds a 4th export format (QCOW2 for OpenShift Virtualization)

Summary by CodeRabbit

  • New Features

    • SSH remote access: enable/disable remote access, set username and paste SSH public key; username shown where applicable.
    • New export format: "QCOW2 (Disk Container)"; export format list and labels are now dynamic and localized.
    • Real-time Image build & export logs UI with selectable sources, status indicators, auto-scroll/streaming, and "View logs" navigation.
  • Bug Fixes / Improvements

    • Longer streaming timeout for more reliable log streaming.
    • Expanded, reorganized and clarified localization strings (export, provisioning, remote access); export log availability notes added.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Jan 27, 2026

Warning

Rate limit exceeded

@celdrake has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 21 minutes and 3 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

Walkthrough

Adds SSH public-key remote access configuration to image builds, introduces a QCOW2 Disk Container export format, makes export formats data-driven, implements image build/export logs with SSE streaming and a client hook, updates related utilities/types/UI, and increases server write timeout for streaming.

Changes

Cohort / File(s) Summary
Translations
libs/i18n/locales/en/translation.json
Added remote-access keys (labels, descriptions), new export format and status/log keys (including QCOW2 Disk Container), and removed/relocated duplicate keys.
ImageBuild user config types
libs/types/imagebuilder/index.ts, libs/types/imagebuilder/models/ImageBuildUserConfiguration.ts, libs/types/imagebuilder/models/ImageBuildSpec.ts
Added ImageBuildUserConfiguration type (username, publickey), exported it, and added optional userConfiguration to ImageBuildSpec.
Export format enum
libs/types/imagebuilder/models/ExportFormatType.ts
Added enum member qcow2-disk-container.
Form types & form handling
libs/ui-components/src/components/ImageBuilds/CreateImageBuildWizard/types.ts, .../utils.ts
Extended form values with userConfiguration (including enabled), updated getInitialValues and getImageBuildResource to conditionally include userConfiguration, reordered export formats, and removed old export-status helper exports.
Wizard UI: Registration & Output
libs/ui-components/src/components/ImageBuilds/CreateImageBuildWizard/steps/RegistrationStep.tsx, .../OutputImageStep.tsx
RegistrationStep: added remote access toggle, username field, public-key upload and wiring. OutputImageStep: switched to data-driven export format cards via getAllExportFormats().
Wizard UI: Review
libs/ui-components/src/components/ImageBuilds/CreateImageBuildWizard/steps/ReviewStep.tsx
Updated calls to getExportFormatLabel to pass translation function t as first argument.
Image build details & logs UI
libs/ui-components/src/components/ImageBuilds/ImageBuildDetails/ImageBuildDetailsPage.tsx, .../ImageBuildDetailsTab.tsx, .../ImageBuildLogsTab.tsx
Added ImageBuildLogsTab and route wiring; ImageBuildDetailsTab shows remote access username and uses localized export labels; ImageBuildLogsTab implements source selection, status indicators, streaming/non-streaming views and badges.
Export cards & gallery
libs/ui-components/src/components/ImageBuilds/ImageExportCards.tsx, .../ImageBuildDetails/ImageBuildExportsGallery.tsx
Added QCOW2 Disk Container icon mapping, centralized export-status helpers, made formats dynamic via getAllExportFormats(), threaded imageBuildId into export actions and log navigation, and updated label generation to use t.
Image build utilities
libs/ui-components/src/utils/imageBuilds.ts
Added getAllExportFormats, changed getExportFormatLabel(t, format), updated descriptions, and added helpers: shouldHaveImageBuildLogs, isImageBuildComplete, shouldHaveImageExportLogs, isImageExportFailed, isImageExportCompleted.
Logs hook
libs/ui-components/src/components/ImageBuilds/useImageBuildLogs.ts
New LogResourceType enum and useImageBuildLogs hook: fetches logs, supports SSE streaming (reads stream, buffers SSE data: lines), manages loading/error/streaming state, and cleans up on unmount.
Backend server config
proxy/app.go
Increased HTTP server WriteTimeout from 15s to 15m to support long-lived streaming responses.

Sequence Diagram(s)

sequenceDiagram
    participant User as User/UI
    participant LogsTab as ImageBuildLogsTab
    participant Hook as useImageBuildLogs
    participant API as API Server
    participant Stream as Event Stream

    User->>LogsTab: Select log source (BUILD or EXPORT)
    LogsTab->>Hook: useImageBuildLogs(resourceId, resourceType, isActive)
    Hook->>API: GET /api/.../logs?resourceId=...&type=...&active=true
    API-->>Hook: 200 OK (Content-Type: text/event-stream)

    rect rgba(100,200,150,0.5)
    Note over Hook,Stream: SSE streaming (isActive = true)
    Hook->>Stream: create reader
    Stream-->>Hook: chunks (data: line\n)
    Hook->>Hook: buffer & parse SSE lines
    Hook-->>LogsTab: incremental log updates (isStreaming = true)
    end

    LogsTab->>User: render logs (auto-scroll while streaming)

    alt switch to completed/non-streaming
    LogsTab->>Hook: useImageBuildLogs(..., isActive=false)
    Hook->>API: GET /api/.../logs?active=false
    API-->>Hook: full log text
    Hook-->>LogsTab: final logs (isStreaming = false)
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the three main changes: adding image builder logs, user configuration, and an additional export format (QCOW2).

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


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

Comment @coderabbitai help to get the list of available commands and usage tips.

@celdrake celdrake force-pushed the EDM-3207-add-logs-and-user-config branch from a7fb448 to 0d56396 Compare January 27, 2026 10:21
Copy link

@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: 5

🤖 Fix all issues with AI agents
In
`@libs/ui-components/src/components/ImageBuilds/CreateImageBuildWizard/utils.ts`:
- Around line 154-159: The code currently sets spec.userConfiguration whenever
values.userConfiguration.username and .publickey are present, ignoring the
feature toggle; update the condition to also check the enable flag (e.g.,
values.userConfiguration?.enabled or the actual boolean property used) before
assigning to spec.userConfiguration so that SSH credentials are only included
when the userConfiguration feature is enabled; modify the block referencing
values.userConfiguration and spec.userConfiguration accordingly to require both
enabled === true and non-empty username/publickey.

In
`@libs/ui-components/src/components/ImageBuilds/ImageBuildDetails/ImageBuildLogsTab.tsx`:
- Around line 147-153: The effect in ImageBuildLogsTab currently only scrolls to
bottom when isStreaming is true, so when logs finish streaming the view can
remain at the bottom; update the React.useEffect that references logsRef, logs
and isStreaming so that when isStreaming is false (i.e., logs completed) you set
logsRef.current.scrollTop = 0 to reset to the top, and ensure the effect also
runs when the log source changes by adding the relevant source identifier (if
present) to the dependency list; locate the effect using the symbols
React.useEffect, logsRef, logs and isStreaming and change the conditional to
handle both branches (scrollHeight when streaming, scrollTop = 0 when not) and
include the source dependency if there is a prop like source/logSource.

In `@libs/ui-components/src/components/ImageBuilds/useImageBuildLogs.ts`:
- Around line 60-63: Old logs persist when resourceId changes because fetchLogs
appends to previous state via setLogs((prev) => ...) without clearing; update
fetchLogs to clear existing logs at start (e.g., call setLogs([]) or the empty
initial value used by your component) and reset related state
(setIsLoading(true), setError(null)) before starting the new fetch, and
additionally guard streaming appends by checking a currentResourceId/ref or
abort signal so only logs for the active resourceId are appended (refer to
fetchLogs, resourceId, setLogs, and any streaming append logic).
- Around line 15-22: getLogContent currently only recognizes lines starting with
"data: " (with a space) and thus ignores "data:" (no space) per the SSE spec;
update getLogContent to check for lines starting with "data:" (use
startsWith('data:')) and then extract the payload by slicing after the "data:"
prefix and, if present, remove a single leading space (e.g., trim only one
leading space or conditionally drop it) before pushing to logLines so both
"data:value" and "data: value" are handled.

In `@proxy/app.go`:
- Around line 97-101: The server's http.Server is setting WriteTimeout to 15 *
time.Minute which can abort long-running proxied streaming responses; update the
srv construction (the http.Server instance created in app.go, where Handler:
corsHandler(router) and Addr: config.BridgePort are set) to either set
WriteTimeout to 0 (disable the absolute cap) or read a configurable timeout
value (e.g., config.BridgeWriteTimeout or similar) and apply that so long-lived
streamed operations (ImageBuilder, FlightCtl, etc.) are not prematurely closed;
ensure the config key is added/parsed and default behavior remains safe if
unset.
🧹 Nitpick comments (4)
libs/ui-components/src/components/ImageBuilds/CreateImageBuildWizard/steps/RegistrationStep.tsx (2)

54-68: Consider batching setFieldValue calls to reduce re-renders.

Multiple setFieldValue calls in the disable branch (lines 64-66) will trigger multiple form re-renders. Consider using setValues or Formik's batch update if this causes performance issues.

♻️ Optional refactor using setValues
   const handleRemoteAccessToggle = (enabled: boolean) => {
     if (enabled) {
       // Ensure userConfiguration object exists
       if (!values.userConfiguration) {
         setFieldValue('userConfiguration', { username: '', publickey: '', enabled: true });
       } else {
         setFieldValue('userConfiguration.enabled', true);
       }
     } else {
       // Clear userConfiguration when disabled
-      setFieldValue('userConfiguration.username', '');
-      setFieldValue('userConfiguration.publickey', '');
-      setFieldValue('userConfiguration.enabled', false);
+      setFieldValue('userConfiguration', { username: '', publickey: '', enabled: false });
     }
   };

165-186: Public key helper text appears outside the conditional checkbox content.

The helper text at lines 182-184 is placed outside the CheckboxField children, meaning it will always be visible regardless of whether remote access is enabled. If this is intentional (to provide context before the user enables the feature), consider moving it above the checkbox or adding visual distinction. If it should only appear when enabled, move it inside the CheckboxField children.

         <CheckboxField
           name="userConfiguration.enabled"
           label={t('Provide an SSH public key to enable passwordless login once your image is deployed.')}
           onChangeCustom={handleRemoteAccessToggle}
         >
           <FormGroup label={t('Username')} fieldId="user-config-username">
             <TextField
               name="userConfiguration.username"
               aria-label={t('Username')}
               helperText={t('The username for the user account')}
             />
           </FormGroup>
-          <UploadField name="userConfiguration.publickey" label={t('Public key')} />
+          <UploadField 
+            name="userConfiguration.publickey" 
+            label={t('Public key')} 
+            helperText={t('Paste the content of an SSH public key you want to use to connect to the device.')}
+          />
         </CheckboxField>
-        <Content component="small">
-          {t('Paste the content of an SSH public key you want to use to connect to the device.')}
-        </Content>
libs/ui-components/src/components/ImageBuilds/ImageBuildDetails/ImageBuildLogsTab.tsx (1)

50-63: Add a default label fallback for unknown formats.
Line 50-63 returns undefined if a new format is added without updating this switch, which would render a blank label. A small fallback avoids that.

♻️ Suggested fallback
 const getExportFormatText = (t: TFunction, format?: ExportFormatType) => {
   switch (format) {
     case undefined:
       return t('Select format');
     case ExportFormatType.ExportFormatTypeVMDK:
       return t('Virtualization (VMDK)');
     case ExportFormatType.ExportFormatTypeQCOW2:
       return t('OpenStack/KVM (QCOW2)');
     case ExportFormatType.ExportFormatTypeQCOW2DiskContainer:
       return t('OpenShift Virtualization (QCOW2)');
     case ExportFormatType.ExportFormatTypeISO:
       return t('Metal installer (ISO)');
+    default:
+      return format ? format.toUpperCase() : t('Select format');
   }
 };
libs/ui-components/src/components/ImageBuilds/CreateImageBuildWizard/utils.ts (1)

33-35: Update the export format list comment.
Line 33-35 still lists only three formats, but QCOW2 Disk Container is now included.

📝 Comment update
-// Returns an array with one item per format (VMDK, QCOW2, ISO), where each item is either
+// Returns an array with one item per format (ISO, QCOW2 Disk Container, QCOW2, VMDK), where each item is either

Comment on lines +147 to +143
React.useEffect(() => {
// For live logs (when they are streaming), we auto-scroll to the bottom.
// For completed logs, we show the logs with scroll at the top
if (logsRef.current && logs && isStreaming) {
logsRef.current.scrollTop = logsRef.current.scrollHeight;
}
}, [logs, isStreaming]);
Copy link

@coderabbitai coderabbitai bot Jan 27, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Reset scroll position for non‑streaming logs.
Line 147-153 only scrolls to bottom when streaming; switching to completed logs can leave the view at the bottom despite the comment. Consider resetting to the top when isStreaming is false (and on source change).

🐛 Suggested adjustment
 React.useEffect(() => {
-  // For live logs (when they are streaming), we auto-scroll to the bottom.
-  // For completed logs, we show the logs with scroll at the top
-  if (logsRef.current && logs && isStreaming) {
-    logsRef.current.scrollTop = logsRef.current.scrollHeight;
-  }
-}, [logs, isStreaming]);
+  // For live logs (when they are streaming), we auto-scroll to the bottom.
+  // For completed logs, we show the logs with scroll at the top
+  if (!logsRef.current || !logs) {
+    return;
+  }
+  logsRef.current.scrollTop = isStreaming ? logsRef.current.scrollHeight : 0;
+}, [logs, isStreaming, selectedEntityId]);
🤖 Prompt for AI Agents
In
`@libs/ui-components/src/components/ImageBuilds/ImageBuildDetails/ImageBuildLogsTab.tsx`
around lines 147 - 153, The effect in ImageBuildLogsTab currently only scrolls
to bottom when isStreaming is true, so when logs finish streaming the view can
remain at the bottom; update the React.useEffect that references logsRef, logs
and isStreaming so that when isStreaming is false (i.e., logs completed) you set
logsRef.current.scrollTop = 0 to reset to the top, and ensure the effect also
runs when the log source changes by adding the relevant source identifier (if
present) to the dependency list; locate the effect using the symbols
React.useEffect, logsRef, logs and isStreaming and change the conditional to
handle both branches (scrollHeight when streaming, scrollTop = 0 when not) and
include the source dependency if there is a prop like source/logSource.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This would keep resetting the scroll to the top as the logs change for completed logs, which is not what we want.

Copy link

Choose a reason for hiding this comment

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

@celdrake You're absolutely right—resetting on every logs change would be disruptive for users trying to read completed logs.

I notice the comment says "For completed logs, we show the logs with scroll at the top," but the current implementation doesn't actually scroll to the top for completed logs. If you'd like completed logs to initially appear scrolled to the top (just when first loading or switching sources), you could track selectedEntityId in the dependencies instead of logs. But if the current behavior (letting users scroll freely through completed logs) is what you want, then the implementation is fine as-is—perhaps just update the comment to reflect that completed logs maintain user scroll position?

Either way, the current approach makes sense for user experience. 👍


🧠 Learnings used
Learnt from: celdrake
Repo: flightctl/flightctl-ui PR: 371
File: libs/types/models/AppType.ts:10-10
Timestamp: 2025-10-29T16:47:29.614Z
Learning: PR `#371` (flightctl/flightctl-ui) adds the AppTypeQuadlet enum member and related types as a preparatory change. Full implementation of quadlet application support in the UI will be added later, after backend support is available.

@celdrake celdrake force-pushed the EDM-3207-add-logs-and-user-config branch from 0d56396 to c6bb6a6 Compare January 27, 2026 14:20
Copy link

@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: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
libs/ui-components/src/components/ImageBuilds/ImageExportCards.tsx (1)

173-186: "View logs" button is missing an onClick handler.

The PR objective states "Adds UI capability to view logs for Image Builds and Image Exports", but this button at line 175 doesn't have an onClick handler and won't perform any action when clicked.

Proposed fix
 <FlexItem>
   {exists ? (
-    <Button variant="secondary">{t('View logs')}</Button>
+    <Button variant="secondary" onClick={() => onViewLogs?.(format)}>{t('View logs')}</Button>
   ) : (

You'll also need to add onViewLogs to the component props:

export type ImageExportFormatCardProps = {
  // ... existing props
  onViewLogs?: (format: ExportFormatType) => void;
};
🧹 Nitpick comments (2)
libs/ui-components/src/components/ImageBuilds/ImageBuildDetails/ImageBuildLogsTab.tsx (1)

97-99: Inconsistent return object structure.

The early return includes availableExportFormats which is not destructured at line 85. Consider returning hasExports: false for consistency with the normal return path.

🔧 Suggested fix
     // ImageExports can only exist once the ImageBuild is complete
     if (!isImageBuildComplete(imageBuild)) {
-      return { selectableEntities: entities, availableExportFormats: [] as ExportFormatType[], failedExportsCount: 0 };
+      return { selectableEntities: entities, hasExports: false, failedExportsCount: 0 };
     }
libs/ui-components/src/components/ImageBuilds/CreateImageBuildWizard/steps/RegistrationStep.tsx (1)

166-186: Consider placing the SSH key helper text inside the checkbox scope.

The helper text at lines 182-184 describing SSH public key paste instructions is always visible, even when remote access is disabled. This could be confusing since the instruction is only relevant when the feature is enabled.

🔧 Suggested adjustment

Move the helper text inside the CheckboxField children or use helperText prop on the UploadField instead:

           <UploadField name="userConfiguration.publickey" label={t('Public key')} />
+          <Content component="small">
+            {t('Paste the content of an SSH public key you want to use to connect to the device.')}
+          </Content>
         </CheckboxField>
-        <Content component="small">
-          {t('Paste the content of an SSH public key you want to use to connect to the device.')}
-        </Content>
       </FormSection>

Comment on lines 62 to 67
} else {
// Clear userConfiguration when disabled
setFieldValue('userConfiguration.username', '');
setFieldValue('userConfiguration.publickey', '');
setFieldValue('userConfiguration.enabled', false);
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

why do you clear it out ? we can leave the values, and just ignore them when enabled === false

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Fixed, now users can uncheck the checkbox. The data is kept but ignored.

Comment on lines 198 to 208
<Badge
isRead={false}
style={
{
'--pf-v6-c-badge--BackgroundColor':
'var(--pf-t--global--icon--color--status--danger--default, #c9190b)',
} as React.CSSProperties
}
>
{failedExportsCount}
</Badge>
Copy link
Collaborator

Choose a reason for hiding this comment

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

Dont we want to use Label instead ? which supports the error/failed state OOTB

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Actually this badge doesn't make sense anymore.
Initially the selector between ImageBuild/Exports were two buttons, and for export format there was an additional dropdown. The badge would show in the "Exports" button.

Now the design changed to a MenuList with two MenuGroup, and I see no badge. I'll remove it.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Modified

@celdrake celdrake force-pushed the EDM-3207-add-logs-and-user-config branch from 2d14a08 to f193f30 Compare January 28, 2026 08:18
@celdrake celdrake force-pushed the EDM-3207-add-logs-and-user-config branch 2 times, most recently from b663dc4 to 7cde0fa Compare January 28, 2026 08:23
@celdrake celdrake force-pushed the EDM-3207-add-logs-and-user-config branch from 7cde0fa to 29053d1 Compare January 28, 2026 08:26
@celdrake celdrake merged commit 7eac0f2 into flightctl:main Jan 28, 2026
6 checks passed
@celdrake celdrake deleted the EDM-3207-add-logs-and-user-config branch January 28, 2026 11:07
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.

2 participants