Skip to content

Adding container application type#380

Closed
liatb-rh wants to merge 2 commits into
flightctl:mainfrom
liatb-rh:EDM-2324-container
Closed

Adding container application type#380
liatb-rh wants to merge 2 commits into
flightctl:mainfrom
liatb-rh:EDM-2324-container

Conversation

@liatb-rh

@liatb-rh liatb-rh commented Nov 18, 2025

Copy link
Copy Markdown

Vibe coding - Adding support for container application type
Screenshot 2025-11-18 at 17 15 26

Summary by CodeRabbit

Release Notes

  • New Features

    • Added Mock Service Worker integration for offline development—run the app with fully mocked API endpoints without requiring a backend
    • Introduced container-based application format with configurable ports, volumes, CPU and memory limits
    • Added dev:mock npm script for convenient mock-enabled development
  • Documentation

    • Updated documentation with instructions for running the standalone UI offline using mock data

…EDM-2451)\n\n- Add appType to application form model\n- Preserve appType from API and emit on save\n- Add Compose/Quadlet selector in Applications step
@coderabbitai

coderabbitai Bot commented Nov 18, 2025

Copy link
Copy Markdown

Walkthrough

Introduces Mock Service Worker (MSW) infrastructure for offline standalone UI development with mocked API endpoints, adds container-based inline application support with quadlet format conversion, extends types and validation for the new container specification, and creates a new ApplicationContainerForm UI component for managing container settings.

Changes

Cohort / File(s) Summary
MSW Setup & Infrastructure
apps/standalone/package.json, apps/standalone/public/mockServiceWorker.js, apps/standalone/src/index.tsx, apps/standalone/src/mocks/browser.ts, apps/standalone/src/mocks/handlers.ts, apps/standalone/webpack.config.ts, package.json
Adds MSW devDependency and dev:mock npm script. Implements MSW service worker with lifecycle management (install/activate), message-based client communication (KEEPALIVE, INTEGRITY_CHECK, MOCK_ACTIVATE), and fetch interception. Conditionally initializes MSW in the app entry point when USE_MSW flag is set. Defines mock handlers for auth, device, and fleet endpoints with sample data. Webpack config updates include MSW worker script copying and dynamic window.API_PORT handling based on USE_MSW flag.
Container & Quadlet Type System
libs/ui-components/src/types/deviceSpec.ts
Adds appType field to AppBase, introduces inlineFormat ('compose' | 'quadlet' | 'container'), and extends InlineAppForm with container object containing image, ports, mounts, memory, cpuQuota, and cpuWeight fields.
Validation for Container Specs
libs/ui-components/src/components/form/validations.ts
Adds dedicated validation branch for container-based inline applications with schema for image, ports, mounts, and resource limits. Updates unique-app-ids test signature to accept unknown type.
Device Spec Utilities
libs/ui-components/src/components/Device/EditDeviceWizard/deviceSpecUtils.ts
Extends toAPIApplication to generate quadlet-based systemd unit descriptions from container specs (Ports as PublishPort, Volumes as Volume, Memory/CPU). Propagates appType through getApplicationValues for both OCI_IMAGE and INLINE variants.
Container Form Component
libs/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationContainerForm.tsx
New React component for managing container settings via Formik, with fields for image, ports (add/remove FieldArray), volumes (add/remove FieldArray), memory, CPU quota, and CPU weight. Enforces appType consistency.
Inline Application UI Integration
libs/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationTemplates.tsx
Integrates ApplicationContainerForm, adds inlineFormat selector UI for choosing Compose/Quadlet/Container formats, updates form initialization to propagate appType, and conditionally renders ApplicationInlineForm or ApplicationContainerForm based on inlineFormat. Refactors PatternFly imports to granular dist-esm paths.
Documentation & Minor
README.md, libs/ui-components/src/components/Fleet/CreateFleet/CreateFleetWizard.tsx
Adds section on running standalone UI with MSW for offline development. Notes OCP plugin is development-only. Minor formatting adjustment to import statement.

Sequence Diagram(s)

sequenceDiagram
    participant App as App (index.tsx)
    participant MSW as MSW Worker
    participant Browser as Browser Network
    participant Handlers as Mock Handlers

    App->>App: Check USE_MSW env var
    alt USE_MSW = true
        App->>MSW: dynamicImport & worker.start()
        MSW->>Browser: Register Service Worker
        MSW->>MSW: Initialize with bypassUnhandledRequest
        App->>App: Render UI with API_PORT = undefined
        Note over App,Handlers: Network requests intercepted by MSW
        Browser->>MSW: Fetch request
        MSW->>Handlers: Route to mock handler
        Handlers->>MSW: Mock response
        MSW->>Browser: Return mock response
    else USE_MSW = false
        App->>App: Render UI with API_PORT = 3001
        Browser->>Browser: Connect to backend on :3001
    end
Loading
sequenceDiagram
    participant Form as ApplicationForm
    participant Utils as deviceSpecUtils
    participant Quadlet as Quadlet Generator

    Form->>Form: User selects container format
    Form->>Form: Populates container specs (image, ports, volumes, resources)
    Form->>Utils: toAPIApplication(containerApp)
    Utils->>Quadlet: Generate quadlet from container
    Quadlet->>Quadlet: Build systemd unit (PublishPort, Volume, Memory, CPU)
    Quadlet->>Utils: Return quadlet manifest content
    Utils->>Utils: Return ApplicationProviderSpec with appType=quadlet and inline file
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Areas requiring extra attention:

  • MSW Service Worker lifecycle (apps/standalone/public/mockServiceWorker.js): Message protocol, active client management, fetch interception, and response serialization logic
  • Quadlet conversion logic (libs/ui-components/src/components/Device/EditDeviceWizard/deviceSpecUtils.ts): Accuracy of systemd unit generation from container specs (ports mapping, volume handling)
  • Form validation schema for containers (libs/ui-components/src/components/form/validations.ts): Nested validation rules for ports and mounts
  • Webpack MSW configuration (apps/standalone/webpack.config.ts): Dynamic window.API_PORT handling and service worker script placement
  • ApplicationContainerForm integration (libs/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationTemplates.tsx): Correct initialization and conditional rendering based on inlineFormat

Possibly related PRs

Suggested reviewers

  • rawagner
  • celdrake
  • amalykhi

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ 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 'Adding container application type' accurately reflects the main change: introducing container application support with new types, UI components, handlers, and MSW setup.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

📝 Customizable high-level summaries are now available in beta!

You can now customize how CodeRabbit generates the high-level summary in your pull requests — including its content, structure, tone, and formatting.

  • Provide your own instructions using the high_level_summary_instructions setting.
  • Format the summary however you like (bullet lists, tables, multi-section layouts, contributor stats, etc.).
  • Use high_level_summary_in_walkthrough to move the summary from the description to the walkthrough section.

Example instruction:

"Divide the high-level summary into five sections:

  1. 📝 Description — Summarize the main change in 50–60 words, explaining what was done.
  2. 📓 References — List relevant issues, discussions, documentation, or related PRs.
  3. 📦 Dependencies & Requirements — Mention any new/updated dependencies, environment variable changes, or configuration updates.
  4. 📊 Contributor Summary — Include a Markdown table showing contributions:
    | Contributor | Lines Added | Lines Removed | Files Changed |
  5. ✔️ Additional Notes — Add any extra reviewer context.
    Keep each section concise (under 200 words) and use bullet or numbered lists for clarity."

Note: This feature is currently in beta for Pro-tier users, and pricing will be announced later.


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.

@liatb-rh liatb-rh changed the title Adding container application file Adding container application type Nov 18, 2025
@liatb-rh liatb-rh marked this pull request as draft November 18, 2025 15:51

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

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/Device/EditDeviceWizard/deviceSpecUtils.ts (1)

274-285: Ensure quadlet-form apps send the correct AppType.

If a user picks the “Quadlet” inline format in the UI, inlineFormat becomes 'quadlet' but app.appType still defaults to compose. This branch then emits AppTypeCompose, so the backend treats the submission as a compose app even though the payload contains quadlet content. The rendered unit will be malformed or rejected. Please derive the fallback app type from inlineFormat before defaulting.

-  return {
-    name: (app as InlineAppForm).name,
-    appType: (app.appType || AppType.AppTypeCompose),
-    inline: (app as InlineAppForm).files.map(
+  const inlineApp = app as InlineAppForm;
+  const derivedAppType =
+    inlineApp.inlineFormat === 'quadlet' ? AppType.AppTypeQuadlet : AppType.AppTypeCompose;
+
+  return {
+    name: inlineApp.name,
+    appType: inlineApp.appType || derivedAppType,
+    inline: inlineApp.files.map(
       (file): InlineApplicationFileFixed => ({
         path: file.path,
         content: file.content || '',
🧹 Nitpick comments (8)
libs/ui-components/src/components/Fleet/CreateFleet/CreateFleetWizard.tsx (1)

2-14: Remove trailing whitespace from import list.

Line 6 has trailing whitespace after the Bullseye, import that should be removed to align with standard formatting practices and avoid unnecessary whitespace in the codebase.

  import {
    Alert,
    Breadcrumb,
    BreadcrumbItem,
-   Bullseye, 
+   Bullseye,
    PageSection,
    PageSectionVariants,
    Spinner,
    Title,
    Wizard,
    WizardStep,
    WizardStepType,
  } from '@patternfly/react-core';
README.md (1)

50-61: Clarify scope of mocked endpoints in docs

The new mock-mode section reads well and matches the npm run dev:mock workflow. Since handlers.ts also mocks devices and auth/login endpoints, consider mentioning those (or using wording like “fleets, repositories, devices, and auth”) so readers understand how complete the mock coverage is.

apps/standalone/src/index.tsx (1)

39-51: Error handling for MSW initialization aligns with MSW best practices

MSW recommends awaiting worker.start() and handling rejections, making the suggested try/catch/finally approach sound and well-grounded. The current code leaves the app vulnerable to a blank UI if MSW fails to initialize.

The refactored approach—catching initialization failures and ensuring render runs in a finally block—is a recommended practice and follows MSW's own guidance for handling worker-registration failures in dev bootstraps.

apps/standalone/src/mocks/handlers.ts (5)

1-8: Scope the ESLint disable and any usage to keep this file safer to evolve

Using a file-wide disable for all the unsafe/any rules is understandable for mocks, but it also removes guardrails if more logic is added here later. Consider either:

  • Limiting the disable to the fixture-heavy section (e.g., around getMockDevices only), or
  • Tightening types so most of the file compiles without any (reserving as any for truly awkward fields).

That keeps the MSW handlers themselves type-checked while still letting you be pragmatic with the fixtures.


21-139: Enrich device fixtures and reduce any to better exercise the new container spec

getMockDevices provides good coverage of basic device fields, but:

  • Most of spec and status is typed as any, so changes in @flightctl/types.Device (especially around applications) won’t be caught here.
  • Given this PR is adding container/quadlet support, none of the sample applications actually exercise those new fields; they are just { name, image }.

To make the standalone+MSW setup more valuable for the new flows, consider:

  • Typing the fixture more narrowly (e.g., a strongly‑typed const mockDevices: Device[] = [...] or at least Partial<Device> with fewer as any casts), and
  • Adding at least one device whose application spec uses the new container/quadlet fields so the ApplicationContainerForm can be wired/tested against realistic data.

Based on learnings


164-219: Consider using MSW path params instead of regex + manual URL parsing

The device handlers currently combine new RegExp(...) with explicit URL parsing and pathname.match(...). MSW supports Express-style paths with named params, which can make these routes simpler and avoid regex complexity. For example:

- http.get(new RegExp(`${apiBase}/devices/([\\w-]+)$`), ({ request }) => {
-   const url = new URL(request.url);
-   const nameMatch = url.pathname.match(/\\/devices\\/([\\w-]+)$/);
-   const name = nameMatch ? nameMatch[1] : '';
+ http.get(`${apiBase}/devices/:name`, ({ params }) => {
+   const name = params.name as string;

Similarly, list and other routes could be expressed as:

http.get(`${apiBase}/devices`, /* ... */);
http.get(`${apiBase}/devices/:name/lastseen`, /* ... */);
http.patch(`${apiBase}/devices/:name`, /* ... */);
http.put(`${apiBase}/devices/:name/decommission`, /* ... */);
http.delete(`${apiBase}/devices/:name`, /* ... */);

This keeps the handlers easier to maintain and naturally supports a wider range of valid names (not just [\w-]).

Please double-check against the MSW version in this repo to confirm the :param path syntax is supported as shown.


206-219: Optional: maintain simple in‑memory state for device mutations

The PATCH, decommission (PUT), and DELETE handlers all return success but don’t adjust any shared device state, so subsequent GETs will still reflect the original fixtures. That’s fine for “happy-path only” mocks, but it can limit how much of the edit/decommission flows you can realistically exercise in the standalone app.

If you find yourself testing those flows end‑to‑end, consider:

  • Hoisting const mockDevices = getMockDevices() to module scope, and
  • Updating/removing entries in that array in the PATCH/PUT/DELETE handlers before returning.

This remains in-memory and dev-only, but better mirrors real backend behavior.


221-261: Echo more of the POSTed fleet body in the create‑fleet handler

The create‑fleet endpoint currently does:

const base = basicFleets[0];
const created: Fleet = {
  ...base,
  metadata: { ...base.metadata, name: body?.metadata?.name ?? base.metadata.name },
};

This means:

  • The response ignores most of the POSTed spec/status fields, and
  • New fleets are essentially clones of basicFleets[0] with only the name changed.

For standalone dev it can be more useful if the response reflects what the UI just submitted. For example:

- const base = basicFleets[0];
- const created: Fleet = {
-   ...base,
-   metadata: { ...base.metadata, name: body?.metadata?.name ?? base.metadata.name },
- };
+ const base = basicFleets[0];
+ const created: Fleet = {
+   ...base,
+   ...body,
+   metadata: {
+     ...base.metadata,
+     ...body.metadata,
+   },
+ };

That keeps any required defaults from base but lets the POST body drive the returned spec, making it easier to validate new fields in the UI.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4fa79ec and 14b2f5d.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (14)
  • README.md (1 hunks)
  • apps/standalone/package.json (3 hunks)
  • apps/standalone/public/mockServiceWorker.js (1 hunks)
  • apps/standalone/src/index.tsx (1 hunks)
  • apps/standalone/src/mocks/browser.ts (1 hunks)
  • apps/standalone/src/mocks/handlers.ts (1 hunks)
  • apps/standalone/webpack.config.ts (3 hunks)
  • libs/ui-components/src/components/Device/EditDeviceWizard/deviceSpecUtils.ts (3 hunks)
  • libs/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationContainerForm.tsx (1 hunks)
  • libs/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationTemplates.tsx (5 hunks)
  • libs/ui-components/src/components/Fleet/CreateFleet/CreateFleetWizard.tsx (1 hunks)
  • libs/ui-components/src/components/form/validations.ts (2 hunks)
  • libs/ui-components/src/types/deviceSpec.ts (3 hunks)
  • package.json (1 hunks)
🧰 Additional context used
🧠 Learnings (11)
📓 Common learnings
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.
📚 Learning: 2025-03-20T12:37:36.986Z
Learnt from: celdrake
Repo: flightctl/flightctl-ui PR: 240
File: libs/ui-components/src/types/deviceSpec.ts:0-0
Timestamp: 2025-03-20T12:37:36.986Z
Learning: In the FlightCtl UI project, `name` is required for inline applications but optional for image applications. This is enforced at the type level in the `InlineAppForm` type.

Applied to files:

  • libs/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationContainerForm.tsx
  • libs/ui-components/src/components/form/validations.ts
  • libs/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationTemplates.tsx
  • libs/ui-components/src/types/deviceSpec.ts
  • libs/ui-components/src/components/Device/EditDeviceWizard/deviceSpecUtils.ts
📚 Learning: 2025-03-19T08:55:03.335Z
Learnt from: celdrake
Repo: flightctl/flightctl-ui PR: 240
File: libs/ui-components/src/components/Device/DeviceDetails/DeviceApplications.tsx:44-50
Timestamp: 2025-03-19T08:55:03.335Z
Learning: In FlightCtl, the name field is required for Inline applications but optional for Image-based applications. This requirement is enforced by validation logic even though TypeScript type definitions might not explicitly show this distinction.

Applied to files:

  • libs/ui-components/src/components/form/validations.ts
  • libs/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationTemplates.tsx
  • libs/ui-components/src/types/deviceSpec.ts
📚 Learning: 2025-05-26T11:41:40.796Z
Learnt from: rawagner
Repo: flightctl/flightctl-ui PR: 300
File: libs/ui-components/src/components/form/validations.ts:459-461
Timestamp: 2025-05-26T11:41:40.796Z
Learning: In TypeScript validation contexts, when working with properly typed form data like `BatchForm`, required array fields (without `?` marker) are guaranteed to be defined arrays, making defensive checks for undefined unnecessary in those specific contexts.

Applied to files:

  • libs/ui-components/src/components/form/validations.ts
📚 Learning: 2025-02-17T08:52:50.747Z
Learnt from: celdrake
Repo: flightctl/flightctl-ui PR: 218
File: libs/ui-components/src/components/Device/DeviceDetails/DeviceDetailsPage.tsx:109-111
Timestamp: 2025-02-17T08:52:50.747Z
Learning: When wrapping a Button component (like `deleteAction`) inside another Button in React/PatternFly, use `component="a"` on the outer Button to avoid accessibility issues caused by button nesting. Include `tabIndex={0}` to ensure proper keyboard navigation.

Applied to files:

  • libs/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationTemplates.tsx
📚 Learning: 2025-03-19T08:57:16.233Z
Learnt from: celdrake
Repo: flightctl/flightctl-ui PR: 240
File: libs/ui-components/src/components/Device/DeviceDetails/DeviceApplications.tsx:44-50
Timestamp: 2025-03-19T08:57:16.233Z
Learning: In FlightCtl, the name field is required for Inline applications but optional for Image-based applications. This requirement is enforced by both frontend validation logic and backend API constraints, ensuring that no inline application can exist in the system without a name field.

Applied to files:

  • libs/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationTemplates.tsx
  • libs/ui-components/src/types/deviceSpec.ts
📚 Learning: 2025-10-29T16:47:29.614Z
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.

Applied to files:

  • libs/ui-components/src/types/deviceSpec.ts
  • libs/ui-components/src/components/Device/EditDeviceWizard/deviceSpecUtils.ts
📚 Learning: 2025-10-10T11:35:04.458Z
Learnt from: celdrake
Repo: flightctl/flightctl-ui PR: 0
File: :0-0
Timestamp: 2025-10-10T11:35:04.458Z
Learning: In the flightctl-ui repository, TypeScript types are generated using hey-api/openapi-ts and output to a single index.ts file (previously used openapi-typescript-codegen with one file per type under /libs/types/models).

Applied to files:

  • libs/ui-components/src/types/deviceSpec.ts
📚 Learning: 2025-03-20T12:34:33.199Z
Learnt from: celdrake
Repo: flightctl/flightctl-ui PR: 240
File: libs/types/models/FileSpec.ts:8-8
Timestamp: 2025-03-20T12:34:33.199Z
Learning: Files in the types directory are autogenerated from the OpenAPI spec in the flightctl project and should not be directly modified.

Applied to files:

  • libs/ui-components/src/types/deviceSpec.ts
📚 Learning: 2025-03-12T09:09:44.139Z
Learnt from: celdrake
Repo: flightctl/flightctl-ui PR: 227
File: libs/ui-components/src/components/Device/EditDeviceWizard/EditDeviceWizard.tsx:87-87
Timestamp: 2025-03-12T09:09:44.139Z
Learning: The `getUpdatePolicyValues` function in libs/ui-components/src/components/Fleet/CreateFleet/fleetSpecUtils.ts is designed to handle undefined input through its optional parameter definition and consistent use of optional chaining.

Applied to files:

  • libs/ui-components/src/components/Fleet/CreateFleet/CreateFleetWizard.tsx
📚 Learning: 2025-03-12T09:09:44.139Z
Learnt from: celdrake
Repo: flightctl/flightctl-ui PR: 227
File: libs/ui-components/src/components/Device/EditDeviceWizard/EditDeviceWizard.tsx:87-87
Timestamp: 2025-03-12T09:09:44.139Z
Learning: The `getUpdatePolicyValues` function in libs/ui-components/src/components/Fleet/CreateFleet/fleetSpecUtils.ts is designed to safely handle undefined values for the updatePolicy parameter.

Applied to files:

  • libs/ui-components/src/components/Fleet/CreateFleet/CreateFleetWizard.tsx
🧬 Code graph analysis (7)
apps/standalone/src/mocks/browser.ts (1)
apps/standalone/src/mocks/handlers.ts (1)
  • handlers (141-262)
libs/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationContainerForm.tsx (2)
libs/ui-components/src/types/deviceSpec.ts (1)
  • InlineAppForm (60-74)
libs/ui-components/src/hooks/useTranslation.ts (1)
  • useTranslation (5-8)
libs/ui-components/src/components/form/validations.ts (1)
libs/ui-components/src/types/deviceSpec.ts (2)
  • InlineAppForm (60-74)
  • AppForm (97-97)
libs/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationTemplates.tsx (1)
libs/ui-components/src/types/deviceSpec.ts (3)
  • AppForm (97-97)
  • isInlineAppForm (107-107)
  • isImageAppForm (106-106)
libs/ui-components/src/types/deviceSpec.ts (1)
libs/types/index.ts (1)
  • AppType (15-15)
apps/standalone/src/index.tsx (1)
apps/standalone/src/mocks/browser.ts (1)
  • worker (4-4)
libs/ui-components/src/components/Device/EditDeviceWizard/deviceSpecUtils.ts (1)
libs/ui-components/src/types/deviceSpec.ts (1)
  • InlineAppForm (60-74)
🪛 ast-grep (0.40.0)
apps/standalone/src/mocks/handlers.ts

[warning] 164-164: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(${apiBase}/devices(\\?.*)?$)
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html

(regexp-from-variable)


[warning] 191-191: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(${apiBase}/devices/([\\w-]+)$)
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html

(regexp-from-variable)


[warning] 201-201: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(${apiBase}/devices/([\\w-]+)/lastseen$)
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html

(regexp-from-variable)


[warning] 206-206: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(${apiBase}/devices/([\\w-]+)$)
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html

(regexp-from-variable)


[warning] 211-211: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(${apiBase}/devices/([\\w-]+)/decommission$)
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html

(regexp-from-variable)


[warning] 216-216: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(${apiBase}/devices/([\\w-]+)$)
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html

(regexp-from-variable)


[warning] 221-221: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(${apiBase}/fleets(\\?.*)?$)
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html

(regexp-from-variable)


[warning] 226-226: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(${apiBase}/fleets/([\\w-]+)$)
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html

(regexp-from-variable)


[warning] 253-253: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(${apiBase}/repositories/([\\w-]+)$)
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html

(regexp-from-variable)

🔇 Additional comments (4)
package.json (1)

21-21: Root dev:mock script wiring looks good

The new dev:mock script cleanly delegates to the standalone workspace and matches the README instructions; no issues spotted.

apps/standalone/src/mocks/browser.ts (1)

1-4: MSW browser worker setup is correct and follows MSW v2 best practices

The setupWorker(...handlers) pattern with import from 'msw/browser' is confirmed as the recommended approach in MSW v2. Code matches idiomatic usage; no issues identified.

apps/standalone/webpack.config.ts (1)

29-37: Fix CopyPlugin to copy the correct MSW worker file instead of the client library

The verification confirms your concern: the CopyPlugin at lines 168–177 copies msw/browser.js (the client-side library used by setupWorker) rather than the actual service worker. Since devServer.static serves dist before public, the dist-copied file will shadow the committed public/mockServiceWorker.js, causing the browser to load the wrong asset and break MSW functionality.

Solution:

Replace the CopyPlugin to copy the real worker from public:

    new CopyPlugin({
      patterns: [
        {
-         from: path.resolve(__dirname, '../../node_modules/msw/browser.js'),
+         from: path.resolve(__dirname, 'public/mockServiceWorker.js'),
          to: 'mockServiceWorker.js',
-         noErrorOnMissing: true,
        },
      ],
    }),

Alternatively, remove the CopyPlugin entirely if you rely on serving public/mockServiceWorker.js directly via the static config.

Regarding the API_PORT define (lines 220–231), the suggested refinement is valid for consistency:

-    defines['window.API_PORT'] = JSON.stringify(process.env.API_PORT) || '3001';
+    defines['window.API_PORT'] = JSON.stringify(process.env.API_PORT || '3001');

Likely an incorrect or invalid review comment.

apps/standalone/src/mocks/handlers.ts (1)

9-16: Reusable list response helper looks good

buildListResponse<T> cleanly standardizes list payloads (apiVersion/kind/metadata/items) and keeps the handlers DRY where it’s used. No changes needed here.

Comment on lines +14 to 15
"dev:mock": "USE_MSW=true npm run dev",
"dev:kind": ". ./scripts/setup_env.sh && npm run dev",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

Align msw dependency with generated worker version

The dev:mock script and msw.workerDirectory config look good and line up with the new mock flow.

One thing to double‑check: apps/standalone/public/mockServiceWorker.js declares a PACKAGE_VERSION of 2.12.1, while this package pins msw to ^2.4.1. MSW uses an integrity check between the client library and the worker, so having different versions can cause warnings and, in worst cases, protocol mismatches.

To keep things in sync, consider either:

  • Bumping the devDependency to match the worker’s version (for example, to "msw": "^2.12.x" based on the worker file), and reinstalling; or
  • Pinning msw to your desired version and regenerating the worker with the matching CLI (npx msw@<version> init public --save).

That way the client and worker stay version-aligned.

Also applies to: 24-24, 63-68


🏁 Script executed:

#!/bin/bash
# Show the version constant in the worker and the msw devDependency side-by-side.

echo "Worker version constant:"
rg -n "PACKAGE_VERSION" apps/standalone/public/mockServiceWorker.js -n || echo "No PACKAGE_VERSION found"

echo
echo "msw devDependency in apps/standalone/package.json:"
rg -n '"msw"' apps/standalone/package.json -n || echo "No msw devDependency found"

Length of output: 196


Align msw dependency version with generated worker

The dev:mock script and msw.workerDirectory config are correctly set up.

However, verification confirms a version mismatch: the worker at apps/standalone/public/mockServiceWorker.js (line 10) declares PACKAGE_VERSION = '2.12.1', while apps/standalone/package.json (line 24) pins "msw": "^2.4.1". MSW's integrity check between client and worker can flag or fail on version mismatches.

Resolve by either:

  • Updating the devDependency to "msw": "^2.12.x" and reinstalling, or
  • Pinning to your desired version and regenerating the worker: npx msw@<version> init public --save
🤖 Prompt for AI Agents
In apps/standalone/package.json around lines 14-15 (and note line 24 where "msw"
is pinned) and apps/standalone/public/mockServiceWorker.js (line 10 showing
PACKAGE_VERSION = '2.12.1'), the MSW package version in package.json mismatches
the generated worker; update package.json to use a matching MSW version (e.g.,
set "msw": "^2.12.x") and run a reinstall (npm install/yarn) so lockfile
updates, or alternatively pin package.json to your desired version and
regenerate the worker with npx msw@<version> init public --save; finally commit
the updated package.json, lockfile, and the regenerated
public/mockServiceWorker.js.

Comment on lines +1 to +349
/* eslint-disable */
/* tslint:disable */

/**
* Mock Service Worker.
* @see https://github.com/mswjs/msw
* - Please do NOT modify this file.
*/

const PACKAGE_VERSION = '2.12.1'
const INTEGRITY_CHECKSUM = '4db4a41e972cec1b64cc569c66952d82'
const IS_MOCKED_RESPONSE = Symbol('isMockedResponse')
const activeClientIds = new Set()

addEventListener('install', function () {
self.skipWaiting()
})

addEventListener('activate', function (event) {
event.waitUntil(self.clients.claim())
})

addEventListener('message', async function (event) {
const clientId = Reflect.get(event.source || {}, 'id')

if (!clientId || !self.clients) {
return
}

const client = await self.clients.get(clientId)

if (!client) {
return
}

const allClients = await self.clients.matchAll({
type: 'window',
})

switch (event.data) {
case 'KEEPALIVE_REQUEST': {
sendToClient(client, {
type: 'KEEPALIVE_RESPONSE',
})
break
}

case 'INTEGRITY_CHECK_REQUEST': {
sendToClient(client, {
type: 'INTEGRITY_CHECK_RESPONSE',
payload: {
packageVersion: PACKAGE_VERSION,
checksum: INTEGRITY_CHECKSUM,
},
})
break
}

case 'MOCK_ACTIVATE': {
activeClientIds.add(clientId)

sendToClient(client, {
type: 'MOCKING_ENABLED',
payload: {
client: {
id: client.id,
frameType: client.frameType,
},
},
})
break
}

case 'CLIENT_CLOSED': {
activeClientIds.delete(clientId)

const remainingClients = allClients.filter((client) => {
return client.id !== clientId
})

// Unregister itself when there are no more clients
if (remainingClients.length === 0) {
self.registration.unregister()
}

break
}
}
})

addEventListener('fetch', function (event) {
const requestInterceptedAt = Date.now()

// Bypass navigation requests.
if (event.request.mode === 'navigate') {
return
}

// Opening the DevTools triggers the "only-if-cached" request
// that cannot be handled by the worker. Bypass such requests.
if (
event.request.cache === 'only-if-cached' &&
event.request.mode !== 'same-origin'
) {
return
}

// Bypass all requests when there are no active clients.
// Prevents the self-unregistered worked from handling requests
// after it's been terminated (still remains active until the next reload).
if (activeClientIds.size === 0) {
return
}

const requestId = crypto.randomUUID()
event.respondWith(handleRequest(event, requestId, requestInterceptedAt))
})

/**
* @param {FetchEvent} event
* @param {string} requestId
* @param {number} requestInterceptedAt
*/
async function handleRequest(event, requestId, requestInterceptedAt) {
const client = await resolveMainClient(event)
const requestCloneForEvents = event.request.clone()
const response = await getResponse(
event,
client,
requestId,
requestInterceptedAt,
)

// Send back the response clone for the "response:*" life-cycle events.
// Ensure MSW is active and ready to handle the message, otherwise
// this message will pend indefinitely.
if (client && activeClientIds.has(client.id)) {
const serializedRequest = await serializeRequest(requestCloneForEvents)

// Clone the response so both the client and the library could consume it.
const responseClone = response.clone()

sendToClient(
client,
{
type: 'RESPONSE',
payload: {
isMockedResponse: IS_MOCKED_RESPONSE in response,
request: {
id: requestId,
...serializedRequest,
},
response: {
type: responseClone.type,
status: responseClone.status,
statusText: responseClone.statusText,
headers: Object.fromEntries(responseClone.headers.entries()),
body: responseClone.body,
},
},
},
responseClone.body ? [serializedRequest.body, responseClone.body] : [],
)
}

return response
}

/**
* Resolve the main client for the given event.
* Client that issues a request doesn't necessarily equal the client
* that registered the worker. It's with the latter the worker should
* communicate with during the response resolving phase.
* @param {FetchEvent} event
* @returns {Promise<Client | undefined>}
*/
async function resolveMainClient(event) {
const client = await self.clients.get(event.clientId)

if (activeClientIds.has(event.clientId)) {
return client
}

if (client?.frameType === 'top-level') {
return client
}

const allClients = await self.clients.matchAll({
type: 'window',
})

return allClients
.filter((client) => {
// Get only those clients that are currently visible.
return client.visibilityState === 'visible'
})
.find((client) => {
// Find the client ID that's recorded in the
// set of clients that have registered the worker.
return activeClientIds.has(client.id)
})
}

/**
* @param {FetchEvent} event
* @param {Client | undefined} client
* @param {string} requestId
* @param {number} requestInterceptedAt
* @returns {Promise<Response>}
*/
async function getResponse(event, client, requestId, requestInterceptedAt) {
// Clone the request because it might've been already used
// (i.e. its body has been read and sent to the client).
const requestClone = event.request.clone()

function passthrough() {
// Cast the request headers to a new Headers instance
// so the headers can be manipulated with.
const headers = new Headers(requestClone.headers)

// Remove the "accept" header value that marked this request as passthrough.
// This prevents request alteration and also keeps it compliant with the
// user-defined CORS policies.
const acceptHeader = headers.get('accept')
if (acceptHeader) {
const values = acceptHeader.split(',').map((value) => value.trim())
const filteredValues = values.filter(
(value) => value !== 'msw/passthrough',
)

if (filteredValues.length > 0) {
headers.set('accept', filteredValues.join(', '))
} else {
headers.delete('accept')
}
}

return fetch(requestClone, { headers })
}

// Bypass mocking when the client is not active.
if (!client) {
return passthrough()
}

// Bypass initial page load requests (i.e. static assets).
// The absence of the immediate/parent client in the map of the active clients
// means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet
// and is not ready to handle requests.
if (!activeClientIds.has(client.id)) {
return passthrough()
}

// Notify the client that a request has been intercepted.
const serializedRequest = await serializeRequest(event.request)
const clientMessage = await sendToClient(
client,
{
type: 'REQUEST',
payload: {
id: requestId,
interceptedAt: requestInterceptedAt,
...serializedRequest,
},
},
[serializedRequest.body],
)

switch (clientMessage.type) {
case 'MOCK_RESPONSE': {
return respondWithMock(clientMessage.data)
}

case 'PASSTHROUGH': {
return passthrough()
}
}

return passthrough()
}

/**
* @param {Client} client
* @param {any} message
* @param {Array<Transferable>} transferrables
* @returns {Promise<any>}
*/
function sendToClient(client, message, transferrables = []) {
return new Promise((resolve, reject) => {
const channel = new MessageChannel()

channel.port1.onmessage = (event) => {
if (event.data && event.data.error) {
return reject(event.data.error)
}

resolve(event.data)
}

client.postMessage(message, [
channel.port2,
...transferrables.filter(Boolean),
])
})
}

/**
* @param {Response} response
* @returns {Response}
*/
function respondWithMock(response) {
// Setting response status code to 0 is a no-op.
// However, when responding with a "Response.error()", the produced Response
// instance will have status code set to 0. Since it's not possible to create
// a Response instance with status code 0, handle that use-case separately.
if (response.status === 0) {
return Response.error()
}

const mockedResponse = new Response(response.body, response)

Reflect.defineProperty(mockedResponse, IS_MOCKED_RESPONSE, {
value: true,
enumerable: true,
})

return mockedResponse
}

/**
* @param {Request} request
*/
async function serializeRequest(request) {
return {
url: request.url,
mode: request.mode,
method: request.method,
headers: Object.fromEntries(request.headers.entries()),
cache: request.cache,
credentials: request.credentials,
destination: request.destination,
integrity: request.integrity,
redirect: request.redirect,
referrer: request.referrer,
referrerPolicy: request.referrerPolicy,
body: await request.arrayBuffer(),
keepalive: request.keepalive,
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

Treat this MSW worker file as the single source of truth

This looks like the standard mockServiceWorker.js generated by the MSW CLI (with integrity checks, message protocol, etc.), and it’s already annotated as “do NOT modify”.

Given that, it’s best to:

  • Keep this file entirely generated (regenerate via MSW CLI when upgrading msw), and
  • Ensure your bundling config (e.g., webpack.config.ts) copies this file when needed, rather than pulling in other MSW artifacts that might diverge from it.

That will keep the client and worker behavior consistent across environments.


🌐 Web query:

Check MSW's documentation on how `mockServiceWorker.js` is generated and kept in sync with the installed `msw` version, and whether it should be committed vs copied from `node_modules`.

💡 Result:

Short answer: MSW provides an init CLI that copies the packaged worker into your public dir (npx msw init <PUBLIC_DIR> --save). The docs recommend committing mockServiceWorker.js to Git so everyone uses the same worker, but also note you may treat it as a generated artifact and generate it automatically (CI/install step) if you prefer. The client accepts any worker within the same major MSW version, but MSW still recommends keeping the copied worker up-to-date with the installed msw and performs an integrity check (you can configure msw.workerDirectory). [1][2][3]

Key doc points:

  • How it’s produced: run npx msw init <PUBLIC_DIR> to copy the worker from the msw package into your app. [1][2]
  • Commit vs generated: recommended to commit mockServiceWorker.js to Git for team consistency; alternative is to generate it automatically as part of project setup/CI. [1][3]
  • Version/sync: the client can use a worker within the same major version, but MSW recommends keeping it in sync and runs an integrity check that may surface an “outdated Service Worker” message; use msw.workerDirectory if needed. [1][3]

Sources:

  • MSW “Managing the worker” / generating & updating the worker. [1]
  • MSW “Browser integration” / init command. [2]
  • MSW FAQ (Should I commit the worker script?). [3]


Align worker file handling with MSW's recommended practice

MSW's recommended approach is to commit mockServiceWorker.js to Git for team consistency, though treating it as a generated artifact and auto-generating it during CI/install is an alternative. The file should not be manually edited (as noted in the existing comment).

For your setup:

  • Use npx msw init <PUBLIC_DIR> to copy the worker from the msw package when updating to a new MSW version, then commit the result.
  • MSW runs an integrity check and may surface an "outdated Service Worker" message if the worker drifts from the installed msw version, so keep them in sync.
  • Ensure your bundling config copies this committed worker as needed rather than pulling from elsewhere.
🤖 Prompt for AI Agents
apps/standalone/public/mockServiceWorker.js lines 1-349: The committed
mockServiceWorker.js is currently treated as a generated artifact but the review
requests aligning with MSW best practices; update your workflow so this file is
produced by running `npx msw init <PUBLIC_DIR>` from the project (or copy the
worker from the msw package) whenever MSW is upgraded, commit the resulting file
to source control, and ensure your bundler/static asset pipeline serves the
committed file from apps/standalone/public (do not modify its contents
manually).

const quadlet = lines.join('\n');
return {
name,
appType: AppType.AppTypeQuadlet,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This doesn't seem right.

The user knows there are several types of applications in the system.

We let them create a "container" application and we transform it to a quadlet application. So next time they want to edit it etc, they will see it as a quadlet application that maybe they are not familiar with.

And we'd be dealing with possible conversion issues that we're unaware of. We should respect what the user defined and store the application as a single container application.

name: app.name,
appType: AppType.AppTypeCompose,
inline: app.files.map(
name: (app as InlineAppForm).name,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Types need to be adjusted using npm run gen-types.

That might remove the need for some of the castings you're doing here, as there was an "Artifact" application type which was quickly dropped.

);
}
// ensure appType stays 'quadlet' for container format
if (app.inlineFormat === 'container' && app.appType !== ('quadlet' as unknown as InlineAppForm['appType'])) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Same as previous comment, we shouldn't be mixing two application types when they exist in the system independent to each other.

</FormGroup>
</SplitItem>
<SplitItem>
<FormGroup label={t('Protocol')}>

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Protocols are not defined in the spec flightctl/flightctl#1955

<TextField name={`${mName}.name`} isDisabled={isReadOnly} />
</FormGroup>
</SplitItem>
<SplitItem isFilled>

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

There are several types of application volumes, each has a different set of properties.

export type ApplicationVolume = ({
  /**
   * Unique name of the volume used within the application.
   */
  name: string;
} & (ImageVolumeProviderSpec | MountVolumeProviderSpec | ImageMountVolumeProviderSpec));


)}
</FieldArray>

<FormGroup label={t('Memory limit')}>

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

API only defines "cpu" and "memory" limits.

@celdrake

Copy link
Copy Markdown
Collaborator

Full implementation here #382

@celdrake celdrake closed this Nov 24, 2025
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