Compare commits

..

331 commits
v68 ... main

Author SHA1 Message Date
robojerk
080513ad6d did stuff 2025-08-26 11:15:33 -07:00
Michal Gold
7391652e17 Wizard: Replace deprecated innerRef with ref in RegionsSelect MenuToggle
Replace `innerRef` prop with standard React `ref` prop in MenuToggle component
2025-08-21 19:42:39 +00:00
Gianluca Zuccarelli
9a17373234 Hooks: extract auth.getUser to its own hook
This code was being called in multiple places and was causing issues
with the on-prem frontend. Extract the logic to a single hook and only
get the `userData` for the hosted frontend.
2025-08-21 16:12:09 +00:00
schutzbot
d7f844b8b6 Post release version bump
[skip ci]
2025-08-21 15:34:33 +00:00
Gianluca Zuccarelli
859b7cace8 Wizard: on-prem aws region in edit
The AWS region was getting reset when going into edit mode for a blueprint.
This was because the request wasn't being properly mapped back to the correct
state.
2025-08-21 13:45:26 +00:00
Gianluca Zuccarelli
3a83a14720 BlueprintCard: fix name truncation
This might be an issue with the pf6 truncate component. Since we're not
really using the popover, we can just use vanilla js to truncate the
string rather than use the Truncate component. We can match the
behaviour of the component by also splitting on 24 characters.

https://github.com/patternfly/patternfly-react/issues/11964
2025-08-21 13:44:58 +00:00
Anna Vítová
e61cb99f1b Launch: implement guidance for Azure (HMS-9003)
This commit adds launch modal for guiding users through launching an
Azure instance from their image. As the launch service will be decommissioned,
the flag shall be turned on, the code will later be cleaned up and the
Provisioning wizard removed.
2025-08-21 12:02:20 +00:00
Michal Gold
a5aa15cbcb Wizard: Resolve row reordering issue on selection and expansion
- Fix issue when clicking the expandable arrow or selecting a package checkbox in the Packages step it caused unexpected row reordering.
- Updated sorting logic to ensure that selecting a package with a specific stream groups all related module streams together at the top.
- Ensured that rows expand in place and selection does not affect row position.
- Add unit test as well
2025-08-21 10:17:06 +00:00
Gianluca Zuccarelli
44c3674072 devDeps: Bump msw from 2.10.4 to 2.10.5
This bumps msw from 2.10.4 to 2.10.5
2025-08-21 10:05:42 +00:00
Anna Vítová
4d783537fb Launch: implement guidance for Oracle (HMS-9004)
This commit adds launch modal for guiding users through launching a Oracle instance from their image. It provides a link to Oracle's cloud and a link for importing the image on the user's side.
2025-08-21 07:42:26 +00:00
Gianluca Zuccarelli
0b96c64c93 devDeps: Bump typescript deps
This bumps @typescript-eslint/eslint-plugin and
@typescript-eslint/parser from 8.40.0 to 8.40.0.
These need to be bumped in tandem.
2025-08-20 19:52:49 +00:00
dependabot[bot]
0d917c3cd8 build(deps-dev): bump @types/node from 24.1.0 to 24.3.0
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 24.1.0 to 24.3.0.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-version: 24.3.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-20 15:56:27 +00:00
Sanne Raymaekers
957700adcc .gitlab-ci.yml: switch to rhel-10.1 nightly 2025-08-20 12:32:43 +00:00
Sanne Raymaekers
fa0560ac4d playwright: wait until distro and arch have been initialized
On-prem the distro and architecture are set after the wizard has been
opened. This triggers a reload of the image types and makes the tests
very flaky.
2025-08-20 12:32:43 +00:00
Sanne Raymaekers
0e7f5d9e7b plans: add gating tests
This tmt[0] test runs the playwright tests as gating tests. Having the
gating tests upstream avoids duplication across fedora and centos
dist-git repositories, and running them upstream should keep them in
working order.

Only add x86_64 for now, the aarch runners seem to be a bit too slow.

[0]: https://tmt.readthedocs.io/en/stable/index.html
2025-08-20 12:32:43 +00:00
schutzbot
e0dd33fdc9 Post release version bump
[skip ci]
2025-08-20 08:35:04 +00:00
dependabot[bot]
b0393a5f4f build(deps-dev): bump typescript-eslint from 8.38.0 to 8.40.0
Bumps [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) from 8.38.0 to 8.40.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.40.0/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: typescript-eslint
  dependency-version: 8.40.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-19 14:46:01 +00:00
Anna Vítová
a9d2ba59a8 Launch: implement guidance for GCP (HMS-9004)
This commit adds launch modal for guiding users through launching a GCP
instance from their image. This commit also adds unique image name in
the command in the clipboard. That way, users can rebuild the image more
times without worrying about duplicate names. This guidance should be as
helpful to users as possible, so even if they are able to create their
own image name here, we chose it for them for the sake of simplicity.
2025-08-19 09:24:45 +00:00
Katarina Sieklova
af19251f17 build(deps): bump @redhat-cloud-services/frontend-components-notifications from 6.1.3 to 6.1.5 2025-08-18 17:55:45 +00:00
Anna Vítová
090544c333 Launch: implement guidance for AWS (HMS-9002)
This commit adds launch modal for guiding users through launching an AWS
instance from their image. As the launch service will be decommissioned,
the flag shall be turned on, the code will later be cleaned up and the
Provisioning wizard removed.
2025-08-18 15:57:58 +00:00
dependabot[bot]
4b188a0393 build(deps): bump @sentry/webpack-plugin from 4.1.0 to 4.1.1
Bumps [@sentry/webpack-plugin](https://github.com/getsentry/sentry-javascript-bundler-plugins) from 4.1.0 to 4.1.1.
- [Release notes](https://github.com/getsentry/sentry-javascript-bundler-plugins/releases)
- [Changelog](https://github.com/getsentry/sentry-javascript-bundler-plugins/blob/main/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-javascript-bundler-plugins/compare/4.1.0...4.1.1)

---
updated-dependencies:
- dependency-name: "@sentry/webpack-plugin"
  dependency-version: 4.1.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-18 10:09:28 +00:00
dependabot[bot]
63f55c7408 build(deps-dev): bump eslint from 9.32.0 to 9.33.0
Bumps [eslint](https://github.com/eslint/eslint) from 9.32.0 to 9.33.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v9.32.0...v9.33.0)

---
updated-dependencies:
- dependency-name: eslint
  dependency-version: 9.33.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-18 07:54:24 +00:00
dependabot[bot]
bc3288a83e build(deps-dev): bump eslint-plugin-prettier from 5.5.3 to 5.5.4
Bumps [eslint-plugin-prettier](https://github.com/prettier/eslint-plugin-prettier) from 5.5.3 to 5.5.4.
- [Release notes](https://github.com/prettier/eslint-plugin-prettier/releases)
- [Changelog](https://github.com/prettier/eslint-plugin-prettier/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prettier/eslint-plugin-prettier/compare/v5.5.3...v5.5.4)

---
updated-dependencies:
- dependency-name: eslint-plugin-prettier
  dependency-version: 5.5.4
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-18 07:54:05 +00:00
regexowl
3e5c5dca76 devDeps: Bump typescript deps
This bumps @typescript-eslint/eslint-plugin and @typescript-eslint/parser from 8.39.0 to 8.39.1

These need to be bumped in tandem.
2025-08-17 10:09:21 +00:00
regexowl
04f0528701 devDeps: Bump stylelint deps
This bumps stylelint-config-recommended-scss from 15.0.1 to 16.0.0 and stylelint from 16.23.0 to 16.23.1, these need to be bumped together
2025-08-17 09:46:42 +00:00
red-hat-konflux[bot]
3e2e9dcaa6 chore(deps): update konflux references
Signed-off-by: red-hat-konflux <126015336+red-hat-konflux[bot]@users.noreply.github.com>
2025-08-17 09:46:00 +00:00
Katarina Sieklova
54e413f459 Wizard: fix overflowing bp name
Truncate the name of a bp if it's too long and does not fit the Blueprint card on landing page. Not sure truncating is what we want tho.

Fixes #3034
2025-08-15 07:35:46 +00:00
dependabot[bot]
f6f6e58449 build(deps): bump @patternfly/patternfly from 6.3.0 to 6.3.1
Bumps [@patternfly/patternfly](https://github.com/patternfly/patternfly) from 6.3.0 to 6.3.1.
- [Release notes](https://github.com/patternfly/patternfly/releases)
- [Changelog](https://github.com/patternfly/patternfly/blob/main/release.config.js)
- [Commits](https://github.com/patternfly/patternfly/compare/v6.3.0...v6.3.1)

---
updated-dependencies:
- dependency-name: "@patternfly/patternfly"
  dependency-version: 6.3.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-15 06:25:42 +00:00
Michal Gold
bf77501eea Wizard: Display customized policy rules in review summary
Previously, when a user selected a compliance policy with tailored rules,
the review page always showed the default profile customizations instead
of the policy-specific customizations.

Root cause: OscapProfileInformation component was only using the profile
endpoint (/oscap/{distribution}/{profile}/customizations) which returns
base profile rules, not the policy endpoint
(/oscap/{policy}/{distribution}/policy_customizations) which returns
customized rules.

Changes:
- Add useGetOscapCustomizationsForPolicyQuery export to backendApi
- Implement dual data fetching in OscapProfileInformation:
  * Profile endpoint: for description and reference ID
  * Policy endpoint: for customized packages, services, kernel args

Fixes the compliance policy customization display bug where edited
policy rules were not reflected in the image build summary.

Add unit tests for compliance policy customizations

Fix profile description title
2025-08-14 19:42:19 +00:00
dependabot[bot]
42b16bafd8 build(deps): bump @patternfly/react-core from 6.3.0 to 6.3.1
Bumps [@patternfly/react-core](https://github.com/patternfly/patternfly-react) from 6.3.0 to 6.3.1.
- [Release notes](https://github.com/patternfly/patternfly-react/releases)
- [Commits](https://github.com/patternfly/patternfly-react/compare/@patternfly/react-core@6.3.0...@patternfly/react-core@6.3.1)

---
updated-dependencies:
- dependency-name: "@patternfly/react-core"
  dependency-version: 6.3.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-14 14:05:46 +00:00
Lucas Garfield
04adcc133c Wizard: add AAP step 2025-08-14 11:28:49 +00:00
Gianluca Zuccarelli
11e352440f Wizard: hide some review items for cockpit frontend
Hide some of the steps in the review step that aren't applicable to the
cockpit frontend.
2025-08-14 08:41:48 +00:00
dependabot[bot]
122c481c09 build(deps): bump @sentry/webpack-plugin from 4.0.2 to 4.1.0
Bumps [@sentry/webpack-plugin](https://github.com/getsentry/sentry-javascript-bundler-plugins) from 4.0.2 to 4.1.0.
- [Release notes](https://github.com/getsentry/sentry-javascript-bundler-plugins/releases)
- [Changelog](https://github.com/getsentry/sentry-javascript-bundler-plugins/blob/main/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-javascript-bundler-plugins/compare/4.0.2...4.1.0)

---
updated-dependencies:
- dependency-name: "@sentry/webpack-plugin"
  dependency-version: 4.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-13 16:30:54 +00:00
Katarina Sieklova
c930621316 Wizard: clean up unit tests
Did a little cleanup of the goToReviewStep functions, and the infinite clickNext calls.
2025-08-13 15:34:18 +00:00
Gianluca Zuccarelli
cedb4f07bd test: increase timeout for satellite registration
The test started to flake because the test was starting to run longer
than the timeout that was initially set.
2025-08-13 14:32:42 +00:00
Gianluca Zuccarelli
0c47c4b165 workflows: add api generation workflow
Add a workflow to re-generate api changes on a cronjob that runs every day.
2025-08-13 14:32:42 +00:00
dependabot[bot]
223d11b691 build(deps-dev): bump eslint-plugin-testing-library from 7.6.3 to 7.6.6
Bumps [eslint-plugin-testing-library](https://github.com/testing-library/eslint-plugin-testing-library) from 7.6.3 to 7.6.6.
- [Release notes](https://github.com/testing-library/eslint-plugin-testing-library/releases)
- [Changelog](https://github.com/testing-library/eslint-plugin-testing-library/blob/main/.releaserc.json)
- [Commits](https://github.com/testing-library/eslint-plugin-testing-library/compare/v7.6.3...v7.6.6)

---
updated-dependencies:
- dependency-name: eslint-plugin-testing-library
  dependency-version: 7.6.6
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-12 15:18:51 +00:00
dependabot[bot]
62d18b2a38 build(deps): bump @redhat-cloud-services/frontend-components-utilities
Bumps [@redhat-cloud-services/frontend-components-utilities](https://github.com/RedHatInsights/frontend-components) from 7.0.2 to 7.0.3.
- [Release notes](https://github.com/RedHatInsights/frontend-components/releases)
- [Changelog](https://github.com/RedHatInsights/frontend-components/blob/master/CHANGELOG.md)
- [Commits](https://github.com/RedHatInsights/frontend-components/compare/@redhat-cloud-services/frontend-components-utilities-7.0.2...@redhat-cloud-services/frontend-components-utilities-7.0.3)

---
updated-dependencies:
- dependency-name: "@redhat-cloud-services/frontend-components-utilities"
  dependency-version: 7.0.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-11 12:37:45 +00:00
dependabot[bot]
df405033d5 build(deps): bump @patternfly/react-code-editor from 6.3.0 to 6.3.1
Bumps [@patternfly/react-code-editor](https://github.com/patternfly/patternfly-react) from 6.3.0 to 6.3.1.
- [Release notes](https://github.com/patternfly/patternfly-react/releases)
- [Commits](https://github.com/patternfly/patternfly-react/compare/@patternfly/react-code-editor@6.3.0...@patternfly/react-code-editor@6.3.1)

---
updated-dependencies:
- dependency-name: "@patternfly/react-code-editor"
  dependency-version: 6.3.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-11 08:13:33 +00:00
red-hat-konflux[bot]
771abb8bc9 Update Konflux references
Signed-off-by: red-hat-konflux <126015336+red-hat-konflux[bot]@users.noreply.github.com>
2025-08-10 14:25:22 +00:00
dependabot[bot]
dea68f8b5c build(deps-dev): bump @patternfly/react-icons from 6.3.0 to 6.3.1
Bumps [@patternfly/react-icons](https://github.com/patternfly/patternfly-react) from 6.3.0 to 6.3.1.
- [Release notes](https://github.com/patternfly/patternfly-react/releases)
- [Commits](https://github.com/patternfly/patternfly-react/compare/@patternfly/react-icons@6.3.0...@patternfly/react-icons@6.3.1)

---
updated-dependencies:
- dependency-name: "@patternfly/react-icons"
  dependency-version: 6.3.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-10 12:26:25 +00:00
dependabot[bot]
f668d295a6 build(deps): bump @redhat-cloud-services/frontend-components
Bumps [@redhat-cloud-services/frontend-components](https://github.com/RedHatInsights/frontend-components) from 6.1.1 to 7.0.3.
- [Release notes](https://github.com/RedHatInsights/frontend-components/releases)
- [Changelog](https://github.com/RedHatInsights/frontend-components/blob/master/CHANGELOG.md)
- [Commits](https://github.com/RedHatInsights/frontend-components/compare/@redhat-cloud-services/frontend-components-6.1.1...@redhat-cloud-services/frontend-components-7.0.3)

---
updated-dependencies:
- dependency-name: "@redhat-cloud-services/frontend-components"
  dependency-version: 7.0.3
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-10 11:39:08 +00:00
Michal Gold
0af6a0324f Test: Fix race conditions in coverage tests with proper await handling
Add waitFor() for CentOS Stream 8 warning dismissal check
Add await to activation key dropdown interactions
Fixes test failures that only occurred during coverage runs due to timing delays

The changes address timing issues where UI elements don't update fast enough during coverage test runs, which are slower due to code instrumentation.
2025-08-07 14:52:37 +00:00
regexowl
eee1f78d27 Wizard: Fix lint warnings and snapshot button behaviour
This fixes lint warnings for Review step and updates behaviour of the snapshot button.

The button for reviewing repeatable build was permanently disabled, now it should be disabled only when there are no snapshotable repositories selected. The content of snapshot popover has updated rendering of states.
2025-08-07 12:08:39 +00:00
Michal Gold
d66f54a847 Wizard: Add FIPS mode support for OpenSCAP and compliance profiles (HMS-8919)
Automatically enable FIPS mode when:

User selects OpenSCAP profile with FIPS enabled (e.g., DISA STIG)
User selects compliance profile with FIPS enabled and not customized off

- Add FIPS checkbox in openscap step
- Display FIPS status in review step
- Add unit tests to FIPS checkbox feature
This ensures security compliance for profiles that require FIPS mode
without manual user intervention.
2025-08-07 11:47:17 +00:00
regexowl
3461c908fb devDeps: Bump typescript deps
This bumps @typescript-eslint/parser and @typescript-eslint/eslint-plugin from version 8.38.0 to 8.39.0

The dependencies need to be bumped in tandem.
2025-08-07 08:23:28 +00:00
Gianluca Zuccarelli
b08fee11bc devDeps: fix npm vulnerabilities
npm was complaining about some high-severity vulnerabilities. Fix these
by running `npm audit fix`
2025-08-07 07:50:47 +00:00
dependabot[bot]
e5de087810 build(deps): bump @redhat-cloud-services/frontend-components-utilities
Bumps [@redhat-cloud-services/frontend-components-utilities](https://github.com/RedHatInsights/frontend-components) from 6.1.1 to 7.0.2.
- [Release notes](https://github.com/RedHatInsights/frontend-components/releases)
- [Changelog](https://github.com/RedHatInsights/frontend-components/blob/master/CHANGELOG.md)
- [Commits](https://github.com/RedHatInsights/frontend-components/compare/@redhat-cloud-services/frontend-components-utilities-6.1.1...@redhat-cloud-services/frontend-components-utilities-7.0.2)

---
updated-dependencies:
- dependency-name: "@redhat-cloud-services/frontend-components-utilities"
  dependency-version: 7.0.2
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-07 06:36:41 +00:00
dependabot[bot]
0c46f052a8 build(deps-dev): bump sass from 1.89.2 to 1.90.0
Bumps [sass](https://github.com/sass/dart-sass) from 1.89.2 to 1.90.0.
- [Release notes](https://github.com/sass/dart-sass/releases)
- [Changelog](https://github.com/sass/dart-sass/blob/main/CHANGELOG.md)
- [Commits](https://github.com/sass/dart-sass/compare/1.89.2...1.90.0)

---
updated-dependencies:
- dependency-name: sass
  dependency-version: 1.90.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-07 06:35:43 +00:00
regexowl
904e4cccea Wizard: Update public cloud logo links
Assets were renamed, this updates naming and fixes logos.
2025-08-06 15:18:04 +00:00
regexowl
c868fe5d41 Wizard: Fix some lint warnings
This resolves lint warnings in `Components/CreateImageWizard`, warnings in `/steps` and `/utilities` sub-directories still need addressing.
2025-08-06 12:13:24 +00:00
regexowl
676ffc9b3a Blueprints: Fix lint warnings
This fixes lint warnings within `Components/Blueprints/.`
2025-08-06 12:13:24 +00:00
regexowl
30f4cdd9c3 ImagesTable: Fix lint warnings
This fixes lint warnings in `/ImagesTable/.`
2025-08-06 12:13:24 +00:00
schutzbot
8209bfe62c Post release version bump
[skip ci]
2025-08-06 08:41:17 +00:00
dependabot[bot]
2098ede032 build(deps): bump @patternfly/react-table from 6.3.0 to 6.3.1
Bumps [@patternfly/react-table](https://github.com/patternfly/patternfly-react) from 6.3.0 to 6.3.1.
- [Release notes](https://github.com/patternfly/patternfly-react/releases)
- [Commits](https://github.com/patternfly/patternfly-react/compare/@patternfly/react-table@6.3.0...@patternfly/react-table@6.3.1)

---
updated-dependencies:
- dependency-name: "@patternfly/react-table"
  dependency-version: 6.3.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-06 07:05:53 +00:00
regexowl
bb345c0e4f ESLint: All the single quotes 2025-08-05 13:56:59 +00:00
regexowl
eafcd200ae ESLint: Set trailingComma to all and run lint fix 2025-08-05 13:56:59 +00:00
regexowl
e9025e460c src: Run lint autofix 2025-08-05 13:56:59 +00:00
regexowl
4c098db796 Move prettier config to ESLint config
ESLint was not enforcing prettier rules defined in `.prettierrc`, this plugs prettier in and moves the rules to ESLint config so all lint settings are in one place.
2025-08-05 13:56:59 +00:00
regexowl
2bea0bd50b Wizard: Update kernel argument validation regex
There are special characters missing from the validation regex.
2025-08-05 12:52:39 +00:00
regexowl
894d2a4d76 Wizard: Fix registration validation for Satellite on edit
When editing a blueprint with Satellite registration the "Save changes" button was disabled due to registration validation failing with "No activation key selected". Activation key is not required for Satellite.
2025-08-05 12:50:14 +00:00
dependabot[bot]
68b2f74a97 build(deps): bump @redhat-cloud-services/frontend-components-notifications
Bumps [@redhat-cloud-services/frontend-components-notifications](https://github.com/RedHatInsights/frontend-components) from 6.1.1 to 6.1.3.
- [Release notes](https://github.com/RedHatInsights/frontend-components/releases)
- [Changelog](https://github.com/RedHatInsights/frontend-components/blob/master/CHANGELOG.md)
- [Commits](https://github.com/RedHatInsights/frontend-components/compare/@redhat-cloud-services/frontend-components-notifications-6.1.1...@redhat-cloud-services/frontend-components-notifications-6.1.3)

---
updated-dependencies:
- dependency-name: "@redhat-cloud-services/frontend-components-notifications"
  dependency-version: 6.1.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-05 11:57:50 +00:00
dependabot[bot]
35c9f32cf8 build(deps-dev): bump @eslint/js from 9.31.0 to 9.32.0
Bumps [@eslint/js](https://github.com/eslint/eslint/tree/HEAD/packages/js) from 9.31.0 to 9.32.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/commits/v9.32.0/packages/js)

---
updated-dependencies:
- dependency-name: "@eslint/js"
  dependency-version: 9.32.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-05 11:35:15 +00:00
red-hat-konflux[bot]
7269b0c7db chore(deps): update build-tools digest to b496d0a
Signed-off-by: red-hat-konflux <126015336+red-hat-konflux[bot]@users.noreply.github.com>
2025-08-05 06:40:36 +00:00
Michal Gold
88dd0880c8 Wizard: Add tooltip explaining password visibility in edit mode
When editing a blueprint, password fields show an eye icon that appears
clickable but is actually disabled for security reasons (passwords cannot
be retrieved from the backend). This creates confusing UX where users
expect the icon to work but it doesn't respond.

This change adds a tooltip that appears when hovering over the disabled
eye button, explaining that "Passwords cannot be viewed when editing a
blueprint for security reasons."

The fix wraps the disabled button in a span element to ensure the tooltip
triggers properly, as disabled buttons don't receive mouse events.

Also adds unit test to verify the tooltip functionality

Fixes #3303

🤖 Generated with AI
2025-08-04 14:06:13 +00:00
dependabot[bot]
4f250ee637 build(deps-dev): bump stylelint from 16.22.0 to 16.23.0
Bumps [stylelint](https://github.com/stylelint/stylelint) from 16.22.0 to 16.23.0.
- [Release notes](https://github.com/stylelint/stylelint/releases)
- [Changelog](https://github.com/stylelint/stylelint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/stylelint/stylelint/compare/16.22.0...16.23.0)

---
updated-dependencies:
- dependency-name: stylelint
  dependency-version: 16.23.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-04 07:07:40 +00:00
Michal Gold
acc79e149c Wizard: Add FIPS state management infrastructure
- Add fips field to wizardState type with enabled boolean property
- Add fips configuration to initialState with default value false
- Add selectFips selector to access FIPS state from store
- Add changeFips reducer action to update FIPS enabled state

This provides the Redux state management foundation for FIPS mode
configuration. UI components and wizard steps will be added separately.
2025-08-04 07:04:46 +00:00
dependabot[bot]
3b8b2ad240 build(deps-dev): bump @currents/playwright from 1.15.2 to 1.15.3
Bumps [@currents/playwright](https://github.com/currents-dev/currents-playwright-changelog) from 1.15.2 to 1.15.3.
- [Changelog](https://github.com/currents-dev/currents-playwright-changelog/blob/main/CHANGELOG.md)
- [Commits](https://github.com/currents-dev/currents-playwright-changelog/commits)

---
updated-dependencies:
- dependency-name: "@currents/playwright"
  dependency-version: 1.15.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-04 07:04:28 +00:00
dependabot[bot]
9f526faa65 build(deps-dev): bump eslint-plugin-playwright from 2.2.1 to 2.2.2
Bumps [eslint-plugin-playwright](https://github.com/playwright-community/eslint-plugin-playwright) from 2.2.1 to 2.2.2.
- [Release notes](https://github.com/playwright-community/eslint-plugin-playwright/releases)
- [Changelog](https://github.com/playwright-community/eslint-plugin-playwright/blob/main/CHANGELOG.md)
- [Commits](https://github.com/playwright-community/eslint-plugin-playwright/compare/v2.2.1...v2.2.2)

---
updated-dependencies:
- dependency-name: eslint-plugin-playwright
  dependency-version: 2.2.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-04 07:03:25 +00:00
Klara Simickova
0acedb913c Revert "playwright: Add duration to test account creation request"
This reverts commit 1096c6d4fb.
2025-08-03 09:53:02 +00:00
regexowl
327e1cd48f src: Fix useChrome import, update mock, solve some lint warnings
This fixes the way we're importing `useChrome` in some places. The mock was also updated to reflect the changes. This resolves several lint warnings.
2025-08-01 10:03:39 +00:00
Ondřej Budai
1bfc830147 remove as much edge mgmt code as possible
We no longer include the edge mgmt federated module so let's remove
all remaining traces of the integration.
2025-08-01 09:00:02 +00:00
regexowl
fdaf5129c8 playwright: Update required Oscap packages
Follow up to https://github.com/osbuild/image-builder-crc/pull/1659.

This updates test to check required Oscap packages to match updated set.
2025-08-01 08:41:26 +00:00
regexowl
64e5744d8c Wizard: Remove RHEL betas from release options
This removes RHEL betas from release menu and cleans up the code in several places.
2025-07-31 13:40:21 +00:00
Katarina Sieklova
90c2c65ebe Wizard: disable adding empty user tabs
When creating an empty user, the "+" sign gets hidden, and only pops back up if at least user name, psswd, or ssh key is filed out.

Fixes #3114
2025-07-31 11:09:40 +00:00
dependabot[bot]
9943f54cd9 build(deps-dev): bump eslint-plugin-testing-library from 7.6.0 to 7.6.3
Bumps [eslint-plugin-testing-library](https://github.com/testing-library/eslint-plugin-testing-library) from 7.6.0 to 7.6.3.
- [Release notes](https://github.com/testing-library/eslint-plugin-testing-library/releases)
- [Changelog](https://github.com/testing-library/eslint-plugin-testing-library/blob/main/.releaserc.json)
- [Commits](https://github.com/testing-library/eslint-plugin-testing-library/compare/v7.6.0...v7.6.3)

---
updated-dependencies:
- dependency-name: eslint-plugin-testing-library
  dependency-version: 7.6.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-31 10:19:31 +00:00
dependabot[bot]
d94834e25f build(deps-dev): bump eslint from 9.30.1 to 9.32.0
Bumps [eslint](https://github.com/eslint/eslint) from 9.30.1 to 9.32.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v9.30.1...v9.32.0)

---
updated-dependencies:
- dependency-name: eslint
  dependency-version: 9.32.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-31 10:18:50 +00:00
dependabot[bot]
9efdd82771 build(deps): bump @sentry/webpack-plugin from 4.0.1 to 4.0.2
Bumps [@sentry/webpack-plugin](https://github.com/getsentry/sentry-javascript-bundler-plugins) from 4.0.1 to 4.0.2.
- [Release notes](https://github.com/getsentry/sentry-javascript-bundler-plugins/releases)
- [Changelog](https://github.com/getsentry/sentry-javascript-bundler-plugins/blob/main/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-javascript-bundler-plugins/compare/4.0.1...4.0.2)

---
updated-dependencies:
- dependency-name: "@sentry/webpack-plugin"
  dependency-version: 4.0.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-31 10:18:43 +00:00
regexowl
1096c6d4fb playwright: Add duration to test account creation request
There were some changes in Ethel recently, duration currently seems to be a required field. Ethel team works on a fix.

Default value for duration is 1 year so I've set it to that.
2025-07-31 09:36:11 +00:00
regexowl
d5321bb078 Wizard: Remove alert from OpenSCAP step
Information about required packages, services and kernel arguments is back on the step. Meaning we can remove the alert as per UX recommendation.
2025-07-30 08:14:12 +00:00
dependabot[bot]
8cf161d4e5 build(deps-dev): bump @testing-library/jest-dom from 6.6.3 to 6.6.4
Bumps [@testing-library/jest-dom](https://github.com/testing-library/jest-dom) from 6.6.3 to 6.6.4.
- [Release notes](https://github.com/testing-library/jest-dom/releases)
- [Changelog](https://github.com/testing-library/jest-dom/blob/main/CHANGELOG.md)
- [Commits](https://github.com/testing-library/jest-dom/compare/v6.6.3...v6.6.4)

---
updated-dependencies:
- dependency-name: "@testing-library/jest-dom"
  dependency-version: 6.6.4
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-30 07:01:25 +00:00
dependabot[bot]
98b890206b build(deps): bump @redhat-cloud-services/frontend-components-utilities
Bumps [@redhat-cloud-services/frontend-components-utilities](https://github.com/RedHatInsights/frontend-components) from 6.1.0 to 6.1.1.
- [Release notes](https://github.com/RedHatInsights/frontend-components/releases)
- [Changelog](https://github.com/RedHatInsights/frontend-components/blob/master/CHANGELOG.md)
- [Commits](https://github.com/RedHatInsights/frontend-components/compare/@redhat-cloud-services/frontend-components-utilities-6.1.0...@redhat-cloud-services/frontend-components-utilities-6.1.1)

---
updated-dependencies:
- dependency-name: "@redhat-cloud-services/frontend-components-utilities"
  dependency-version: 6.1.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-30 06:29:25 +00:00
dependabot[bot]
8d656b766c build(deps-dev): bump @testing-library/dom from 10.4.0 to 10.4.1
Bumps [@testing-library/dom](https://github.com/testing-library/dom-testing-library) from 10.4.0 to 10.4.1.
- [Release notes](https://github.com/testing-library/dom-testing-library/releases)
- [Changelog](https://github.com/testing-library/dom-testing-library/blob/main/CHANGELOG.md)
- [Commits](https://github.com/testing-library/dom-testing-library/compare/v10.4.0...v10.4.1)

---
updated-dependencies:
- dependency-name: "@testing-library/dom"
  dependency-version: 10.4.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-30 06:29:13 +00:00
Anna Vítová
ceec85209c Playwright: OSCAP basic test not altering things added by OSCAP 2025-07-29 15:46:56 +00:00
Anna Vítová
cfa8cbcb28 Compliance: refactor handleKernelAppend duplicate 2025-07-29 15:46:56 +00:00
Anna Vítová
96da1817df Compliance: refactor handlePartitions duplicate 2025-07-29 15:46:56 +00:00
Anna Vítová
9da490ad52 Compliance: refactor handleServices duplicate 2025-07-29 15:46:56 +00:00
Anna Vítová
128abcb98f Compliance: refactor handlePackages duplicate 2025-07-29 15:46:56 +00:00
Anna Vítová
c026102dd3 Compliance: refactor clearPackage duplicate 2025-07-29 15:46:56 +00:00
regexowl
d5877b256c test: Add test for failing fetching of target environments
This adds a test to check that the appropriate alert gets rendered.
2025-07-29 14:53:43 +00:00
regexowl
730554dc84 Wizard: Add fetch and error state or target environments
This renders an empty state with a spinner when fetching target environments and alert when there's an issue.
2025-07-29 14:53:43 +00:00
red-hat-konflux[bot]
09febf8061 chore(deps): update build-tools digest to 37ab5d0
Signed-off-by: red-hat-konflux <126015336+red-hat-konflux[bot]@users.noreply.github.com>
2025-07-29 10:09:19 +00:00
dependabot[bot]
8524e8e374 build(deps): bump @patternfly/react-core from 6.1.0 to 6.3.0
Bumps [@patternfly/react-core](https://github.com/patternfly/patternfly-react) from 6.1.0 to 6.3.0.
- [Release notes](https://github.com/patternfly/patternfly-react/releases)
- [Commits](https://github.com/patternfly/patternfly-react/compare/@patternfly/react-core@6.1.0...@patternfly/react-core@6.3.0)

---
updated-dependencies:
- dependency-name: "@patternfly/react-core"
  dependency-version: 6.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-29 10:07:27 +00:00
dependabot[bot]
a8f21a7a90 build(deps): bump @unleash/proxy-client-react from 5.0.0 to 5.0.1
Bumps [@unleash/proxy-client-react](https://github.com/Unleash/unleash-proxy-react) from 5.0.0 to 5.0.1.
- [Release notes](https://github.com/Unleash/unleash-proxy-react/releases)
- [Changelog](https://github.com/Unleash/proxy-client-react/blob/main/Changelog.md)
- [Commits](https://github.com/Unleash/unleash-proxy-react/compare/v5.0.0...v5.0.1)

---
updated-dependencies:
- dependency-name: "@unleash/proxy-client-react"
  dependency-version: 5.0.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-29 09:31:27 +00:00
dependabot[bot]
e52b43bdb7 build(deps): bump @redhat-cloud-services/frontend-components-notifications
Bumps [@redhat-cloud-services/frontend-components-notifications](https://github.com/RedHatInsights/frontend-components) from 6.1.0 to 6.1.1.
- [Release notes](https://github.com/RedHatInsights/frontend-components/releases)
- [Changelog](https://github.com/RedHatInsights/frontend-components/blob/master/CHANGELOG.md)
- [Commits](https://github.com/RedHatInsights/frontend-components/compare/@redhat-cloud-services/frontend-components-notifications-6.1.0...@redhat-cloud-services/frontend-components-notifications-6.1.1)

---
updated-dependencies:
- dependency-name: "@redhat-cloud-services/frontend-components-notifications"
  dependency-version: 6.1.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-29 08:38:48 +00:00
regexowl
2c3efe4c04 Wizard: Resolve FSC lint warnings
This resolves lint warnings for File System Configuration step.
2025-07-29 08:16:27 +00:00
regexowl
ad5ea22da8 Update API
This adds changes from `npm api`.
2025-07-29 07:58:00 +00:00
regexowl
9f3ad99037 test: Remove delay for .upload user method
This sets delay to null for `uploadFile` helper, meaning there's no delay for the `.upload` user method now.
2025-07-28 10:59:46 +00:00
dependabot[bot]
f4e872548c build(deps): bump @sentry/webpack-plugin from 4.0.0 to 4.0.1
Bumps [@sentry/webpack-plugin](https://github.com/getsentry/sentry-javascript-bundler-plugins) from 4.0.0 to 4.0.1.
- [Release notes](https://github.com/getsentry/sentry-javascript-bundler-plugins/releases)
- [Changelog](https://github.com/getsentry/sentry-javascript-bundler-plugins/blob/main/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-javascript-bundler-plugins/compare/4.0.0...4.0.1)

---
updated-dependencies:
- dependency-name: "@sentry/webpack-plugin"
  dependency-version: 4.0.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-28 09:49:39 +00:00
dependabot[bot]
5b7b8daa4d build(deps): bump @redhat-cloud-services/frontend-components
Bumps [@redhat-cloud-services/frontend-components](https://github.com/RedHatInsights/frontend-components) from 6.1.0 to 6.1.1.
- [Release notes](https://github.com/RedHatInsights/frontend-components/releases)
- [Changelog](https://github.com/RedHatInsights/frontend-components/blob/master/CHANGELOG.md)
- [Commits](https://github.com/RedHatInsights/frontend-components/compare/@redhat-cloud-services/frontend-components-6.1.0...@redhat-cloud-services/frontend-components-6.1.1)

---
updated-dependencies:
- dependency-name: "@redhat-cloud-services/frontend-components"
  dependency-version: 6.1.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-28 08:59:20 +00:00
regexowl
67a0f86dde ESLint: Set no-unused-vars rule to error
This rule should output error instead of just a warning.
2025-07-28 08:55:24 +00:00
dependabot[bot]
4d051eecde build(deps-dev): bump eslint-plugin-playwright from 2.2.0 to 2.2.1
Bumps [eslint-plugin-playwright](https://github.com/playwright-community/eslint-plugin-playwright) from 2.2.0 to 2.2.1.
- [Release notes](https://github.com/playwright-community/eslint-plugin-playwright/releases)
- [Changelog](https://github.com/playwright-community/eslint-plugin-playwright/blob/main/CHANGELOG.md)
- [Commits](https://github.com/playwright-community/eslint-plugin-playwright/compare/v2.2.0...v2.2.1)

---
updated-dependencies:
- dependency-name: eslint-plugin-playwright
  dependency-version: 2.2.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-28 07:44:53 +00:00
Gianluca Zuccarelli
661fd29a5e playwright: add timeout for cockpit tests
Playwright at times is making assertions before the osbuild-worker file is loaded.
This is causing some flakiness in the tests, adding a timeout before some of the
assertions should hopefully remove some of the flakiness.
2025-07-25 09:47:56 +00:00
dependabot[bot]
6bf800d4d9 build(deps-dev): bump @patternfly/react-icons from 6.1.0 to 6.3.0
Bumps [@patternfly/react-icons](https://github.com/patternfly/patternfly-react) from 6.1.0 to 6.3.0.
- [Release notes](https://github.com/patternfly/patternfly-react/releases)
- [Commits](https://github.com/patternfly/patternfly-react/compare/@patternfly/react-icons@6.1.0...@patternfly/react-icons@6.3.0)

---
updated-dependencies:
- dependency-name: "@patternfly/react-icons"
  dependency-version: 6.3.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-25 08:42:30 +00:00
dependabot[bot]
825e3beac1 build(deps-dev): bump @types/node from 24.0.13 to 24.1.0
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 24.0.13 to 24.1.0.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-version: 24.1.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-25 06:28:31 +00:00
Michal Gold
6f9a34c972 profileSelector: Prevent wizard refresh on Enter key in OpenSCAP profile selector
Added event.preventDefault() in onKeyDown handler to stop default form
submission behavior when hitting Enter in the typeahead filter.
Also implemented auto-selection of single filtered results on Enter.
2025-07-24 10:27:34 +00:00
regexowl
42d96edd00 test: Remove typing delay for locale test
`Step Locale - unknown option is disabled` was quite flaky recently. Removing typing delay from the userEvent actions should solve the issue.
2025-07-24 08:43:24 +00:00
dependabot[bot]
b8dc0e60c9 build(deps): bump axios from 1.10.0 to 1.11.0
Bumps [axios](https://github.com/axios/axios) from 1.10.0 to 1.11.0.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v1.10.0...v1.11.0)

---
updated-dependencies:
- dependency-name: axios
  dependency-version: 1.11.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-24 07:43:43 +00:00
dependabot[bot]
5c1f9dbbdd build(deps): bump @patternfly/react-code-editor from 6.1.0 to 6.3.0
Bumps [@patternfly/react-code-editor](https://github.com/patternfly/patternfly-react) from 6.1.0 to 6.3.0.
- [Release notes](https://github.com/patternfly/patternfly-react/releases)
- [Commits](https://github.com/patternfly/patternfly-react/compare/@patternfly/react-code-editor@6.1.0...@patternfly/react-code-editor@6.3.0)

---
updated-dependencies:
- dependency-name: "@patternfly/react-code-editor"
  dependency-version: 6.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-24 06:42:38 +00:00
Gianluca Zuccarelli
3d39065ad0 README: update api generation
Since our codegen tool supports pulling in OpenAPI schemas from a url,
we no longer download the OpenAPI schema files. Update the README to
reflect the changes.
2025-07-23 10:23:12 +00:00
Gianluca Zuccarelli
f86f81d6d5 api: remove pull command
The api config supports pulling in the openapi schema's via a url, so there isn't a
need to pull this in manually and just run the code generation.

We also need to remove the `openshift-virt` target since this was removed from
image-builder-crc.
2025-07-23 10:23:12 +00:00
Gianluca Zuccarelli
690b71636a playwright: fix cockpit cloud provider test
The test was a bit flaky, move the worker file check to the end of
the test since it appears that we might be trying to read the file
before it has been created.
2025-07-23 08:58:26 +00:00
Gianluca Zuccarelli
a6eadbffac playwright: add an aws upload test
Create a blueprint configured for AWS and make sure it shows up in
the images table when it is built.
2025-07-23 08:58:26 +00:00
Gianluca Zuccarelli
8af4181ae9 store/cockpitApi: refactor /compose endpoint
The on-prem `/compose` endpoint needs to know about two different
compose requests. The image-builder-crc compose request gets saved to
the frontend's local storage. While a different request gets sent to
osbuild's cloudapi. This commit tidy's up and refactors the code in the
/compose endpoint in the cockpit api.
2025-07-23 08:58:26 +00:00
Gianluca Zuccarelli
3a9e3aa200 requestMapper: add region to aws upload type
The on-prem backend needs the aws region in order to upload the ami
target to aws. In order to do this, we use some specific cockpit types
that contain the region field, meaning that requests to image-builder-crc
should be unchanged. This is essentialy the wiring commit to get this
functionality into the `/composes` endpoint for on-prem.
2025-07-23 08:58:26 +00:00
Gianluca Zuccarelli
4339420cb8 Wizard: add region selector for on-prem AWS
We need to be able to select the region for AWS targets in the frontend
image builder. This commit adds the field to the wizard, but doesn't
wire this up to the api call just yet.
2025-07-23 08:58:26 +00:00
Gianluca Zuccarelli
fe5abaeb45 Table: fix the image table status for on prem aws uploads
We need to make some minor tweaks to get this to show properly for the
on-prem frontend.
2025-07-23 08:58:26 +00:00
Gianluca Zuccarelli
c88171da19 cockpit: add cockpit image request types
Update the types to include some cockpit specific fields to the
image requests. We could also update the image-builder-crc api
openapi specs, but this would need further discussion
2025-07-23 08:58:26 +00:00
Gianluca Zuccarelli
2f765a1d4b multi: fix analytics for on-prem
This commit fixes some more analytics calls happening in the on-prem
frontend.
2025-07-23 08:58:26 +00:00
Gianluca Zuccarelli
2ce62d4ef0 store/cockpitApi: enable aws image types 2025-07-23 08:58:26 +00:00
Gianluca Zuccarelli
253317497e CreateImageWizard: hide aws sources for on-prem
For now just allow manual entry of the aws account id, since we aren't
able to configure sources on-premise yet (if ever).
2025-07-23 08:58:26 +00:00
dependabot[bot]
8dd82d5801 build(deps): bump @sentry/webpack-plugin from 3.6.1 to 4.0.0
Bumps [@sentry/webpack-plugin](https://github.com/getsentry/sentry-javascript-bundler-plugins) from 3.6.1 to 4.0.0.
- [Release notes](https://github.com/getsentry/sentry-javascript-bundler-plugins/releases)
- [Changelog](https://github.com/getsentry/sentry-javascript-bundler-plugins/blob/main/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-javascript-bundler-plugins/compare/3.6.1...4.0.0)

---
updated-dependencies:
- dependency-name: "@sentry/webpack-plugin"
  dependency-version: 4.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-23 08:37:17 +00:00
schutzbot
33d3a02ee5 Post release version bump
[skip ci]
2025-07-23 08:40:00 +00:00
dependabot[bot]
1621042a08 build(deps): bump @patternfly/react-table from 6.1.0 to 6.3.0
Bumps [@patternfly/react-table](https://github.com/patternfly/patternfly-react) from 6.1.0 to 6.3.0.
- [Release notes](https://github.com/patternfly/patternfly-react/releases)
- [Commits](https://github.com/patternfly/patternfly-react/compare/@patternfly/react-table@6.1.0...@patternfly/react-table@6.3.0)

---
updated-dependencies:
- dependency-name: "@patternfly/react-table"
  dependency-version: 6.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-23 07:04:59 +00:00
dependabot[bot]
786b334573 build(deps): bump @patternfly/patternfly from 6.1.0 to 6.3.0
Bumps [@patternfly/patternfly](https://github.com/patternfly/patternfly) from 6.1.0 to 6.3.0.
- [Release notes](https://github.com/patternfly/patternfly/releases)
- [Changelog](https://github.com/patternfly/patternfly/blob/main/release.config.js)
- [Commits](https://github.com/patternfly/patternfly/compare/v6.1.0...v6.3.0)

---
updated-dependencies:
- dependency-name: "@patternfly/patternfly"
  dependency-version: 6.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-23 06:50:20 +00:00
dependabot[bot]
c67eefbe25 build(deps): bump form-data from 4.0.1 to 4.0.4
Bumps [form-data](https://github.com/form-data/form-data) from 4.0.1 to 4.0.4.
- [Release notes](https://github.com/form-data/form-data/releases)
- [Changelog](https://github.com/form-data/form-data/blob/master/CHANGELOG.md)
- [Commits](https://github.com/form-data/form-data/compare/v4.0.1...v4.0.4)

---
updated-dependencies:
- dependency-name: form-data
  dependency-version: 4.0.4
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-22 13:42:19 +00:00
regexowl
520b94099e Wizard: Fix stuck page for package search
Fixes #3381

This resets the paging to page 1 when search term is updated.
2025-07-22 11:33:36 +00:00
regexowl
4fe4872be4 devDeps: Bump msw from 2.10.3 to 2.10.4
This bumps msw from 2.10.3 to 2.10.4
2025-07-22 10:51:34 +00:00
regexowl
f40e67a98a devDeps: Bump typecript deps
This bumps @typescript-eslint/eslint-plugin and @typescript-eslint/parser to version 8.38.0, the dependencies need to be bumped together.
2025-07-22 08:53:06 +00:00
dependabot[bot]
69751cba1a build(deps): bump @sentry/webpack-plugin from 3.5.0 to 3.6.1
Bumps [@sentry/webpack-plugin](https://github.com/getsentry/sentry-javascript-bundler-plugins) from 3.5.0 to 3.6.1.
- [Release notes](https://github.com/getsentry/sentry-javascript-bundler-plugins/releases)
- [Changelog](https://github.com/getsentry/sentry-javascript-bundler-plugins/blob/main/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-javascript-bundler-plugins/compare/3.5.0...3.6.1)

---
updated-dependencies:
- dependency-name: "@sentry/webpack-plugin"
  dependency-version: 3.6.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-22 08:05:40 +00:00
dependabot[bot]
06a5db21e0 build(deps-dev): bump typescript-eslint from 8.37.0 to 8.38.0
Bumps [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) from 8.37.0 to 8.38.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.38.0/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: typescript-eslint
  dependency-version: 8.38.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-22 07:16:24 +00:00
dependabot[bot]
016cf0b4f3 build(deps-dev): bump @vitejs/plugin-react from 4.6.0 to 4.7.0
Bumps [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react) from 4.6.0 to 4.7.0.
- [Release notes](https://github.com/vitejs/vite-plugin-react/releases)
- [Changelog](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite-plugin-react/commits/plugin-react@4.7.0/packages/plugin-react)

---
updated-dependencies:
- dependency-name: "@vitejs/plugin-react"
  dependency-version: 4.7.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-21 10:30:04 +00:00
dependabot[bot]
654458c12d build(deps-dev): bump stylelint from 16.21.1 to 16.22.0
Bumps [stylelint](https://github.com/stylelint/stylelint) from 16.21.1 to 16.22.0.
- [Release notes](https://github.com/stylelint/stylelint/releases)
- [Changelog](https://github.com/stylelint/stylelint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/stylelint/stylelint/compare/16.21.1...16.22.0)

---
updated-dependencies:
- dependency-name: stylelint
  dependency-version: 16.22.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-21 09:59:35 +00:00
red-hat-konflux[bot]
49fef039c0 chore(deps): update konflux references
Signed-off-by: red-hat-konflux <126015336+red-hat-konflux[bot]@users.noreply.github.com>
2025-07-21 09:27:02 +00:00
regexowl
e657b88bfc src: Fix status text color
The `className` for text color was changed in PF6, this updates it in relevant places.
2025-07-21 07:54:20 +00:00
dependabot[bot]
cdca105c97 build(deps-dev): bump typescript-eslint from 8.35.1 to 8.37.0
---
updated-dependencies:
- dependency-name: typescript-eslint
  dependency-version: 8.37.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-18 11:17:15 +00:00
dependabot[bot]
78ea07d777 build(deps): bump on-headers and compression
---
updated-dependencies:
- dependency-name: on-headers
  dependency-version: 1.1.0
  dependency-type: indirect
- dependency-name: compression
  dependency-version: 1.8.1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-18 10:50:48 +00:00
dependabot[bot]
c550ba1ae8 build(deps-dev): bump @typescript-eslint/parser from 8.36.0 to 8.37.0
Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 8.36.0 to 8.37.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.37.0/packages/parser)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/parser"
  dependency-version: 8.37.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-18 10:26:08 +00:00
Katarina Sieklova
d03f41f160 Wizard: Make the blueprint name unclickable after filtering
Fixes #3419
2025-07-17 14:06:48 +00:00
regexowl
0373a55f8c dependabot: Lower limit for number of open PRs
We no longer have dependency bumps "stuck" in the limbo. Let's lower the limit again as we're not overwhelmed by new dependabot PRs.
2025-07-17 12:59:03 +00:00
dependabot[bot]
eeae4f9467 build(deps-dev): bump @currents/playwright from 1.15.1 to 1.15.2
---
updated-dependencies:
- dependency-name: "@currents/playwright"
  dependency-version: 1.15.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-17 06:49:37 +00:00
dependabot[bot]
4c04f2dc54 build(deps-dev): bump eslint-plugin-testing-library from 7.5.4 to 7.6.0
Bumps [eslint-plugin-testing-library](https://github.com/testing-library/eslint-plugin-testing-library) from 7.5.4 to 7.6.0.
- [Release notes](https://github.com/testing-library/eslint-plugin-testing-library/releases)
- [Changelog](https://github.com/testing-library/eslint-plugin-testing-library/blob/main/.releaserc.json)
- [Commits](https://github.com/testing-library/eslint-plugin-testing-library/compare/v7.5.4...v7.6.0)

---
updated-dependencies:
- dependency-name: eslint-plugin-testing-library
  dependency-version: 7.6.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-17 06:42:45 +00:00
dependabot[bot]
10ff517ab0 build(deps-dev): bump @eslint/js from 9.30.1 to 9.31.0
Bumps [@eslint/js](https://github.com/eslint/eslint/tree/HEAD/packages/js) from 9.30.1 to 9.31.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/commits/v9.31.0/packages/js)

---
updated-dependencies:
- dependency-name: "@eslint/js"
  dependency-version: 9.31.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-16 07:32:01 +00:00
regexowl
25a5f140d8 ESLint: Add rule to sort imports alphabetically
The `import/order` rule isn't enough to sort import within a single import group alphabetically.

This adds `sort-imports` rule that handles the sorting within groups.
2025-07-15 16:52:45 +00:00
regexowl
91b2cc2d10 ESLint: Add rule for duplicate imports
We had decent amount of import groups split into several imports, this adds a rule to output error when there's a duplicate import.
2025-07-15 16:52:45 +00:00
regexowl
a0fe3644c3 src: Remove more data-testids
As using `data-testid`s in tests is an anti-pattern, this removes more of them and replaces them with appropriate locators in the tests.
2025-07-15 16:51:29 +00:00
regexowl
0ea874abc6 Wizard: Fix dropdown behaviour on "Enter"
The page refreshed when pressing "Enter" while in the dropdown input. This fixes the behaviour.

If the dropdown's closed, "Enter" will open the options, if there's an input value that perfectly matches one of the activation keys, it gets selected.

The order of functions was also slightly cleaned up so they're all in one place after `useEffect`s.
2025-07-15 14:41:52 +00:00
regexowl
2406d14304 Wizard: Update activation keys dropdown
This updates key dropdown's helper text to match recent mocks.

Also checked other criteria:
- the input is clearable
- current selection is marked with a check in the list of options
2025-07-15 14:41:52 +00:00
Katarina Sieklova
c2e94100db Wizard: bring back invlaid group testing in users 2025-07-15 13:47:37 +00:00
Florian Schüller
ccfdb49db2 CreateImageWizard: Implement user-friendly error messages
Puts the error messages in one place - LabelInput.tsx
2025-07-15 13:47:37 +00:00
regexowl
3f35101f68 Migrate ESLint to v 9.x
This bumps needed dependencies and migrates previously used ESLint configs to the new flat config schema.
2025-07-15 13:39:36 +00:00
regexowl
10a40aaec4 Wizard: Move org ID into a copy-able field
This moves the org ID into a copy-able field and updates the permanent info as per mocks.

The popover that previously included the org ID is removed from mocks.
2025-07-15 13:27:46 +00:00
Gianluca Zuccarelli
c9d721ea52 playwright: add a test for cockpit cloud config
SSIA
2025-07-15 12:38:52 +00:00
Gianluca Zuccarelli
0b0171bb87 cloudConfig: add a popover for creds path
Add a popover to give more information on the aws credentials
path.
2025-07-15 12:38:52 +00:00
Gianluca Zuccarelli
1ed4380bfc cloudConfig: restart worker on submit
Restart osbuild-composer and the worker after updating the config, this
is a necessary step for osbuild-composer to register the changes.
2025-07-15 12:38:52 +00:00
Gianluca Zuccarelli
5afe1c1fc1 cloudConfig: save the aws configs
Save the AWS config modifications to the `osbuild-worker.toml` file.
2025-07-15 12:38:52 +00:00
Gianluca Zuccarelli
ca6c59bfb8 store/cockpitApi: add worker config mutation
Add an endpoint to update the worker config.
2025-07-15 12:38:52 +00:00
Gianluca Zuccarelli
09df007eb9 test/mocks: add cockpit modify
Add the `modify` file function which performs atomic modifications to a
file.
2025-07-15 12:38:52 +00:00
Gianluca Zuccarelli
c55706b931 cockpitApi: create worker config file
Create the `osbuild-worker.toml` file if it doesn't exist already.
2025-07-15 12:38:52 +00:00
Gianluca Zuccarelli
afcc0126e4 cloudConfig: toggle aws config
This is still a wip since the form fields aren't yet disabled when the
config toggle is set to off.
2025-07-15 12:38:52 +00:00
Gianluca Zuccarelli
73ffb97414 cloudConfig: error component
Add an error component to improve the UI when there is an issue reading
the `osbuild-worker.toml` file.
2025-07-15 12:38:52 +00:00
Gianluca Zuccarelli
ecc1c2c8cd cockpitApi: get worker config
Add a query to load the `/etc/osbuild-worker/osbuild-worker.toml` config
and use this to set the state of the `cloudConfig` store slice.
2025-07-15 12:38:52 +00:00
Gianluca Zuccarelli
d7945a458a cockpitApi: add worker config types
Create a few types to help stick to conventions and tidy up the code.
2025-07-15 12:38:52 +00:00
Gianluca Zuccarelli
87647f8854 cloudConfig: configure the footer
Setup the footer for the AWS config step.
2025-07-15 12:38:52 +00:00
Gianluca Zuccarelli
9d2c798376 cloudConfig: add aws config fields 2025-07-15 12:38:52 +00:00
Gianluca Zuccarelli
1e545af0c7 cloudConfigSlice: add new slice 2025-07-15 12:38:52 +00:00
regexowl
719ee1a024 README: Remove information about deprecated insights proxy
The insights proxy has been deprecated for a long time.
2025-07-15 08:34:39 +00:00
red-hat-konflux[bot]
146a9131b4 chore(deps): update build-tools digest to 07aeb0d
Signed-off-by: red-hat-konflux <126015336+red-hat-konflux[bot]@users.noreply.github.com>
2025-07-15 06:42:40 +00:00
Lucas Garfield
fd474dace0 PackageRecommendations: Add modelVersion to analytics tracking
Previous commit 852d24e5 added modelVersion to the API response and mentioned
adding it to analytics tracking, but only added the distribution field to
the analytics events. This commit completes the implementation by adding
modelVersion to both package recommendation analytics events:

- "Package Recommendations Shown" event now includes modelVersion
- "Recommended Package Added" event now includes modelVersion

This enables proper tracking of which recommendation models are being used
and their effectiveness.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-14 15:47:21 +00:00
regexowl
b6e9fef70b Wizard: Change WSL file extension
This updates WSL file extension from `.tar.gz` to `.wsl`
2025-07-14 14:02:03 +00:00
regexowl
10a67ca6e3 README: Re-structure, add info for external developers
This re-organizes the structure of the README so the parts are better grouped by topic.

Information for external developers was also added (VPN required for service development).
2025-07-14 10:03:42 +00:00
red-hat-konflux[bot]
d0aa6d733e chore(deps): update konflux references
Signed-off-by: red-hat-konflux <126015336+red-hat-konflux[bot]@users.noreply.github.com>
2025-07-14 09:05:23 +00:00
dependabot[bot]
a2da15b9d2 build(deps-dev): bump @types/node from 24.0.12 to 24.0.13
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 24.0.12 to 24.0.13.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-version: 24.0.13
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-11 07:18:31 +00:00
dependabot[bot]
6beae64f60 build(deps-dev): bump eslint-plugin-testing-library from 7.5.3 to 7.5.4
Bumps [eslint-plugin-testing-library](https://github.com/testing-library/eslint-plugin-testing-library) from 7.5.3 to 7.5.4.
- [Release notes](https://github.com/testing-library/eslint-plugin-testing-library/releases)
- [Changelog](https://github.com/testing-library/eslint-plugin-testing-library/blob/main/.releaserc.json)
- [Commits](https://github.com/testing-library/eslint-plugin-testing-library/compare/v7.5.3...v7.5.4)

---
updated-dependencies:
- dependency-name: eslint-plugin-testing-library
  dependency-version: 7.5.4
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-11 07:17:59 +00:00
dependabot[bot]
3e115228e4 build(deps-dev): bump @redhat-cloud-services/tsc-transform-imports
Bumps @redhat-cloud-services/tsc-transform-imports from 1.0.24 to 1.0.25.

---
updated-dependencies:
- dependency-name: "@redhat-cloud-services/tsc-transform-imports"
  dependency-version: 1.0.25
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-11 06:59:40 +00:00
dependabot[bot]
c17b54c68c build(deps-dev): bump @currents/playwright from 1.14.1 to 1.15.1
Bumps [@currents/playwright](https://github.com/currents-dev/currents-playwright-changelog) from 1.14.1 to 1.15.1.
- [Changelog](https://github.com/currents-dev/currents-playwright-changelog/blob/main/CHANGELOG.md)
- [Commits](https://github.com/currents-dev/currents-playwright-changelog/commits)

---
updated-dependencies:
- dependency-name: "@currents/playwright"
  dependency-version: 1.15.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-11 06:54:01 +00:00
regexowl
74817d87de devDeps: Bump msw from 2.10.2 to 2.10.3
This bumps msw from 2.10.2 to 2.10.3
2025-07-10 10:38:05 +00:00
dependabot[bot]
0bf939f2d0 build(deps-dev): bump eslint-plugin-import from 2.31.0 to 2.32.0
Bumps [eslint-plugin-import](https://github.com/import-js/eslint-plugin-import) from 2.31.0 to 2.32.0.
- [Release notes](https://github.com/import-js/eslint-plugin-import/releases)
- [Changelog](https://github.com/import-js/eslint-plugin-import/blob/main/CHANGELOG.md)
- [Commits](https://github.com/import-js/eslint-plugin-import/compare/v2.31.0...v2.32.0)

---
updated-dependencies:
- dependency-name: eslint-plugin-import
  dependency-version: 2.32.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-10 07:13:13 +00:00
dependabot[bot]
9ffbf67c42 build(deps-dev): bump @types/node from 24.0.10 to 24.0.12
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 24.0.10 to 24.0.12.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-version: 24.0.12
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-10 06:22:32 +00:00
schutzbot
8c4e8d4fab Post release version bump
[skip ci]
2025-07-09 08:37:19 +00:00
regexowl
bb9c5620ee Wizard: Show error with duplicated values
This show an error when user imports duplicate values to a `<LabelInput>` field.
2025-07-09 08:07:28 +00:00
Simon Steinbeiss
a92d087014 Drop Edge Management federated module (HMS-8637) 2025-07-09 07:03:34 +00:00
dependabot[bot]
33b374b3a0 build(deps-dev): bump @typescript-eslint/eslint-plugin
Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 8.35.1 to 8.36.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.36.0/packages/eslint-plugin)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/eslint-plugin"
  dependency-version: 8.36.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-08 12:22:21 +00:00
dependabot[bot]
94bcb9672f build(deps-dev): bump @typescript-eslint/parser from 8.35.1 to 8.36.0
Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 8.35.1 to 8.36.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.36.0/packages/parser)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/parser"
  dependency-version: 8.36.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-08 10:49:59 +00:00
dependabot[bot]
875b8a150d build(deps-dev): bump @babel/core from 7.27.7 to 7.28.0
Bumps [@babel/core](https://github.com/babel/babel/tree/HEAD/packages/babel-core) from 7.27.7 to 7.28.0.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.28.0/packages/babel-core)

---
updated-dependencies:
- dependency-name: "@babel/core"
  dependency-version: 7.28.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-07 10:03:50 +00:00
dependabot[bot]
8abeb4f53b build(deps-dev): bump stylelint from 16.21.0 to 16.21.1
Bumps [stylelint](https://github.com/stylelint/stylelint) from 16.21.0 to 16.21.1.
- [Release notes](https://github.com/stylelint/stylelint/releases)
- [Changelog](https://github.com/stylelint/stylelint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/stylelint/stylelint/compare/16.21.0...16.21.1)

---
updated-dependencies:
- dependency-name: stylelint
  dependency-version: 16.21.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-07 08:50:53 +00:00
dependabot[bot]
5c93300927 build(deps-dev): bump @babel/preset-env from 7.27.2 to 7.28.0
Bumps [@babel/preset-env](https://github.com/babel/babel/tree/HEAD/packages/babel-preset-env) from 7.27.2 to 7.28.0.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.28.0/packages/babel-preset-env)

---
updated-dependencies:
- dependency-name: "@babel/preset-env"
  dependency-version: 7.28.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-07 07:10:47 +00:00
red-hat-konflux[bot]
9f8b271b7d chore(deps): update konflux references
Signed-off-by: red-hat-konflux <126015336+red-hat-konflux[bot]@users.noreply.github.com>
2025-07-07 06:51:36 +00:00
CD Cabrera
9a29eeb28d fix: hms-8708 landing page sticky header 2025-07-04 08:23:56 +00:00
regexowl
3f1a80fbe2 Run actions with Node 22
The github actions were previously ran with Node 20, this bumps them to Node 22.

README was also updated to reflect currently used version of Node.
2025-07-03 14:45:48 +00:00
regexowl
855f1430ad api: Update pull.sh, regenerate schemas and fix errors
This adds missing schema links to the `pull.sh` script, pulls new schema definitions, re-generates schemas and updates the code where needed.
2025-07-03 10:01:15 +00:00
regexowl
0b1abb57b9 Remove devel dockerfile and update README
Since local fullstack deployment is being done via https://github.com/osbuild/osbuild-getting-started, we can get rid of the original dockerile.

README was also updated to no longer point to the `devel` folder as it just points you to the same repository. osbuild-getting-started is mentioned in the "Backend development" section.
2025-07-03 09:28:59 +00:00
regexowl
6521a46bb1 Wizard: Replace deprecated Modals with non-deprecated ones
The schema for `Modal` component changed between PF5 and PF6, this updates the modals to their non-deprecated version.
2025-07-03 09:19:22 +00:00
regexowl
caa678ebeb Wizard: Replace VMware radios with checkboxes
This replaces previously used VMware radios with checkboxes, allowing the user to select both VMware vSphere targets at the same time.
2025-07-03 09:15:05 +00:00
regexowl
ea93498ef2 Remove IQE scripts and configs
Since we're no longer testing via IQE, we can say bye bye to the `pr_check.sh` script and `iqe-trigger-integration.yml`.
2025-07-03 09:06:08 +00:00
Lucas Garfield
5d6c6dc58b BlueprintCard: Make hand cursor appear on hover
This commit fixes an issue where if both isClickable and isSelectable
are passed to a card in Patternfly 6, the hand cursor no longer appears
on hover and it is not obvious the card is clickable.

Co-Author: Gianluca Zuccarelli <gzuccare@redhat.com>
2025-07-03 06:52:30 +00:00
regexowl
bf952c5c7a devDeps: Bump @typescript-eslint
This bumps @typescript-eslint/eslint-plugin and @typescript-eslint/parser from 8.34.1 to 8.35.1, the deps need to be bumped in tandem.
2025-07-02 10:12:41 +00:00
regexowl
6ae8b3e740 Wizard: Hide Kernel step for WSL
This hides Kernel step for WSL targets only and adds an alert with info that the customization will not be added to the WSL images if more targets are selected.
2025-07-02 10:12:01 +00:00
Dominik Vagner
eaead88a78 Fix: package search bug with RH repos in template
This fixes a bug inside of the package search step where packages from
additional (not the 2 default ones) RH repos were not coming up when
searching. This only happens when using a content template as that's
currently the only way to add more RH repos.
2025-07-02 08:28:37 +00:00
dependabot[bot]
8d106499fd build(deps-dev): bump @babel/preset-typescript from 7.27.0 to 7.27.1
Bumps [@babel/preset-typescript](https://github.com/babel/babel/tree/HEAD/packages/babel-preset-typescript) from 7.27.0 to 7.27.1.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.27.1/packages/babel-preset-typescript)

---
updated-dependencies:
- dependency-name: "@babel/preset-typescript"
  dependency-version: 7.27.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-02 07:48:34 +00:00
dependabot[bot]
2c29290212 build(deps-dev): bump @types/node from 24.0.4 to 24.0.10
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 24.0.4 to 24.0.10.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-version: 24.0.10
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-02 07:08:16 +00:00
regexowl
9367bb4b28 Wizard: Remove repository column from packages table
This removes the column with repository information from package table as it could be confusing and misleading.
2025-07-02 06:39:20 +00:00
dependabot[bot]
6a62e71ec8 build(deps-dev): bump sass from 1.88.0 to 1.89.2
Bumps [sass](https://github.com/sass/dart-sass) from 1.88.0 to 1.89.2.
- [Release notes](https://github.com/sass/dart-sass/releases)
- [Changelog](https://github.com/sass/dart-sass/blob/main/CHANGELOG.md)
- [Commits](https://github.com/sass/dart-sass/compare/1.88.0...1.89.2)

---
updated-dependencies:
- dependency-name: sass
  dependency-version: 1.89.2
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-01 12:27:24 +00:00
dependabot[bot]
779c50762f build(deps): bump @redhat-cloud-services/frontend-components
Bumps [@redhat-cloud-services/frontend-components](https://github.com/RedHatInsights/frontend-components) from 6.0.4 to 6.1.0.
- [Release notes](https://github.com/RedHatInsights/frontend-components/releases)
- [Changelog](https://github.com/RedHatInsights/frontend-components/blob/master/CHANGELOG.md)
- [Commits](https://github.com/RedHatInsights/frontend-components/compare/@redhat-cloud-services/frontend-components-6.0.4...@redhat-cloud-services/frontend-components-6.1.0)

---
updated-dependencies:
- dependency-name: "@redhat-cloud-services/frontend-components"
  dependency-version: 6.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-01 11:29:24 +00:00
regexowl
02cafcc29a devDeps: Bump eslint-plugin-testing-library
This bumps eslint-plugin-testing-library from 7.2.2 to 7.5.3 and updates a test to pass linter check.
2025-07-01 10:41:45 +00:00
dependabot[bot]
9350d4ee6b build(deps): bump @redhat-cloud-services/frontend-components-notifications
Bumps [@redhat-cloud-services/frontend-components-notifications](https://github.com/RedHatInsights/frontend-components) from 5.0.4 to 6.1.0.
- [Release notes](https://github.com/RedHatInsights/frontend-components/releases)
- [Changelog](https://github.com/RedHatInsights/frontend-components/blob/master/CHANGELOG.md)
- [Commits](https://github.com/RedHatInsights/frontend-components/compare/@redhat-cloud-services/frontend-components-notifications-5.0.4...@redhat-cloud-services/frontend-components-notifications-6.1.0)

---
updated-dependencies:
- dependency-name: "@redhat-cloud-services/frontend-components-notifications"
  dependency-version: 6.1.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-01 10:14:30 +00:00
regexowl
1f34e95469 Wizard: Hide FSC step for WSL targets
This hides FSC step when only WSL target is selected.
2025-07-01 09:47:09 +00:00
Gianluca Zuccarelli
e8d46dd716 deps: migrate fec/notifications
The frontend component library decoupled notifications from redux.
Dispatching notifications via the notifications middleware was replaced
by a new `useAddNotifications` hook.

We mostly used the notifications middleware outside of React Components
in our `enhancedImageBuilderApi` store for mutation events. I created a
wrapper around the RTK hooks that uses the `useAddNotification` hook
and created a directory for the new hooks.

In other places, where we were using the notification dispatcher inside
React components, I replaced the call with the new hook.

[1] b1d4973144/packages/notifications/doc/migration.md

bump @redhat-cloud-services/frontend-components-notifications

---
updated-dependencies:
- dependency-name: "@redhat-cloud-services/frontend-components-notifications"
  dependency-version: 6.0.2
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Co-authored-by: dependabot[bot] <support@github.com>
Assisted-by: cursor ide for generalizing the `useMutationWithNotification`
hook.
2025-07-01 09:17:38 +00:00
dependabot[bot]
77e0f5d6bf build(deps-dev): bump @babel/core from 7.27.4 to 7.27.7
Bumps [@babel/core](https://github.com/babel/babel/tree/HEAD/packages/babel-core) from 7.27.4 to 7.27.7.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.27.7/packages/babel-core)

---
updated-dependencies:
- dependency-name: "@babel/core"
  dependency-version: 7.27.7
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-01 08:42:40 +00:00
regexowl
eafb9ea0f3 Playwright: Update login
The login screen was recently changed, this updates the tests to reflect it.
2025-07-01 07:49:10 +00:00
Michal Gold
852d24e568 PackageRecommendations: Add distribution to package recommendations
- Add required distribution field to RecommendPackageRequest
- Add modelVersion field to RecommendationsResponse
- Update frontend to send RHEL major version to API
- Add analytics tracking for distribution and modelVersion

Enables version-specific package recommendations and model usage tracking.
2025-06-30 12:51:40 +00:00
regexowl
6cefc6c199 Wizard: Render labels for all FSC table columns
This adds column names for the Suffix and Unit columns that were previously not labeled.
2025-06-30 12:41:07 +00:00
Gianluca Zuccarelli
eca4e55c67 workflows: split out dev checks into jobs
Split out the dev checks into multiple jobs, the benefit of this is that it
will be easier to see which check is failing and why.
2025-06-30 09:24:02 +00:00
Gianluca Zuccarelli
9247ea6196 workflows: split out unit tests
Split out the unit tests into their own action. This decouples the unit tests
from the other tests. This means the gitlab schutzbot tests don't have to
wait until the unit tests are completed before they are triggered. This
also a step towards splitting out the tests so it is more obvious to see
which step is failing and why.
2025-06-30 09:24:02 +00:00
Dominik Vagner
e05079330b Wizard: add EPEL 10 support
This adds support for EPEL 10 repository. Changes the way of getting
the correct EPEL version for RHEL distribution to be more future proof.
2025-06-30 08:43:32 +00:00
dependabot[bot]
12024b08c6 build(deps-dev): bump stylelint from 16.18.0 to 16.21.0
Bumps [stylelint](https://github.com/stylelint/stylelint) from 16.18.0 to 16.21.0.
- [Release notes](https://github.com/stylelint/stylelint/releases)
- [Changelog](https://github.com/stylelint/stylelint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/stylelint/stylelint/compare/16.18.0...16.21.0)

---
updated-dependencies:
- dependency-name: stylelint
  dependency-version: 16.21.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-30 07:29:27 +00:00
red-hat-konflux[bot]
d7c2202b11 Update Konflux references
Signed-off-by: red-hat-konflux <126015336+red-hat-konflux[bot]@users.noreply.github.com>
2025-06-30 07:07:29 +00:00
regexowl
fc874422de test: Add templates/:uuid mock handler
This adds a mock handler for the templates/:uuid end point to resolve following warning in the test output:
```
[MSW] Error: intercepted a request without a matching request handler:
  • GET /api/content-sources/v1/templates/c40e221b-93d6-4f7e-a704-f3041b8d75c3
```
2025-06-27 15:27:29 +00:00
regexowl
2894858838 Wizard: Deduplicate ManageRepositoriesButton
This moves the `ManageRepositoriesButton` component to its own file and deduplicates it in the code base.

Tooltip for Upload repositories was also fixed and is now readable again.
2025-06-27 14:28:27 +00:00
Tom Koscielniak
0319c81b41 playwright: Add check to ensure test is authenticated
In order to fix the flakiness in cockpit plugin tests introduced by the single login, the tests need a check if they are authenticated at the start of their execution and relog in case the session expired.
2025-06-27 12:53:37 +00:00
regexowl
0597541af2 Manually revert "Fedora-services: add support for fedora env"
This reverts #2984 as Fedora service frontend is not being currently actively maintained.
2025-06-27 14:15:05 +02:00
regexowl
969497e722 Wizard: Parse locale codes to readable options
This parses options in the Languages dropdown to human readable form like so:
'en_US.UTF-8' -> 'English - United States (en_US.UTF-8)'
2025-06-27 11:45:26 +02:00
regexowl
839559d42c test: Add mock handler for repository_parameters
This adds mock handler for newly used `repository_parameters` end point, ensuring the warning about missing handler is not being printed to test output when runnning `npm run test`.
2025-06-27 11:11:51 +02:00
dependabot[bot]
4ead145e38 build(deps-dev): bump @types/node from 24.0.3 to 24.0.4
---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-version: 24.0.4
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-27 10:33:33 +02:00
dependabot[bot]
35485fc163 build(deps-dev): bump @vitejs/plugin-react from 4.4.1 to 4.6.0
---
updated-dependencies:
- dependency-name: "@vitejs/plugin-react"
  dependency-version: 4.6.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-27 09:10:17 +02:00
Florian Schüller
2d8da339e7 cockpit/README.md: initial version 2025-06-26 15:46:31 +01:00
schutzbot
f6b1971760 Post release version bump
[skip ci]
2025-06-25 08:39:24 +00:00
regexowl
2f2f40c4b7 devDeps: Bump @typescript-eslint/eslint-plugin and @typescript-eslint/parser
The dependencies need to be bumped in tandem, both went from 8.34.0 to 8.34.1
2025-06-24 15:25:58 +00:00
Katarina Sieklova
2f034dffd8 Wizard: organize steps components into the "components" folders 2025-06-24 14:09:36 +00:00
regexowl
1ea1c2de8c Wizard: Add packages, kernel and services to Oscap step
This adds the information about profile packages, kernel arguments and services to the profile info list.
2025-06-24 12:12:54 +00:00
regexowl
47aace0c5f devDeps: Bump vitest and @vitest/coverage-v8
This bumps vitest and @vitest/coverage-v8 as they need to be updated in tandem. Both versions went from 3.1.2 to 3.2.4
2025-06-24 11:43:23 +00:00
regexowl
c98659fbd7 Wizard: Show release chart on review step for RHEL 9 as well
Follow up to #3338

Since we're showing the release lifecycle for RHEL 9 now we should probably show it also on the Review step.
2025-06-24 11:09:28 +00:00
regexowl
b9fdb9946a Wizard: Update recommendations description
Turn statements around, first explain the functionality and then specify that recommendations are not dependencies.
2025-06-24 10:03:15 +00:00
Katarina Sieklova
981b62d7b0 Wizard: conflicting packages
Disable a package if it's a conflicting module stream or if it's a non-modular package whose base name is already covered by an enabled module stream.
Disable a module if anouther package with the same name was selected.
Fixes #3274
2025-06-24 07:38:33 +00:00
Katarina Sieklova
3e4ee6891d Wizard: fix filtering of OpenSCAP profiles
Fixes #3273
2025-06-24 07:16:32 +00:00
Katarina Sieklova
064aa172a0 Wizard: add Filesystem customization test 2025-06-24 07:14:56 +00:00
Gianluca Zuccarelli
7f06002b26 Wizard: hide other repos for on-prem
There is no way of configuring 3rd party repos for on-prem as of yet,
we should hide this tab from the users.

Fixes: HMS-6135
2025-06-24 06:53:31 +00:00
regexowl
1a65c0c3d4 Wizard: Update package recommendations description
This adds more information to the recommended packages expandable to highlight that:
- all needed dependencies will be included by default
- recommendations are optional and based on choices of other users
2025-06-24 06:40:14 +00:00
dependabot[bot]
434960e0fc build(deps): bump @redhat-cloud-services/frontend-components-utilities
Bumps [@redhat-cloud-services/frontend-components-utilities](https://github.com/RedHatInsights/frontend-components) from 6.0.2 to 6.1.0.
- [Release notes](https://github.com/RedHatInsights/frontend-components/releases)
- [Changelog](https://github.com/RedHatInsights/frontend-components/blob/master/CHANGELOG.md)
- [Commits](https://github.com/RedHatInsights/frontend-components/compare/@redhat-cloud-services/frontend-components-utilities-6.0.2...@redhat-cloud-services/frontend-components-utilities-6.1.0)

---
updated-dependencies:
- dependency-name: "@redhat-cloud-services/frontend-components-utilities"
  dependency-version: 6.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-24 06:31:38 +00:00
dependabot[bot]
0d65220826 build(deps-dev): bump @currents/playwright from 1.13.2 to 1.14.1
Bumps [@currents/playwright](https://github.com/currents-dev/currents-playwright-changelog) from 1.13.2 to 1.14.1.
- [Changelog](https://github.com/currents-dev/currents-playwright-changelog/blob/main/CHANGELOG.md)
- [Commits](https://github.com/currents-dev/currents-playwright-changelog/commits)

---
updated-dependencies:
- dependency-name: "@currents/playwright"
  dependency-version: 1.14.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-24 06:31:21 +00:00
red-hat-konflux[bot]
626ebaa3c7 chore(deps): update konflux references
Signed-off-by: red-hat-konflux <126015336+red-hat-konflux[bot]@users.noreply.github.com>
2025-06-23 14:26:40 +00:00
regexowl
874f5dd040 Wizard: Fix target env cards clickability
The cards are now broken, this should make them selectable again.
2025-06-20 08:55:32 +00:00
dependabot[bot]
6eaf2f9862 build(deps-dev): bump chart.js from 4.4.9 to 4.5.0
Bumps [chart.js](https://github.com/chartjs/Chart.js) from 4.4.9 to 4.5.0.
- [Release notes](https://github.com/chartjs/Chart.js/releases)
- [Commits](https://github.com/chartjs/Chart.js/compare/v4.4.9...v4.5.0)

---
updated-dependencies:
- dependency-name: chart.js
  dependency-version: 4.5.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-20 06:36:00 +00:00
regexowl
4b411c9a27 test: Update tests to reflect changes in chart visibility 2025-06-19 14:30:33 +00:00
Simon Steinbeiss
f44afe2723 Wizard: Show lifecycle information for RHEL9
Since we want to nudge customers to default to RHEL 10, it may make
sense to show them the shorter lifecycle of RHEL 9 when they select it.
2025-06-19 14:30:33 +00:00
Tom Koscielniak
2a611e9704 playwright: Add handler for new intercom notifications
Adds handler for the new intercom popup notifications that trigger OpenScap window. Also adds noWaitAfter for the click action to fix flakiness.
2025-06-19 14:26:03 +00:00
Tom Koscielniak
b499dfcf93 playwright: Add single login for all tests
Add a single login for all tests in the form of a global setup. This commit also removes the login from all tests and replaces it with navigation to landing page and revamps the popup closing logic from being applied in logging step  into a separate fixture.
2025-06-19 14:26:03 +00:00
Gianluca Zuccarelli
78bb1e118b multiple: fix act errors in the tests
This also helps reduce noise in the test output.
2025-06-19 13:53:49 +00:00
Gianluca Zuccarelli
c2998306cf multiple: fix selectable card onChange
The `onChange` event for the selectable cards needed to be placed
inside the `selectableActions` of the `CardHeader`. Since the
`onChange` action was not implemented we were getting warnings
in the test output for that a component was changing an uncontrolled
input to controlled.
2025-06-19 13:53:49 +00:00
Gianluca Zuccarelli
bac647ded6 multiple: fix selectable card actions
The Card component needs an aria-label when the card is selectable. Since this was
not set, a warning error was polluting the test output.

Fixes #3319
2025-06-19 13:53:49 +00:00
Gianluca Zuccarelli
16e5bdbe3a Wizard: fix state change on render
A setState action was occuring in during render in the customStatusNavItem
component of the CreateImageWizard. We just needed to wrap this setState
call in a useEffect hook. I also renamed the component to CustomStatusNavItem
for React component naming conventions.

See: https://reactjs.org/link/setstate-in-render
2025-06-19 13:53:49 +00:00
regexowl
66ed82a531 test: Update mocked cockpit release 2025-06-19 13:13:08 +00:00
regexowl
1d3967a585 test: Add test fixtures and update tests where needed
Mock fixtures for RHEL 10 were missing, meaning the tests were unable to fetch mocked data and failed.
2025-06-19 13:13:08 +00:00
regexowl
cdd10a01ff Wizard: Add rhel9 query parameter
We've been previously setting RHEL 9 as default, meaning there was no specific quer parameter for it.
2025-06-19 13:13:08 +00:00
Simon Steinbeiss
5545ce4027 Wizard: Adjust distribution sorting for RHEL 10
Since RHEL 10 is the new default, the sort order should reflect that.
2025-06-19 13:13:08 +00:00
Simon Steinbeiss
362bfd393b Wizard: Make RHEL 10 the default distribution 2025-06-19 13:13:08 +00:00
Michal Gold
df5388dae8 Repositories: fix architecture/version display to use readable names from API
- Add listRepositoryParameters endpoint to contentSources API
- Display human-readable names instead of technical labels
- Fix inconsistency between Image Builder and Repositories service display
- Resolve "any" vs "Any" capitalization issue -
https://github.com/osbuild/image-builder-frontend/issues/3008
2025-06-19 10:27:56 +00:00
regexowl
b59a729656 sharedComponents: Rename "Create blueprint" button
This renames the button to "Create image blueprint" to make the relationship between images and blueprints clearer.
2025-06-18 14:12:47 +00:00
regexowl
0ce28044b8 Konflux: Migrate apply-tags from 0.1 to 0.2 2025-06-18 11:48:42 +00:00
red-hat-konflux[bot]
b7860f33fc Update Konflux references
Signed-off-by: red-hat-konflux <126015336+red-hat-konflux[bot]@users.noreply.github.com>
2025-06-18 11:48:42 +00:00
Tomáš Hozza
07f500b94a GHA: enable the stale action to delete its saved state
It turns out that the stale action is not able to delete its saved
state due to missing permissions. As a result, it was not processing
issues and PRs, that have been processed once, for almost a month.

The error in the job log was:
```
Warning: Error delete _state: [403] Resource not accessible by integration
```

The fix is to add `actions: write` to the action permissions

Signed-off-by: Tomáš Hozza <thozza@redhat.com>
2025-06-18 11:45:20 +00:00
Katarina Sieklova
76320925a0 Wizard: change order of the systemd services 2025-06-18 11:24:36 +00:00
Anna Vítová
235d853f42 fix: replace hardcoded paths for satellite 2025-06-18 11:21:54 +00:00
Anna Vítová
a4ac280350 fix: replace hardcoded paths for firstboot svc 2025-06-18 11:21:54 +00:00
Anna Vítová
47d526cf5c fix: replace hardcoded paths for firstboot 2025-06-18 11:21:54 +00:00
Anna Vítová
9189a20e57 Wizard: add constants for fb paths 2025-06-18 11:21:54 +00:00
Anna Vítová
4667f6b0ac Wizard: cleanup request mapper 2025-06-18 11:21:54 +00:00
Anna Vítová
cd137fb055 Tets: add Kernel customizations test 2025-06-18 08:52:06 +00:00
regexowl
9478958085 Wizard: Fix wizard height
This adds a style to make sure the Wizard takes up the entire height of the page also in Firefox.
2025-06-18 08:06:52 +00:00
Katarina Sieklova
564c5461d4 Wizard: edit tests related to Compliance step 2025-06-18 07:25:00 +00:00
Katarina Sieklova
0cfe3dde30 Wizard: Fix "None" options in Selects for policies and Oscap profiles 2025-06-18 07:25:00 +00:00
Gianluca Zuccarelli
50d88e5949 AppCockpit: add dark mode helper
Import the cockpit-dark-theme helper from the cockpit project. This detects
when dark mode is enabled and automatically applies the correct styles.
2025-06-18 06:37:51 +00:00
Gianluca Zuccarelli
cb08466734 Makefile: update cockpit ref
To enable dark mode in cockpit we need to update the ref since both cockpit and
the image-builder frontend have been updated to PF6. The old ref was still from
before cockpit was migrated to PF6 and so the incorrect classes were being added.
2025-06-18 06:37:51 +00:00
regexowl
139dd367fe Manually revert "Wizard: temporarily disable OCI"
This reverts commit efed39d, manual revert was needed as the tile were migrated to cards in the meantime.
2025-06-17 12:33:05 +00:00
dependabot[bot]
2f8b550408 build(deps-dev): bump @types/node from 22.15.1 to 24.0.3
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 22.15.1 to 24.0.3.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-version: 24.0.3
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-17 10:01:49 +00:00
regexowl
31d259e988 devDeps: Bump msw from 2.7.5 to 2.10.2
This bumps msw from 2.7.5 to 2.10.2
2025-06-17 09:55:15 +00:00
dependabot[bot]
798d994ad0 build(deps-dev): bump stylelint-config-recommended-scss
Bumps [stylelint-config-recommended-scss](https://github.com/stylelint-scss/stylelint-config-recommended-scss) from 14.1.0 to 15.0.1.
- [Release notes](https://github.com/stylelint-scss/stylelint-config-recommended-scss/releases)
- [Changelog](https://github.com/stylelint-scss/stylelint-config-recommended-scss/blob/master/CHANGELOG.md)
- [Commits](https://github.com/stylelint-scss/stylelint-config-recommended-scss/compare/v14.1.0...v15.0.1)

---
updated-dependencies:
- dependency-name: stylelint-config-recommended-scss
  dependency-version: 15.0.1
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-17 09:48:58 +00:00
regexowl
8e504a527b Wizard: Make popover button independent on tab
Previously when the Included/Other repos popover button was clicked the tab changed as well. This makes the popover button independent on the selected tab.
2025-06-17 08:51:13 +00:00
regexowl
d6acce47a2 Wizard: Fix release dropdown's maxWidth
This applies the styling and removes `Warning: Unsupported style property max-width. Did you mean maxWidth?` from the test output.
2025-06-17 07:51:34 +00:00
regexowl
cb8c8a3d5c Wizard: Remove unused styling
We don't use the `Tile` component anymore. I believe we can remove this styling.
2025-06-17 07:50:25 +00:00
Gianluca Zuccarelli
c59cde1ab9 ImagesTable: fix main section alignment
With the change to PF6 the list of items in the table section was not
padded properly, we can fix this by wrapping the code in a `PageSection`
component.
2025-06-16 11:40:36 +00:00
regexowl
b465920b18 src: Remove image-builder.users.enabled flag
The Users customization is available in production now, the flag is no longer used.
2025-06-16 11:26:03 +00:00
regexowl
3312beb6e7 devDeps: Bump @typescript-eslint/eslint-plugin and @typescript-eslint/parser
The dependencies need to be bumped in tandem, both went from 8.32.1 to 8.34.0
2025-06-16 11:16:48 +00:00
regexowl
8f0c53138e Wizard: Sort activation keys list
This sorts the activation keys list by name.

The `sortFn` function was also updated to handle undefined values.
2025-06-16 10:54:13 +00:00
dependabot[bot]
4e19ccc5e9 build(deps-dev): bump brace-expansion from 1.1.11 to 1.1.12
Bumps [brace-expansion](https://github.com/juliangruber/brace-expansion) from 1.1.11 to 1.1.12.
- [Release notes](https://github.com/juliangruber/brace-expansion/releases)
- [Commits](https://github.com/juliangruber/brace-expansion/compare/1.1.11...v1.1.12)

---
updated-dependencies:
- dependency-name: brace-expansion
  dependency-version: 1.1.12
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-16 10:49:34 +00:00
dependabot[bot]
54b6877f95 build(deps-dev): bump @babel/core from 7.26.10 to 7.27.4
Bumps [@babel/core](https://github.com/babel/babel/tree/HEAD/packages/babel-core) from 7.26.10 to 7.27.4.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.27.4/packages/babel-core)

---
updated-dependencies:
- dependency-name: "@babel/core"
  dependency-version: 7.27.4
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-12 14:01:34 +00:00
Gianluca Zuccarelli
7f5013ef07 ReviewStep: fix alignment
Patternfly6 handles the grid for description lists slightly differntly
to Patternfly5. Add custom css to change the behaviour to match PF5 and
get the items in the review step to align properly
2025-06-12 13:29:58 +00:00
Gianluca Zuccarelli
1ba0f33240 ImageTable: fix blueprint list cards
The blueprint cards were missing their borders, making it unclear when
the item was selected or not.
2025-06-12 13:29:58 +00:00
Gianluca Zuccarelli
4932ba6909 Wizard: remove public clouds if none available
Fixes HMS-6136
2025-06-12 13:29:58 +00:00
Gianluca Zuccarelli
84bc0f92a0 cockpit: fix fonts
The fonts weren't getting loaded properly and cockpit was falling back
to `Helvetica`. This was particularly noticeable on the `ReviewStep` of
the `CreateImageWizard`.
2025-06-12 13:29:58 +00:00
Gianluca Zuccarelli
e7bf1d3540 cockpit: page section wrapper for entrypoint
Wrap the AppCockpit entrypoint in `Page` & `PageSection` wrappers so
that it is more consistent with other cockpit elements.
2025-06-12 13:29:58 +00:00
Gianluca Zuccarelli
40fe892dbf sharedComponents: tidy up image-builder-header
This commit simplifies and tidies up the shared ImageBuilderHeader
component by removing some of the `Flex` and `FlexItem` components.
Instead we can use the `actionComponents` from the `PageHeader`
component which takes care of some of the flex logic for us.
2025-06-12 13:29:58 +00:00
Gianluca Zuccarelli
3dd67c8f39 CloudStatus: disable analytics for on-prem
Analytics was enabled for on-prem which broke the images table. This
commit disables the analytics for the on-prem frontend.
2025-06-12 13:29:58 +00:00
Sanne Raymaekers
c29cee781f .github: run test workflows on merge_group
Make sure they work with merge queues.
2025-06-11 14:08:11 +00:00
Anna Vítová
2b37ee998e Blueprints: disable analytics for on-prem delete
Analytics broke on prem deletion of the blueprint.
2025-06-11 15:36:57 +02:00
Sanne Raymaekers
34c3dc614d .tekton: support merge queues
This lets konflux listen for push events to merge queue branches.

See https://konflux-ci.dev/docs/patterns/github-merge-queues/.
2025-06-11 14:33:46 +02:00
schutzbot
56b22eee53 Post release version bump
[skip ci]
2025-06-11 08:36:31 +00:00
dependabot[bot]
d7cd3ae285 build(deps): bump @sentry/webpack-plugin from 3.4.0 to 3.5.0
Bumps [@sentry/webpack-plugin](https://github.com/getsentry/sentry-javascript-bundler-plugins) from 3.4.0 to 3.5.0.
- [Release notes](https://github.com/getsentry/sentry-javascript-bundler-plugins/releases)
- [Changelog](https://github.com/getsentry/sentry-javascript-bundler-plugins/blob/main/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-javascript-bundler-plugins/compare/3.4.0...3.5.0)

---
updated-dependencies:
- dependency-name: "@sentry/webpack-plugin"
  dependency-version: 3.5.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-10 12:12:58 +02:00
red-hat-konflux[bot]
6b7d2cef3b Update Konflux references
Signed-off-by: red-hat-konflux <126015336+red-hat-konflux[bot]@users.noreply.github.com>
2025-06-10 11:28:47 +02:00
Lucas Garfield
9243d96646 Wizard: Fix overflow issues
The in-page wizard should have its footer fixed/sticky at the bottom of
the page with the main body area scrollable if necessary. This is the
default Patternfly behavior.

Currently, the entire wizard grows to fill its parent container. Setting
the overflow property in its parent div, which has the .chr-render class
defined by Insights Chrome, to overflow solves the issue and causes the
wizard to behave as it should.

The Insights Chrome team is hesitant to make this change in Insights
Chrome as it could break other apps layouts, and suggested using
Javascript to accomplish this:
https://github.com/RedHatInsights/insights-chrome/pull/3117#discussion_r2129045341
2025-06-09 06:47:49 -05:00
Anna Vítová
85d8850dce Wizard: remove else after return in firstboot 2025-06-09 13:19:55 +02:00
Anna Vítová
548a87bb44 Wizard: remove else after return in filesystem table 2025-06-09 13:19:55 +02:00
Anna Vítová
6ec6f33fda Wizard: fix datepicker reset glitch (MS-8610)
If the reset button is clicked, there is a glitch that shows error state
for a moment. This commit removes the glitch by setting the snapshot
date even for invalid values, and removes the workaround that was
previously added.
2025-06-09 12:20:23 +02:00
Gianluca Zuccarelli
a93a163afb CreateImageWizard: reusable TargetEnv card
Create a re-usable component for the Target Enviromnent cards. This
helps dry up the code a bit and should make it easier to edit going
forward.
2025-06-06 16:28:37 +02:00
Gianluca Zuccarelli
4668ed71ab CreateImageWizard: switch tiles to cards
Tiles are being deprecated in PatternFly. This commit switches from
tiles to cards for the Target Environments in the CreateImageWizard.

The keyboard test had to be removed since cards don't have a keydown
event.
2025-06-06 16:28:37 +02:00
Katarina Sieklova
362e5f7ca6 Wizard: indicate which user tab contains error
Fixes: #3097

Added exclamation marks to the tabs where the user contains error.
2025-06-05 17:45:59 +02:00
Katarina Sieklova
310f7a05cf Wizard: edit validation of port format in the Firewall step
Fixes #3269
2025-06-05 16:18:25 +02:00
Tom Koscielniak
42d7379a6c playwright: Cleanup fails gracefully if no BP is created
If no blueprint was created, the cleanup function fails gracefully without throwing error. Also includes a fix for a missing navigation to the IB landing page.
2025-06-05 14:17:18 +02:00
Anna Vítová
b98239bfe4 docs: Update docs to reflect current cockpit state 2025-06-03 19:36:00 +02:00
Anna Vítová
d4eced47c4 Makefile: fix install for first time users 2025-06-03 19:36:00 +02:00
Katarina Sieklova
3093310a6c Wizard: edit the timezone filter to fix whitespaces 2025-06-03 16:41:21 +02:00
Katarina Sieklova
6c244ba09e Wizard: fix filtering of Timezone
Fixes #3264
2025-06-03 16:41:21 +02:00
Gianluca Zuccarelli
72d09a09da schutzbot: fix update github status f42
There seems to be a change in behaviour from Fedora 41 to Fedora 42.
When a script is executed remotely a non-interactive session is created.
However, the user session with noTTY in Fedora F42 is picked up by the
`who`, `w` & `users` commands.

Since we run a remote script that checks for logged in users with the
`who` command, the `update_github_status.sh` script blocks and creates
an endless loop. This happens because the session only closes once the
remote script has finished executing.

The fix is a bit ugly, but we can filter this session out by checking
for user sessions that don't have a terminal device `?`.
2025-05-29 09:55:52 -05:00
regexowl
15b4aa01f9 dependabot: Temporarily increase PR limit
This increases the limit for open dependabot pull requests from 3 to 5. We currently have two bumps that will require manual bumping/migrations, leaving us with little room for any other dependencies.
2025-05-29 13:28:11 +02:00
regexowl
45b6a034db Wizard: Override max-width for release select 2025-05-28 09:08:52 -05:00
regexowl
93d994affa Blueprints: Make blueprint cards selectable
Highlighting the card on select got broken during the migration. This fixes the issue.
2025-05-28 09:08:52 -05:00
regexowl
7039db2585 Wizard: Spacing between footer buttons on Review step 2025-05-28 09:08:52 -05:00
regexowl
4f339aec8f Wizard: Update code after rebasing 2025-05-28 09:08:52 -05:00
regexowl
2c2b961d90 Wizard: Update spacing and colors used on Review step 2025-05-28 09:08:52 -05:00
regexowl
eed08effe1 Wizard: Update group labels spacing and "add button" color 2025-05-28 09:08:52 -05:00
regexowl
e60e15c6d6 test: Update tests 2025-05-28 09:08:52 -05:00
regexowl
3ac980e321 Wizard: Update <EmptyState> to match PF6 2025-05-28 09:08:52 -05:00
regexowl
4bb826ee06 Wizard: Set helper text variant 2025-05-28 09:08:52 -05:00
regexowl
49d05440b9 Wizard: Fix spacing of buttons in Wizard's footer 2025-05-28 09:08:52 -05:00
regexowl
91577343df src: Update <Content> tag components 2025-05-28 09:08:52 -05:00
regexowl
d98dd02fa3 test: Update tests 2025-05-28 09:08:52 -05:00
regexowl
2080425753 src: Update remaining v5 version slugs 2025-05-28 09:08:52 -05:00
regexowl
4312cca4dd src: Run class-name-updater
This runs `npx @patternfly/class-name-updater src --v6 --fix`.
2025-05-28 09:08:52 -05:00
regexowl
1fc1f0cb8d src: Run codemods and lint
Run `npx @patternfly/pf-codemods@latest src --v6 --fix` and lint autofix to get the bulk of the changes in.
2025-05-28 09:08:52 -05:00
regexowl
3617c0260d deps: Bump Patternfly and frontend-components versions
This bumps dependencies.
2025-05-28 09:08:52 -05:00
schutzbot
362bcc68ab Post release version bump
[skip ci]
2025-05-28 08:36:04 +00:00
321 changed files with 15287 additions and 47817 deletions

View file

@ -1,8 +0,0 @@
# Ignore programatically generated API slices
imageBuilderApi.ts
contentSourcesApi.ts
rhsmApi.ts
provisioningApi.ts
edgeApi.ts
complianceApi.ts
composerCloudApi.ts

View file

@ -1,65 +0,0 @@
extends: [
"plugin:jsx-a11y/recommended",
"@redhat-cloud-services/eslint-config-redhat-cloud-services",
"plugin:react/recommended",
"plugin:react-hooks/recommended",
"plugin:@typescript-eslint/recommended",
"plugin:react-redux/recommended"
]
globals:
insights: 'readonly'
shallow: readonly
render: 'readonly'
mount: 'readonly'
parser: "@typescript-eslint/parser"
parserOptions:
project: ["tsconfig.json"]
plugins:
- import
- disable-autofix
rules:
import/order:
- error
- groups:
- builtin
- external
- internal
- sibling
- parent
- index
alphabetize:
order: asc
caseInsensitive: true
newlines-between: always
pathGroups: # ensures the import of React is always on top
- pattern: react
group: builtin
position: before
pathGroupsExcludedImportTypes:
- react
prefer-const:
- error
- destructuring: any
no-console: error
eqeqeq: error
array-callback-return: warn
"@typescript-eslint/ban-ts-comment":
- error
- ts-expect-error: "allow-with-description"
ts-ignore: "allow-with-description"
ts-nocheck: true
ts-check: true
minimumDescriptionLength: 5
"@typescript-eslint/ban-types": off
disable-autofix/@typescript-eslint/no-unnecessary-condition: warn
# Temporarily disabled
jsx-a11y/no-autofocus: off
rulesdir/forbid-pf-relative-imports: off
overrides:
- files: ["src/tests/**/*.ts"]
extends: "plugin:testing-library/react"
- files: ["playwright/**/*.ts"]
extends: "plugin:playwright/recommended"
rules:
playwright/no-conditional-in-test: off
playwright/no-conditional-expect: off

1
.fmf/version Normal file
View file

@ -0,0 +1 @@
1

257
.forgejo/workflows/ci.yml Normal file
View file

@ -0,0 +1,257 @@
---
name: Debian Image Builder Frontend CI/CD
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
workflow_dispatch:
env:
NODE_VERSION: "18"
DEBIAN_FRONTEND: noninteractive
jobs:
build-and-test:
name: Build and Test Frontend
runs-on: ubuntu-latest
container:
image: node:18-bullseye
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js environment
run: |
node --version
npm --version
- name: Install build dependencies
run: |
apt-get update
apt-get install -y \
build-essential \
git \
ca-certificates \
python3
- name: Install Node.js dependencies
run: |
npm ci
npm run build || echo "Build script not found"
- name: Run tests
run: |
if [ -f package.json ] && npm run test; then
npm test
else
echo "No test script found, skipping tests"
fi
- name: Run linting
run: |
if [ -f package.json ] && npm run lint; then
npm run lint
else
echo "No lint script found, skipping linting"
fi
- name: Build production bundle
run: |
if [ -f package.json ] && npm run build; then
npm run build
else
echo "No build script found"
fi
- name: Upload build artifacts
uses: actions/upload-artifact@v4
with:
name: frontend-build
path: |
dist/
build/
retention-days: 30
package:
name: Package Frontend
runs-on: ubuntu-latest
container:
image: node:18-bullseye
needs: build-and-test
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js environment
run: |
node --version
npm --version
- name: Install build dependencies
run: |
apt-get update
apt-get install -y \
build-essential \
devscripts \
debhelper \
git \
ca-certificates \
python3
- name: Install Node.js dependencies
run: npm ci
- name: Build production bundle
run: |
if [ -f package.json ] && npm run build; then
npm run build
else
echo "No build script found"
fi
- name: Create debian directory
run: |
mkdir -p debian
cat > debian/control << EOF
Source: debian-image-builder-frontend
Section: web
Priority: optional
Maintainer: Debian Forge Team <team@debian-forge.org>
Build-Depends: debhelper (>= 13), nodejs, npm, git, ca-certificates
Standards-Version: 4.6.2
Package: debian-image-builder-frontend
Architecture: all
Depends: \${misc:Depends}, nodejs, nginx
Description: Debian Image Builder Frontend
Web-based frontend for Debian Image Builder with Cockpit integration.
Provides a user interface for managing image builds, blueprints,
and system configurations through a modern React application.
EOF
cat > debian/rules << EOF
#!/usr/bin/make -f
%:
dh \$@
override_dh_auto_install:
dh_auto_install
mkdir -p debian/debian-image-builder-frontend/usr/share/debian-image-builder-frontend
mkdir -p debian/debian-image-builder-frontend/etc/nginx/sites-available
mkdir -p debian/debian-image-builder-frontend/etc/cockpit
# Copy built frontend files
if [ -d dist ]; then
cp -r dist/* debian/debian-image-builder-frontend/usr/share/debian-image-builder-frontend/
elif [ -d build ]; then
cp -r build/* debian/debian-image-builder-frontend/usr/share/debian-image-builder-frontend/
fi
# Copy source files for development
cp -r src debian/debian-image-builder-frontend/usr/share/debian-image-builder-frontend/
cp package.json debian/debian-image-builder-frontend/usr/share/debian-image-builder-frontend/
# Create nginx configuration
cat > debian/debian-image-builder-frontend/etc/nginx/sites-available/debian-image-builder-frontend << 'NGINX_EOF'
server {
listen 80;
server_name localhost;
root /usr/share/debian-image-builder-frontend;
index index.html;
location / {
try_files \$uri \$uri/ /index.html;
}
location /api/ {
proxy_pass http://localhost:8080/;
proxy_set_header Host \$host;
proxy_set_header X-Real-IP \$remote_addr;
}
}
NGINX_EOF
# Create cockpit manifest
cat > debian/debian-image-builder-frontend/etc/cockpit/debian-image-builder.manifest << 'COCKPIT_EOF'
{
"version": 1,
"manifest": {
"name": "debian-image-builder",
"version": "1.0.0",
"title": "Debian Image Builder",
"description": "Build and manage Debian atomic images",
"url": "/usr/share/debian-image-builder-frontend",
"icon": "debian-logo",
"requires": {
"cockpit": ">= 200"
}
}
}
COCKPIT_EOF
EOF
cat > debian/changelog << EOF
debian-image-builder-frontend (1.0.0-1) unstable; urgency=medium
* Initial release
* Debian Image Builder Frontend with Cockpit integration
* React-based web interface for image management
-- Debian Forge Team <team@debian-forge.org> $(date -R)
EOF
cat > debian/compat << EOF
13
EOF
chmod +x debian/rules
- name: Build Debian package
run: |
dpkg-buildpackage -us -uc -b
ls -la ../*.deb
- name: Upload Debian package
uses: actions/upload-artifact@v4
with:
name: debian-image-builder-frontend-deb
path: ../*.deb
retention-days: 30
cockpit-integration:
name: Test Cockpit Integration
runs-on: ubuntu-latest
container:
image: node:18-bullseye
needs: build-and-test
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js environment
run: |
node --version
npm --version
- name: Install Node.js dependencies
run: npm ci
- name: Test cockpit integration
run: |
echo "Testing Cockpit integration..."
if [ -d cockpit ]; then
echo "Cockpit directory found:"
ls -la cockpit/
else
echo "No cockpit directory found"
fi
if [ -f package.json ]; then
echo "Package.json scripts:"
npm run
fi

View file

@ -0,0 +1,257 @@
---
name: Debian Image Builder Frontend CI/CD
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
workflow_dispatch:
env:
NODE_VERSION: "18"
DEBIAN_FRONTEND: noninteractive
jobs:
build-and-test:
name: Build and Test Frontend
runs-on: ubuntu-latest
container:
image: node:18-bullseye
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js environment
run: |
node --version
npm --version
- name: Install build dependencies
run: |
apt-get update
apt-get install -y \
build-essential \
git \
ca-certificates \
python3
- name: Install Node.js dependencies
run: |
npm ci
npm run build || echo "Build script not found"
- name: Run tests
run: |
if [ -f package.json ] && npm run test; then
npm test
else
echo "No test script found, skipping tests"
fi
- name: Run linting
run: |
if [ -f package.json ] && npm run lint; then
npm run lint
else
echo "No lint script found, skipping linting"
fi
- name: Build production bundle
run: |
if [ -f package.json ] && npm run build; then
npm run build
else
echo "No build script found"
fi
- name: Upload build artifacts
uses: actions/upload-artifact@v4
with:
name: frontend-build
path: |
dist/
build/
retention-days: 30
package:
name: Package Frontend
runs-on: ubuntu-latest
container:
image: node:18-bullseye
needs: build-and-test
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js environment
run: |
node --version
npm --version
- name: Install build dependencies
run: |
apt-get update
apt-get install -y \
build-essential \
devscripts \
debhelper \
git \
ca-certificates \
python3
- name: Install Node.js dependencies
run: npm ci
- name: Build production bundle
run: |
if [ -f package.json ] && npm run build; then
npm run build
else
echo "No build script found"
fi
- name: Create debian directory
run: |
mkdir -p debian
cat > debian/control << EOF
Source: debian-image-builder-frontend
Section: web
Priority: optional
Maintainer: Debian Forge Team <team@debian-forge.org>
Build-Depends: debhelper (>= 13), nodejs, npm, git, ca-certificates
Standards-Version: 4.6.2
Package: debian-image-builder-frontend
Architecture: all
Depends: \${misc:Depends}, nodejs, nginx
Description: Debian Image Builder Frontend
Web-based frontend for Debian Image Builder with Cockpit integration.
Provides a user interface for managing image builds, blueprints,
and system configurations through a modern React application.
EOF
cat > debian/rules << EOF
#!/usr/bin/make -f
%:
dh \$@
override_dh_auto_install:
dh_auto_install
mkdir -p debian/debian-image-builder-frontend/usr/share/debian-image-builder-frontend
mkdir -p debian/debian-image-builder-frontend/etc/nginx/sites-available
mkdir -p debian/debian-image-builder-frontend/etc/cockpit
# Copy built frontend files
if [ -d dist ]; then
cp -r dist/* debian/debian-image-builder-frontend/usr/share/debian-image-builder-frontend/
elif [ -d build ]; then
cp -r build/* debian/debian-image-builder-frontend/usr/share/debian-image-builder-frontend/
fi
# Copy source files for development
cp -r src debian/debian-image-builder-frontend/usr/share/debian-image-builder-frontend/
cp package.json debian/debian-image-builder-frontend/usr/share/debian-image-builder-frontend/
# Create nginx configuration
cat > debian/debian-image-builder-frontend/etc/nginx/sites-available/debian-image-builder-frontend << 'NGINX_EOF'
server {
listen 80;
server_name localhost;
root /usr/share/debian-image-builder-frontend;
index index.html;
location / {
try_files \$uri \$uri/ /index.html;
}
location /api/ {
proxy_pass http://localhost:8080/;
proxy_set_header Host \$host;
proxy_set_header X-Real-IP \$remote_addr;
}
}
NGINX_EOF
# Create cockpit manifest
cat > debian/debian-image-builder-frontend/etc/cockpit/debian-image-builder.manifest << 'COCKPIT_EOF'
{
"version": 1,
"manifest": {
"name": "debian-image-builder",
"version": "1.0.0",
"title": "Debian Image Builder",
"description": "Build and manage Debian atomic images",
"url": "/usr/share/debian-image-builder-frontend",
"icon": "debian-logo",
"requires": {
"cockpit": ">= 200"
}
}
}
COCKPIT_EOF
EOF
cat > debian/changelog << EOF
debian-image-builder-frontend (1.0.0-1) unstable; urgency=medium
* Initial release
* Debian Image Builder Frontend with Cockpit integration
* React-based web interface for image management
-- Debian Forge Team <team@debian-forge.org> $(date -R)
EOF
cat > debian/compat << EOF
13
EOF
chmod +x debian/rules
- name: Build Debian package
run: |
dpkg-buildpackage -us -uc -b
ls -la ../*.deb
- name: Upload Debian package
uses: actions/upload-artifact@v4
with:
name: debian-image-builder-frontend-deb
path: ../*.deb
retention-days: 30
cockpit-integration:
name: Test Cockpit Integration
runs-on: ubuntu-latest
container:
image: node:18-bullseye
needs: build-and-test
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js environment
run: |
node --version
npm --version
- name: Install Node.js dependencies
run: npm ci
- name: Test cockpit integration
run: |
echo "Testing Cockpit integration..."
if [ -d cockpit ]; then
echo "Cockpit directory found:"
ls -la cockpit/
else
echo "No cockpit directory found"
fi
if [ -f package.json ]; then
echo "Package.json scripts:"
npm run
fi

View file

@ -5,35 +5,79 @@ on:
branches: [ "main" ]
push:
branches: [ "main" ]
merge_group:
concurrency:
group: ${{github.workflow}}-${{ github.ref }}
cancel-in-progress: true
jobs:
dev-check:
build:
name: Build Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Use Node.js 20
- name: Use Node.js 22
uses: actions/setup-node@v4
with:
node-version: 20
node-version: 22
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run build
run: npm run build
lint-checks:
name: ESLint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Use Node.js 22
uses: actions/setup-node@v4
with:
node-version: 22
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run lint check
run: npm run lint
circular-dependencies:
name: Circular Dependencies Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Use Node.js 22
uses: actions/setup-node@v4
with:
node-version: 22
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Check for circular dependencies
run: npm run circular
api-changes:
name: Manual API Changes Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Use Node.js 22
uses: actions/setup-node@v4
with:
node-version: 22
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Check for manual changes to API
run: npm run api:generate && [ -z "$(git status --porcelain=v1 2>/dev/null)" ] && echo "✓ No manual API changes." || echo "✗ API manually changed, please refer to the README for the procedure to follow for programmatically generated API endpoints." && [ -z "$(git status --porcelain=v1 2>/dev/null)" ]
- name: Check for circular dependencies
run: npm run circular
- name: Run build
run: npm run build
- name: Run lint check
run: npm run lint
- name: Run unit tests
run: npm run test:coverage
- name: Run unit tests with cockpit
run: npm run test:cockpit
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./coverage/junit.xml
verbose: true
run: |
npm run api
if [ -n "$(git status --porcelain)" ]; then
echo
echo "✗ API manually changed, please refer to the README for the procedure to follow for programmatically generated API endpoints."
exit 1
else
echo
echo "✓ No manual API changes."
exit 0
fi

View file

@ -4,6 +4,13 @@ on:
pull_request:
types: [opened, reopened, synchronize, labeled, unlabeled]
workflow_dispatch:
merge_group:
# this prevents multiple jobs from the same pr
# running when new changes are pushed.
concurrency:
group: ${{github.workflow}}-${{ github.ref }}
cancel-in-progress: true
jobs:
playwright-tests:
@ -30,7 +37,7 @@ jobs:
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 20
node-version: 22
cache: "npm"
- name: Install front-end dependencies

View file

@ -6,6 +6,7 @@ on:
types: [opened, synchronize, reopened, edited]
issue_comment:
types: [created]
merge_group:
jobs:
pr-best-practices:

View file

@ -13,10 +13,10 @@ jobs:
# artefact name.
- uses: actions/checkout@v4
- name: Use Node.js 20
- name: Use Node.js 22
uses: actions/setup-node@v4
with:
node-version: 20
node-version: 22
cache: 'npm'
- name: Install dependencies

View file

@ -8,6 +8,7 @@ jobs:
stale:
runs-on: ubuntu-latest
permissions:
actions: write # needed to clean up the saved action state
issues: write
pull-requests: write
steps:

51
.github/workflows/unit-tests.yml vendored Normal file
View file

@ -0,0 +1,51 @@
name: Unit Tests
on:
pull_request:
branches: [ "main" ]
push:
branches: [ "main" ]
merge_group:
# this prevents multiple jobs from the same pr
# running when new changes are pushed.
concurrency:
group: ${{github.workflow}}-${{ github.ref }}
cancel-in-progress: true
jobs:
unit-tests:
name: Service Tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Use Node.js 22
uses: actions/setup-node@v4
with:
node-version: 22
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run unit tests
run: npm run test:coverage
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./coverage/junit.xml
verbose: true
cockpit-unit-tests:
name: Cockpit Tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Use Node.js 22
uses: actions/setup-node@v4
with:
node-version: 22
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run unit tests with cockpit
run: npm run test:cockpit

51
.github/workflows/update-apis.yml vendored Normal file
View file

@ -0,0 +1,51 @@
# This action checks API updates every day at 5:00 UTC.
name: Update API code generation
on:
workflow_dispatch:
schedule:
- cron: "0 5 * * *"
jobs:
update-api:
name: "Update API definitions"
if: github.repository == 'osbuild/image-builder-frontend'
runs-on: ubuntu-latest
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@v4
- name: Use Node.js 22
uses: actions/setup-node@v4
with:
node-version: 22
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Mark the working directory as safe for git
run: git config --global --add safe.directory "$(pwd)"
- name: Run API code generation
run: npm run api
- name: Check if there are any changes
run: |
if [ "$(git status --porcelain)" ]; then
echo
echo "API codegen is up-to-date"
exit "0"
fi
- name: Create Pull Request
uses: peter-evans/create-pull-request@v6
with:
branch: update-apis
delete-branch: true
title: "api: regenerate api code generation"
commit-message: "api: regenerate api code generation"
body: Update api code generation
token: ${{ secrets.SCHUTZBOT_GITHUB_ACCESS_TOKEN }}
author: schutzbot <schutzbot@gmail.com>

1
.gitignore vendored
View file

@ -48,3 +48,4 @@ rpmbuild
/blob-report/
/playwright/.cache/
.env
.auth

View file

@ -32,8 +32,7 @@ test:
- RUNNER:
- aws/fedora-41-x86_64
- aws/fedora-42-x86_64
- aws/rhel-9.6-nightly-x86_64
- aws/rhel-10.0-nightly-x86_64
- aws/rhel-10.1-nightly-x86_64
INTERNAL_NETWORK: ["true"]
finish:

View file

@ -1,9 +0,0 @@
{
"semi": true,
"tabWidth": 2,
"singleQuote": false,
"jsxSingleQuote": false,
"bracketSpacing": true,
"tsxSingleQuote": true,
"tsSingleQuote": true
}

View file

@ -7,9 +7,8 @@ metadata:
build.appstudio.redhat.com/pull_request_number: '{{pull_request_number}}'
build.appstudio.redhat.com/target_branch: '{{target_branch}}'
pipelinesascode.tekton.dev/max-keep-runs: "3"
pipelinesascode.tekton.dev/on-cel-expression: event == "pull_request" && target_branch
== "main"
creationTimestamp: null
pipelinesascode.tekton.dev/on-cel-expression: (event == "pull_request" && target_branch == "main") || (event == "push" && target_branch.startsWith("gh-readonly-queue/main/"))
creationTimestamp:
labels:
appstudio.openshift.io/application: insights-image-builder
appstudio.openshift.io/component: image-builder-frontend
@ -46,7 +45,7 @@ spec:
- name: name
value: show-sbom
- name: bundle
value: quay.io/konflux-ci/tekton-catalog/task-show-sbom:0.1@sha256:002f7c8c1d2f9e09904035da414aba1188ae091df0ea9532cd997be05e73d594
value: quay.io/konflux-ci/tekton-catalog/task-show-sbom:0.1@sha256:beb0616db051952b4b861dd8c3e00fa1c0eccbd926feddf71194d3bb3ace9ce7
- name: kind
value: task
resolver: bundles
@ -65,7 +64,7 @@ spec:
- name: name
value: summary
- name: bundle
value: quay.io/konflux-ci/tekton-catalog/task-summary:0.2@sha256:76075b709fa06ed824cbc84f41448b397b85bfde1cf9809395ba6d286f5b7cbd
value: quay.io/konflux-ci/tekton-catalog/task-summary:0.2@sha256:3f6e8513cbd70f0416eb6c6f2766973a754778526125ff33d8e3633def917091
- name: kind
value: task
resolver: bundles
@ -84,13 +83,11 @@ spec:
name: output-image
type: string
- default: .
description: Path to the source code of an application's component from where
to build image.
description: Path to the source code of an application's component from where to build image.
name: path-context
type: string
- default: Dockerfile
description: Path to the Dockerfile inside the context specified by parameter
path-context
description: Path to the Dockerfile inside the context specified by parameter path-context
name: dockerfile
type: string
- default: "false"
@ -110,8 +107,7 @@ spec:
name: prefetch-input
type: string
- default: ""
description: Image tag expiration time, time values could be something like
1h, 2d, 3w for hours, days, and weeks, respectively.
description: Image tag expiration time, time values could be something like 1h, 2d, 3w for hours, days, and weeks, respectively.
name: image-expires-after
- default: "false"
description: Build a source image.
@ -156,7 +152,7 @@ spec:
- name: name
value: init
- name: bundle
value: quay.io/konflux-ci/tekton-catalog/task-init:0.2@sha256:7a24924417260b7094541caaedd2853dc8da08d4bb0968f710a400d3e8062063
value: quay.io/konflux-ci/tekton-catalog/task-init:0.2@sha256:08e18a4dc5f947c1d20e8353a19d013144bea87b72f67236b165dd4778523951
- name: kind
value: task
resolver: bundles
@ -173,7 +169,7 @@ spec:
- name: name
value: git-clone
- name: bundle
value: quay.io/konflux-ci/tekton-catalog/task-git-clone:0.1@sha256:3ced9a6b9d8520773d3ffbf062190515a362ecda11e72f56e38e4dd980294b57
value: quay.io/konflux-ci/tekton-catalog/task-git-clone:0.1@sha256:7939000e2f92fc8b5d2c4ee4ba9000433c5aa7700d2915a1d4763853d5fd1fd4
- name: kind
value: task
resolver: bundles
@ -198,7 +194,7 @@ spec:
- name: name
value: prefetch-dependencies
- name: bundle
value: quay.io/konflux-ci/tekton-catalog/task-prefetch-dependencies:0.2@sha256:08eec5362aa774347f08a210531a6901020778a08ca921b02758a91b5b2e1357
value: quay.io/konflux-ci/tekton-catalog/task-prefetch-dependencies:0.2@sha256:ce5f2485d759221444357fe38276be876fc54531651e50dcfc0f84b34909d760
- name: kind
value: task
resolver: bundles
@ -242,7 +238,7 @@ spec:
- name: name
value: buildah
- name: bundle
value: quay.io/konflux-ci/tekton-catalog/task-buildah:0.4@sha256:c777fdb0947aff3e4ac29a93ed6358c6f7994e6b150154427646788ec773c440
value: quay.io/konflux-ci/tekton-catalog/task-buildah:0.4@sha256:7782cb7462130de8e8839a58dd15ed78e50938d718b51375267679c6044b4367
- name: kind
value: task
resolver: bundles
@ -274,7 +270,7 @@ spec:
- name: name
value: build-image-index
- name: bundle
value: quay.io/konflux-ci/tekton-catalog/task-build-image-index:0.1@sha256:1b357f2ed430d18a009740a1783dd15af70ce1e23dc6254da1a83e9ec595d5be
value: quay.io/konflux-ci/tekton-catalog/task-build-image-index:0.1@sha256:72f77a8c62f9d6f69ab5c35170839e4b190026e6cc3d7d4ceafa7033fc30ad7b
- name: kind
value: task
resolver: bundles
@ -286,7 +282,9 @@ spec:
- name: build-source-image
params:
- name: BINARY_IMAGE
value: $(params.output-image)
value: $(tasks.build-image-index.results.IMAGE_URL)
- name: BINARY_IMAGE_DIGEST
value: $(tasks.build-image-index.results.IMAGE_DIGEST)
runAfter:
- build-image-index
taskRef:
@ -294,7 +292,7 @@ spec:
- name: name
value: source-build
- name: bundle
value: quay.io/konflux-ci/tekton-catalog/task-source-build:0.2@sha256:11029fa30652154f772b44132f8a116382c136a6223e8f9576137f99b9901dcb
value: quay.io/konflux-ci/tekton-catalog/task-source-build:0.3@sha256:96ed9431854ecf9805407dca77b063abdf7aba1b3b9d1925a5c6145c6b7e95fd
- name: kind
value: task
resolver: bundles
@ -323,7 +321,7 @@ spec:
- name: name
value: sast-shell-check
- name: bundle
value: quay.io/konflux-ci/tekton-catalog/task-sast-shell-check:0.1@sha256:188a4f6a582ac43d4de46c3998ded3c2a8ee237fb0604d90559a3b6e0aa62b0f
value: quay.io/konflux-ci/tekton-catalog/task-sast-shell-check:0.1@sha256:4a63982791a1a68f560c486f524ef5b9fdbeee0c16fe079eee3181a2cfd1c1bf
- name: kind
value: task
resolver: bundles
@ -339,6 +337,8 @@ spec:
params:
- name: image-url
value: $(tasks.build-image-index.results.IMAGE_URL)
- name: image-digest
value: $(tasks.build-image-index.results.IMAGE_DIGEST)
runAfter:
- build-image-index
taskRef:
@ -346,7 +346,7 @@ spec:
- name: name
value: sast-unicode-check
- name: bundle
value: quay.io/konflux-ci/tekton-catalog/task-sast-unicode-check:0.2@sha256:e4a5215b45b1886a185a9db8ab392f8440c2b0848f76d719885637cf8d2628ed
value: quay.io/konflux-ci/tekton-catalog/task-sast-unicode-check:0.3@sha256:bec18fa5e82e801c3f267f29bf94535a5024e72476f2b27cca7271d506abb5ad
- name: kind
value: task
resolver: bundles
@ -371,7 +371,7 @@ spec:
- name: name
value: deprecated-image-check
- name: bundle
value: quay.io/konflux-ci/tekton-catalog/task-deprecated-image-check:0.5@sha256:ecd33669676b3a193ff4c2c6223cb912cc1b0cf5cc36e080eaec7718500272cf
value: quay.io/konflux-ci/tekton-catalog/task-deprecated-image-check:0.5@sha256:1d07d16810c26713f3d875083924d93697900147364360587ccb5a63f2c31012
- name: kind
value: task
resolver: bundles
@ -393,7 +393,7 @@ spec:
- name: name
value: clair-scan
- name: bundle
value: quay.io/konflux-ci/tekton-catalog/task-clair-scan:0.2@sha256:878ae247ffc58d95a9ac68e4d658ef91ef039363e03e65a386bc0ead02d9d7d8
value: quay.io/konflux-ci/tekton-catalog/task-clair-scan:0.2@sha256:893ffa3ce26b061e21bb4d8db9ef7ed4ddd4044fe7aa5451ef391034da3ff759
- name: kind
value: task
resolver: bundles
@ -413,7 +413,7 @@ spec:
- name: name
value: ecosystem-cert-preflight-checks
- name: bundle
value: quay.io/konflux-ci/tekton-catalog/task-ecosystem-cert-preflight-checks:0.2@sha256:302828e9d7abc72b8a44fb2b9be068f86c982d8e5f4550b8bf654571d6361ee8
value: quay.io/konflux-ci/tekton-catalog/task-ecosystem-cert-preflight-checks:0.2@sha256:1f151e00f7fc427654b7b76045a426bb02fe650d192ffe147a304d2184787e38
- name: kind
value: task
resolver: bundles
@ -435,7 +435,7 @@ spec:
- name: name
value: sast-snyk-check
- name: bundle
value: quay.io/konflux-ci/tekton-catalog/task-sast-snyk-check:0.4@sha256:15a945b054b245f6713845dd6ae813d373c9f9cbac386d7382964f1b70ae3076
value: quay.io/konflux-ci/tekton-catalog/task-sast-snyk-check:0.4@sha256:351f2dce893159b703e9b6d430a2450b3df9967cb9bd3adb46852df8ccfe4c0d
- name: kind
value: task
resolver: bundles
@ -460,7 +460,7 @@ spec:
- name: name
value: clamav-scan
- name: bundle
value: quay.io/konflux-ci/tekton-catalog/task-clamav-scan:0.2@sha256:98d94290d6f21b6e231485326e3629bbcdec75c737b84e05ac9eac78f9a2c8b4
value: quay.io/konflux-ci/tekton-catalog/task-clamav-scan:0.3@sha256:cce2dfcc5bd6e91ee54aacdadad523b013eeae5cdaa7f6a4624b8cbcc040f439
- name: kind
value: task
resolver: bundles
@ -471,8 +471,10 @@ spec:
- "false"
- name: apply-tags
params:
- name: IMAGE
- name: IMAGE_URL
value: $(tasks.build-image-index.results.IMAGE_URL)
- name: IMAGE_DIGEST
value: $(tasks.build-image-index.results.IMAGE_DIGEST)
runAfter:
- build-image-index
taskRef:
@ -480,7 +482,7 @@ spec:
- name: name
value: apply-tags
- name: bundle
value: quay.io/konflux-ci/tekton-catalog/task-apply-tags:0.1@sha256:9d9871143ab3a818f681488be6074f5b2f892c1843795a46f6daf3f5487e72d1
value: quay.io/konflux-ci/tekton-catalog/task-apply-tags:0.2@sha256:70881c97a4c51ee1f4d023fa1110e0bdfcfd2f51d9a261fa543c3862b9a4eee9
- name: kind
value: task
resolver: bundles
@ -501,7 +503,7 @@ spec:
- name: name
value: push-dockerfile
- name: bundle
value: quay.io/konflux-ci/tekton-catalog/task-push-dockerfile:0.1@sha256:c82189e5d331e489cff99f0399f133fd3fad08921bea86747dfa379d1b5c748d
value: quay.io/konflux-ci/tekton-catalog/task-push-dockerfile:0.1@sha256:d5cb22a833be51dd72a872cac8bfbe149e8ad34da7cb48a643a1e613447a1f9d
- name: kind
value: task
resolver: bundles
@ -521,7 +523,7 @@ spec:
- name: name
value: rpms-signature-scan
- name: bundle
value: quay.io/konflux-ci/tekton-catalog/task-rpms-signature-scan:0.2@sha256:297c2d8928aa3b114fcb1ba5d9da8b10226b68fed30706e78a6a5089c6cd30e3
value: quay.io/konflux-ci/tekton-catalog/task-rpms-signature-scan:0.2@sha256:1b6c20ab3dbfb0972803d3ebcb2fa72642e59400c77bd66dfd82028bdd09e120
- name: kind
value: task
resolver: bundles
@ -542,7 +544,7 @@ spec:
- name: workspace
volumeClaimTemplate:
metadata:
creationTimestamp: null
creationTimestamp:
spec:
accessModes:
- ReadWriteOnce

View file

@ -6,9 +6,8 @@ metadata:
build.appstudio.redhat.com/commit_sha: '{{revision}}'
build.appstudio.redhat.com/target_branch: '{{target_branch}}'
pipelinesascode.tekton.dev/max-keep-runs: "3"
pipelinesascode.tekton.dev/on-cel-expression: event == "push" && target_branch
== "main"
creationTimestamp: null
pipelinesascode.tekton.dev/on-cel-expression: event == "push" && target_branch == "main"
creationTimestamp:
labels:
appstudio.openshift.io/application: insights-image-builder
appstudio.openshift.io/component: image-builder-frontend
@ -43,7 +42,7 @@ spec:
- name: name
value: show-sbom
- name: bundle
value: quay.io/konflux-ci/tekton-catalog/task-show-sbom:0.1@sha256:002f7c8c1d2f9e09904035da414aba1188ae091df0ea9532cd997be05e73d594
value: quay.io/konflux-ci/tekton-catalog/task-show-sbom:0.1@sha256:beb0616db051952b4b861dd8c3e00fa1c0eccbd926feddf71194d3bb3ace9ce7
- name: kind
value: task
resolver: bundles
@ -62,7 +61,7 @@ spec:
- name: name
value: summary
- name: bundle
value: quay.io/konflux-ci/tekton-catalog/task-summary:0.2@sha256:76075b709fa06ed824cbc84f41448b397b85bfde1cf9809395ba6d286f5b7cbd
value: quay.io/konflux-ci/tekton-catalog/task-summary:0.2@sha256:3f6e8513cbd70f0416eb6c6f2766973a754778526125ff33d8e3633def917091
- name: kind
value: task
resolver: bundles
@ -81,13 +80,11 @@ spec:
name: output-image
type: string
- default: .
description: Path to the source code of an application's component from where
to build image.
description: Path to the source code of an application's component from where to build image.
name: path-context
type: string
- default: Dockerfile
description: Path to the Dockerfile inside the context specified by parameter
path-context
description: Path to the Dockerfile inside the context specified by parameter path-context
name: dockerfile
type: string
- default: "false"
@ -107,8 +104,7 @@ spec:
name: prefetch-input
type: string
- default: ""
description: Image tag expiration time, time values could be something like
1h, 2d, 3w for hours, days, and weeks, respectively.
description: Image tag expiration time, time values could be something like 1h, 2d, 3w for hours, days, and weeks, respectively.
name: image-expires-after
- default: "false"
description: Build a source image.
@ -153,7 +149,7 @@ spec:
- name: name
value: init
- name: bundle
value: quay.io/konflux-ci/tekton-catalog/task-init:0.2@sha256:7a24924417260b7094541caaedd2853dc8da08d4bb0968f710a400d3e8062063
value: quay.io/konflux-ci/tekton-catalog/task-init:0.2@sha256:08e18a4dc5f947c1d20e8353a19d013144bea87b72f67236b165dd4778523951
- name: kind
value: task
resolver: bundles
@ -170,7 +166,7 @@ spec:
- name: name
value: git-clone
- name: bundle
value: quay.io/konflux-ci/tekton-catalog/task-git-clone:0.1@sha256:3ced9a6b9d8520773d3ffbf062190515a362ecda11e72f56e38e4dd980294b57
value: quay.io/konflux-ci/tekton-catalog/task-git-clone:0.1@sha256:7939000e2f92fc8b5d2c4ee4ba9000433c5aa7700d2915a1d4763853d5fd1fd4
- name: kind
value: task
resolver: bundles
@ -195,7 +191,7 @@ spec:
- name: name
value: prefetch-dependencies
- name: bundle
value: quay.io/konflux-ci/tekton-catalog/task-prefetch-dependencies:0.2@sha256:08eec5362aa774347f08a210531a6901020778a08ca921b02758a91b5b2e1357
value: quay.io/konflux-ci/tekton-catalog/task-prefetch-dependencies:0.2@sha256:ce5f2485d759221444357fe38276be876fc54531651e50dcfc0f84b34909d760
- name: kind
value: task
resolver: bundles
@ -239,7 +235,7 @@ spec:
- name: name
value: buildah
- name: bundle
value: quay.io/konflux-ci/tekton-catalog/task-buildah:0.4@sha256:c777fdb0947aff3e4ac29a93ed6358c6f7994e6b150154427646788ec773c440
value: quay.io/konflux-ci/tekton-catalog/task-buildah:0.4@sha256:7782cb7462130de8e8839a58dd15ed78e50938d718b51375267679c6044b4367
- name: kind
value: task
resolver: bundles
@ -271,7 +267,7 @@ spec:
- name: name
value: build-image-index
- name: bundle
value: quay.io/konflux-ci/tekton-catalog/task-build-image-index:0.1@sha256:1b357f2ed430d18a009740a1783dd15af70ce1e23dc6254da1a83e9ec595d5be
value: quay.io/konflux-ci/tekton-catalog/task-build-image-index:0.1@sha256:72f77a8c62f9d6f69ab5c35170839e4b190026e6cc3d7d4ceafa7033fc30ad7b
- name: kind
value: task
resolver: bundles
@ -283,7 +279,9 @@ spec:
- name: build-source-image
params:
- name: BINARY_IMAGE
value: $(params.output-image)
value: $(tasks.build-image-index.results.IMAGE_URL)
- name: BINARY_IMAGE_DIGEST
value: $(tasks.build-image-index.results.IMAGE_DIGEST)
runAfter:
- build-image-index
taskRef:
@ -291,7 +289,7 @@ spec:
- name: name
value: source-build
- name: bundle
value: quay.io/konflux-ci/tekton-catalog/task-source-build:0.2@sha256:11029fa30652154f772b44132f8a116382c136a6223e8f9576137f99b9901dcb
value: quay.io/konflux-ci/tekton-catalog/task-source-build:0.3@sha256:96ed9431854ecf9805407dca77b063abdf7aba1b3b9d1925a5c6145c6b7e95fd
- name: kind
value: task
resolver: bundles
@ -320,7 +318,7 @@ spec:
- name: name
value: sast-shell-check
- name: bundle
value: quay.io/konflux-ci/tekton-catalog/task-sast-shell-check:0.1@sha256:188a4f6a582ac43d4de46c3998ded3c2a8ee237fb0604d90559a3b6e0aa62b0f
value: quay.io/konflux-ci/tekton-catalog/task-sast-shell-check:0.1@sha256:4a63982791a1a68f560c486f524ef5b9fdbeee0c16fe079eee3181a2cfd1c1bf
- name: kind
value: task
resolver: bundles
@ -336,6 +334,8 @@ spec:
params:
- name: image-url
value: $(tasks.build-image-index.results.IMAGE_URL)
- name: image-digest
value: $(tasks.build-image-index.results.IMAGE_DIGEST)
runAfter:
- build-image-index
taskRef:
@ -343,7 +343,7 @@ spec:
- name: name
value: sast-unicode-check
- name: bundle
value: quay.io/konflux-ci/tekton-catalog/task-sast-unicode-check:0.2@sha256:e4a5215b45b1886a185a9db8ab392f8440c2b0848f76d719885637cf8d2628ed
value: quay.io/konflux-ci/tekton-catalog/task-sast-unicode-check:0.3@sha256:bec18fa5e82e801c3f267f29bf94535a5024e72476f2b27cca7271d506abb5ad
- name: kind
value: task
resolver: bundles
@ -368,7 +368,7 @@ spec:
- name: name
value: deprecated-image-check
- name: bundle
value: quay.io/konflux-ci/tekton-catalog/task-deprecated-image-check:0.5@sha256:ecd33669676b3a193ff4c2c6223cb912cc1b0cf5cc36e080eaec7718500272cf
value: quay.io/konflux-ci/tekton-catalog/task-deprecated-image-check:0.5@sha256:1d07d16810c26713f3d875083924d93697900147364360587ccb5a63f2c31012
- name: kind
value: task
resolver: bundles
@ -390,7 +390,7 @@ spec:
- name: name
value: clair-scan
- name: bundle
value: quay.io/konflux-ci/tekton-catalog/task-clair-scan:0.2@sha256:878ae247ffc58d95a9ac68e4d658ef91ef039363e03e65a386bc0ead02d9d7d8
value: quay.io/konflux-ci/tekton-catalog/task-clair-scan:0.2@sha256:893ffa3ce26b061e21bb4d8db9ef7ed4ddd4044fe7aa5451ef391034da3ff759
- name: kind
value: task
resolver: bundles
@ -410,7 +410,7 @@ spec:
- name: name
value: ecosystem-cert-preflight-checks
- name: bundle
value: quay.io/konflux-ci/tekton-catalog/task-ecosystem-cert-preflight-checks:0.2@sha256:302828e9d7abc72b8a44fb2b9be068f86c982d8e5f4550b8bf654571d6361ee8
value: quay.io/konflux-ci/tekton-catalog/task-ecosystem-cert-preflight-checks:0.2@sha256:1f151e00f7fc427654b7b76045a426bb02fe650d192ffe147a304d2184787e38
- name: kind
value: task
resolver: bundles
@ -432,7 +432,7 @@ spec:
- name: name
value: sast-snyk-check
- name: bundle
value: quay.io/konflux-ci/tekton-catalog/task-sast-snyk-check:0.4@sha256:15a945b054b245f6713845dd6ae813d373c9f9cbac386d7382964f1b70ae3076
value: quay.io/konflux-ci/tekton-catalog/task-sast-snyk-check:0.4@sha256:351f2dce893159b703e9b6d430a2450b3df9967cb9bd3adb46852df8ccfe4c0d
- name: kind
value: task
resolver: bundles
@ -457,7 +457,7 @@ spec:
- name: name
value: clamav-scan
- name: bundle
value: quay.io/konflux-ci/tekton-catalog/task-clamav-scan:0.2@sha256:98d94290d6f21b6e231485326e3629bbcdec75c737b84e05ac9eac78f9a2c8b4
value: quay.io/konflux-ci/tekton-catalog/task-clamav-scan:0.3@sha256:cce2dfcc5bd6e91ee54aacdadad523b013eeae5cdaa7f6a4624b8cbcc040f439
- name: kind
value: task
resolver: bundles
@ -468,8 +468,10 @@ spec:
- "false"
- name: apply-tags
params:
- name: IMAGE
- name: IMAGE_URL
value: $(tasks.build-image-index.results.IMAGE_URL)
- name: IMAGE_DIGEST
value: $(tasks.build-image-index.results.IMAGE_DIGEST)
runAfter:
- build-image-index
taskRef:
@ -477,7 +479,7 @@ spec:
- name: name
value: apply-tags
- name: bundle
value: quay.io/konflux-ci/tekton-catalog/task-apply-tags:0.1@sha256:9d9871143ab3a818f681488be6074f5b2f892c1843795a46f6daf3f5487e72d1
value: quay.io/konflux-ci/tekton-catalog/task-apply-tags:0.2@sha256:70881c97a4c51ee1f4d023fa1110e0bdfcfd2f51d9a261fa543c3862b9a4eee9
- name: kind
value: task
resolver: bundles
@ -498,7 +500,7 @@ spec:
- name: name
value: push-dockerfile
- name: bundle
value: quay.io/konflux-ci/tekton-catalog/task-push-dockerfile:0.1@sha256:c82189e5d331e489cff99f0399f133fd3fad08921bea86747dfa379d1b5c748d
value: quay.io/konflux-ci/tekton-catalog/task-push-dockerfile:0.1@sha256:d5cb22a833be51dd72a872cac8bfbe149e8ad34da7cb48a643a1e613447a1f9d
- name: kind
value: task
resolver: bundles
@ -518,7 +520,7 @@ spec:
- name: name
value: rpms-signature-scan
- name: bundle
value: quay.io/konflux-ci/tekton-catalog/task-rpms-signature-scan:0.2@sha256:297c2d8928aa3b114fcb1ba5d9da8b10226b68fed30706e78a6a5089c6cd30e3
value: quay.io/konflux-ci/tekton-catalog/task-rpms-signature-scan:0.2@sha256:1b6c20ab3dbfb0972803d3ebcb2fa72642e59400c77bd66dfd82028bdd09e120
- name: kind
value: task
resolver: bundles
@ -539,7 +541,7 @@ spec:
- name: workspace
volumeClaimTemplate:
metadata:
creationTimestamp: null
creationTimestamp:
spec:
accessModes:
- ReadWriteOnce

View file

@ -1,12 +1,13 @@
PACKAGE_NAME = cockpit-image-builder
INSTALL_DIR = /share/cockpit/$(PACKAGE_NAME)
INSTALL_DIR_BASE = /share/cockpit/
INSTALL_DIR = $(INSTALL_DIR_BASE)$(PACKAGE_NAME)
APPSTREAMFILE=org.image-builder.$(PACKAGE_NAME).metainfo.xml
VERSION := $(shell (cd "$(SRCDIR)" && grep "^Version:" cockpit/$(PACKAGE_NAME).spec | sed 's/[^[:digit:]]*\([[:digit:]]\+\).*/\1/'))
COMMIT = $(shell (cd "$(SRCDIR)" && git rev-parse HEAD))
# TODO: figure out a strategy for keeping this updated
COCKPIT_REPO_COMMIT = b0e82161b4afcb9f0a6fddd8ff94380e983b2238
COCKPIT_REPO_COMMIT = a70142a7a6f9c4e78e71f3c4ec738b6db2fbb04f
COCKPIT_REPO_URL = https://github.com/cockpit-project/cockpit.git
COCKPIT_REPO_TREE = '$(strip $(COCKPIT_REPO_COMMIT))^{tree}'
@ -55,6 +56,7 @@ cockpit/devel-uninstall:
cockpit/devel-install: PREFIX=~/.local
cockpit/devel-install:
PREFIX="~/.local"
mkdir -p $(PREFIX)$(INSTALL_DIR_BASE)
ln -s $(shell pwd)/cockpit/public $(PREFIX)$(INSTALL_DIR)
.PHONY: cockpit/download

209
README.md
View file

@ -19,16 +19,20 @@ Frontend code for Image Builder.
## Table of Contents
1. [How to build and run image-builder-frontend](#frontend-development)
1. [Frontend Development](#frontend-development)
1. [API](#api-endpoints)
2. [Unleash feature flags](#unleash-feature-flags)
2. [Backend Development](#backend-development)
2. [File structure](#file-structure)
3. [Style Guidelines](#style-guidelines)
4. [Test Guidelines](#test-guidelines)
5. [Running hosted service Playwright tests](#running-hosted-service-playwright-tests)
2. [Image builder as Cockpit plugin](#image-builder-as-cockpit-plugin)
3. [Backend Development](#backend-development)
2. [API](#api-endpoints)
3. [Unleash feature flags](#unleash-feature-flags)
4. [File structure](#file-structure)
5. [Style Guidelines](#style-guidelines)
6. [Test Guidelines](#test-guidelines)
7. [Running hosted service Playwright tests](#running-hosted-service-playwright-tests)
## How to build and run image-builder-frontend
> [!IMPORTANT]
> Running image-builder-frontend against [console.redhat.com](https://console.redhat.com/) requires connection to the Red Hat VPN, which is only available to Red Hat employees. External contributors can locally run [image builder as Cockpit plugin](#image-builder-as-cockpit-plugin).
### Frontend Development
To develop the frontend you can use a proxy to run image-builder-frontend locally
@ -39,7 +43,7 @@ worrying if a feature from stage has been released yet.
#### Nodejs and npm version
Make sure you have npm@10 and node 18+ installed. If you need multiple versions of nodejs check out [nvm](https://github.com/nvm-sh/nvm).
Make sure you have npm@10 and node 22+ installed. If you need multiple versions of nodejs check out [nvm](https://github.com/nvm-sh/nvm).
#### Webpack proxy
@ -69,52 +73,70 @@ echo "127.0.0.1 stage.foo.redhat.com" >> /etc/hosts
4. open browser at `https://stage.foo.redhat.com:1337/beta/insights/image-builder`
#### Insights proxy (deprecated)
### Image builder as Cockpit plugin
1. Clone the insights proxy: https://github.com/RedHatInsights/insights-proxy
> [!NOTE]
> Issues marked with [cockpit-image-builder](https://github.com/osbuild/image-builder-frontend/issues?q=is%3Aissue%20state%3Aopen%20label%3Acockpit-image-builder) label are reproducible in image builder plugin and can be worked on by external contributors without connection to the Red Hat VPN.
2. Setting up the proxy
#### Cockpit setup
To install and setup Cockpit follow guide at: https://cockpit-project.org/running.html
Choose a runner (podman or docker), and point the SPANDX_CONFIG variable to
`profile/local-frontend.js` included in image-builder-frontend.
#### On-premises image builder installation and configuration
To install and configure `osbuild-composer` on your local machine follow our documentation: https://osbuild.org/docs/on-premises/installation/
```bash
sudo insights-proxy/scripts/patch-etc-hosts.sh
export RUNNER="podman"
export SPANDX_CONFIG=$PATH_TO/image-builder-frontend/profiles/local-frontend.js
sudo -E insights-proxy/scripts/run.sh
```
#### Scripts for local development of image builder plugin
3. Starting up image-builder-frontend
The following scripts are used to build the frontend with Webpack and install it into the Cockpit directories. These scripts streamline the development process by automating build and installation steps.
In the image-builder-frontend checkout directory
Runs Webpack with the specified configuration (cockpit/webpack.config.ts) to build the frontend assets.
Use this command whenever you need to compile the latest changes in your frontend code.
```bash
npm install
npm start
```
Creates the necessary directory in the user's local Cockpit share (~/.local/share/cockpit/).
Creates a symbolic link (image-builder-frontend) pointing to the built frontend assets (cockpit/public).
Use this command after building the frontend to install it locally for development purposes.
The symbolic link allows Cockpit to serve the frontend assets from your local development environment,
making it easier to test changes in real-time without deploying to a remote server.
The UI should be running on
https://prod.foo.redhat.com:1337/beta/insights/image-builder/landing.
Note that this requires you to have access to either production or stage (plus VPN and proxy config) of insights.
```bash
make cockpit/build
```
#### API endpoints
```bash
make cockpit/devel-install
```
To uninstall and remove the symbolic link, run the following command:
```bash
make cockpit/devel-uninstall
```
For convenience, you can run the following to combine all three steps:
```bash
make cockpit/devel
```
### Backend Development
To develop both the frontend and the backend you can again use the proxy to run both the
frontend and backend locally against the chrome at cloud.redhat.com. For instructions
see the [osbuild-getting-started project](https://github.com/osbuild/osbuild-getting-started).
## API endpoints
API slice definitions are programmatically generated using the [@rtk-query/codegen-openapi](https://redux-toolkit.js.org/rtk-query/usage/code-generation) package.
OpenAPI schema for the endpoints are stored in `/api/schema`. Their
corresponding configuration files are stored in `/api/config`. Each endpoint
has a corresponding empty API slice and generated API slice which are stored in
`/src/store`.
The OpenAPI schema are imported during code generation. OpenAPI configuration files are
stored in `/api/config`. Each endpoint has a corresponding empty API slice and generated API
slice which are stored in `/src/store`.
##### Add a new API
### Add a new API schema
For a hypothetical API called foobar
1. Download the foobar API OpenAPI json or yaml representation under
`api/schema/foobar.json`
2. Create a new "empty" API file under `src/store/emptyFoobarApi.ts` that has following
1. Create a new "empty" API file under `src/store/emptyFoobarApi.ts` that has following
content:
```typescript
@ -130,21 +152,21 @@ export const emptyFoobarApi = createApi({
});
```
3. Declare new constant `FOOBAR_API` with the API url in `src/constants.ts`
2. Declare new constant `FOOBAR_API` with the API url in `src/constants.ts`
```typescript
export const FOOBAR_API = 'api/foobar/v1'
```
4. Create the config file for code generation in `api/config/foobar.ts` containing:
3. Create the config file for code generation in `api/config/foobar.ts` containing:
```typescript
import type { ConfigFile } from '@rtk-query/codegen-openapi';
const config: ConfigFile = {
schemaFile: '../schema/foobar.json',
schemaFile: 'URL_TO_THE_OPENAPI_SCHEMA',
apiFile: '../../src/store/emptyFoobarApi.ts',
apiImport: 'emptyEdgeApi',
apiImport: 'emptyContentSourcesApi',
outputFile: '../../src/store/foobarApi.ts',
exportName: 'foobarApi',
hooks: true,
@ -152,20 +174,16 @@ const config: ConfigFile = {
};
```
5. Update the `api.sh` script by adding a new line for npx to generate the code:
```bash
npx @rtk-query/codegen-openapi ./api/config/foobar.ts &
```
6. Update the `.eslintignore` file by adding a new line for the generated code:
4. Update the `eslint.config.js` file by adding the generated code path to the ignores array:
```
foobarApi.ts
ignores: [
<other ignored files>,
'**/foobarApi.ts',
]
```
7. run api generation
5. run api generation
```bash
npm run api
@ -173,12 +191,12 @@ npm run api
And voilà!
##### Add a new endpoint
### Add a new endpoint
To add a new endpoint, simply update the `api/config/foobar.ts` file with new
endpoints in the `filterEndpoints` table.
#### Unleash feature flags
## Unleash feature flags
Your user needs to have the corresponding rights, do the
same as this MR in internal gitlab https://gitlab.cee.redhat.com/service/app-interface/-/merge_requests/79225
@ -194,7 +212,7 @@ existing flags:
https://github.com/RedHatInsights/image-builder-frontend/blob/c84b493eba82ce83a7844943943d91112ffe8322/src/Components/ImagesTable/ImageLink.js#L99
##### Mocking flags for tests
### Mocking flags for tests
Flags can be mocked for the unit tests to access some feature. Checkout:
https://github.com/osbuild/image-builder-frontend/blob/9a464e416bc3769cfc8e23b62f1dd410eb0e0455/src/test/Components/CreateImageWizard/CreateImageWizard.test.tsx#L49
@ -204,66 +222,18 @@ base, then it's good practice to test the two of them. If not, only test what's
actually owned by the frontend project.
##### Cleaning the flags
### Cleaning the flags
Unleash toggles are expected to live for a limited amount of time, documentation
specify 40 days for a release, we should keep that in mind for each toggle
we're planning on using.
### Backend Development
To develop both the frontend and the backend you can again use the proxy to run both the
frontend and backend locally against the chrome at cloud.redhat.com. For instructions
see the [osbuild-getting-started project](https://github.com/osbuild/osbuild-getting-started).
## File Structure
### OnPremise Development - Cockpit Build and Install
## Overview
The following scripts are used to build the frontend with Webpack and install it into the Cockpit directories. These scripts streamline the development process by automating build and installation steps.
### Scripts
#### 1. Build the Cockpit Frontend
Runs Webpack with the specified configuration (cockpit/webpack.config.ts) to build the frontend assets.
Use this command whenever you need to compile the latest changes in your frontend code.
Creates the necessary directory in the user's local Cockpit share (~/.local/share/cockpit/).
Creates a symbolic link (image-builder-frontend) pointing to the built frontend assets (cockpit/public).
Use this command after building the frontend to install it locally for development purposes.
The symbolic link allows Cockpit to serve the frontend assets from your local development environment,
making it easier to test changes in real-time without deploying to a remote server.
```bash
make devel-install
```
```bash
make build
```
To uninstall and remove the symbolic link, run the following command:
```bash
make devel-uninstall
```
For convenience, you can run the following to combine all three steps:
```bash
make cockpit/all
```
### Quick Reference
| Directory | Description |
| --------- | ----------- |
| [`/api`](https://github.com/RedHatInsights/image-builder-frontend/tree/main/api) | API schema and config files |
| [`/config`](https://github.com/RedHatInsights/image-builder-frontend/tree/main/config) | webpack configuration |
| [`/devel`](https://github.com/RedHatInsights/image-builder-frontend/tree/main/devel) | tools for local development |
| [`/src`](https://github.com/RedHatInsights/image-builder-frontend/tree/main/src) | source code |
| [`/src/Components`](https://github.com/RedHatInsights/image-builder-frontend/tree/main/src/Components) | source code split by individual components |
| [`/src/test`](https://github.com/RedHatInsights/image-builder-frontend/tree/main/src/test) | test utilities |
@ -272,8 +242,19 @@ make cockpit/all
## Style Guidelines
This project uses eslint's recommended styling guidelines. These rules can be found here:
https://eslint.org/docs/rules/
This project uses recommended rule sets rom several plugins:
- `@eslint/js`
- `typescript-eslint`
- `eslint-plugin-react`
- `eslint-plugin-react-hooks`
- `eslint-plugin-react-redux`
- `eslint-plugin-import`
- `eslint-plugin-jsx-a11y`
- `eslint-plugin-disable-autofix`
- `eslint-plugin-jest-dom`
- `eslint-plugin-testing-library`
- `eslint-plugin-playwright`
- `@redhat-cloud-services/eslint-config-redhat-cloud-services`
To run the linter, use:
```bash
@ -282,16 +263,10 @@ npm run lint
Any errors that can be fixed automatically, can be corrected by running:
```bash
npm run lint --fix
npm run lint:js:fix
```
All the linting rules and configuration of eslint can be found in [`.eslintrc.yml`](https://github.com/RedHatInsights/image-builder-frontend/blob/main/.eslintrc.yml).
### Additional eslint rules
There are also additional rules added to enforce code style. Those being:
- `import/order` -> enforces the order in import statements and separates them into groups based on their type
- `prefer-const` -> enforces use of `const` declaration for variables that are never reassigned
- `no-console` -> throws an error for any calls of `console` methods leftover after debugging
All the linting rules and configuration of ESLint can be found in [`eslint.config.js`](https://github.com/RedHatInsights/image-builder-frontend/blob/main/eslint.config.js).
## Test Guidelines
@ -369,16 +344,16 @@ Follow these steps to find and paste the certification file into the 'Keychain A
npm ci
```
3. Download the Playwright browsers with
3. Download the Playwright browsers with
```bash
npx playwright install
```
4. Start the local development stage server by running
4. Start the local development stage server by running
```bash
npm run start:stage
```
5. Now you have two options of how to run the tests:
* (Preferred) Use VS Code and the [Playwright Test module for VSCode](https://marketplace.visualstudio.com/items?itemName=ms-playwright.playwright). But other editors do have similar plugins for ease of use, if so desired
* Using terminal - `npx playwright test` will run the playwright test suite. `npx playwright test --headed` will run the suite in a vnc-like browser so you can watch it's interactions.
* Using terminal - `npx playwright test` will run the playwright test suite. `npx playwright test --headed` will run the suite in a vnc-like browser so you can watch it's interactions.

View file

@ -5,7 +5,6 @@ npx @rtk-query/codegen-openapi ./api/config/imageBuilder.ts &
npx @rtk-query/codegen-openapi ./api/config/rhsm.ts &
npx @rtk-query/codegen-openapi ./api/config/contentSources.ts &
npx @rtk-query/codegen-openapi ./api/config/provisioning.ts &
npx @rtk-query/codegen-openapi ./api/config/edge.ts &
npx @rtk-query/codegen-openapi ./api/config/compliance.ts &
npx @rtk-query/codegen-openapi ./api/config/composerCloudApi.ts &

View file

@ -1,7 +1,7 @@
import type { ConfigFile } from '@rtk-query/codegen-openapi';
const config: ConfigFile = {
schemaFile: '../schema/compliance.json',
schemaFile: 'https://console.redhat.com/api/compliance/v2/openapi.json',
apiFile: '../../src/store/service/emptyComplianceApi.ts',
apiImport: 'emptyComplianceApi',
outputFile: '../../src/store/service/complianceApi.ts',

View file

@ -1,17 +1,15 @@
import type { ConfigFile } from '@rtk-query/codegen-openapi';
const config: ConfigFile = {
schemaFile: '../schema/composerCloudApi.v2.yaml',
schemaFile:
'https://raw.githubusercontent.com/osbuild/osbuild-composer/main/internal/cloudapi/v2/openapi.v2.yml',
apiFile: '../../src/store/cockpit/emptyComposerCloudApi.ts',
apiImport: 'emptyComposerCloudApi',
outputFile: '../../src/store/cockpit/composerCloudApi.ts',
exportName: 'composerCloudApi',
hooks: false,
unionUndefined: true,
filterEndpoints: [
'postCompose',
'getComposeStatus',
],
filterEndpoints: ['postCompose', 'getComposeStatus'],
};
export default config;

View file

@ -1,7 +1,7 @@
import type { ConfigFile } from '@rtk-query/codegen-openapi';
const config: ConfigFile = {
schemaFile: '../schema/contentSources.json',
schemaFile: 'https://console.redhat.com/api/content-sources/v1/openapi.json',
apiFile: '../../src/store/service/emptyContentSourcesApi.ts',
apiImport: 'emptyContentSourcesApi',
outputFile: '../../src/store/service/contentSourcesApi.ts',
@ -12,6 +12,7 @@ const config: ConfigFile = {
'createRepository',
'listRepositories',
'listRepositoriesRpms',
'listRepositoryParameters',
'searchRpm',
'searchPackageGroup',
'listFeatures',

View file

@ -1,36 +0,0 @@
import type { ConfigFile } from '@rtk-query/codegen-openapi';
const config: ConfigFile = {
schemaFile: '../schema/edge.json',
apiFile: '../../src/store/service/emptyEdgeApi.ts',
apiImport: 'emptyEdgeApi',
outputFile: '../../src/store/service/edgeApi.ts',
exportName: 'edgeApi',
hooks: true,
unionUndefined: true,
filterEndpoints: [
'createImage',
'createImageUpdate',
'getAllImages',
'getImageStatusByID',
'getImageByID',
'getImageDetailsByID',
'getImageByOstree',
'createInstallerForImage',
'getRepoForImage',
'getMetadataForImage',
'createKickStartForImage',
'checkImageName',
'retryCreateImage',
'listAllImageSets',
'getImageSetsByID',
'getImageSetsView',
'getImageSetViewByID',
'getAllImageSetImagesView',
'getImageSetsDevicesByID',
'deleteImageSet',
'getImageSetImageView',
],
};
export default config;

View file

@ -1,7 +1,8 @@
import type { ConfigFile } from '@rtk-query/codegen-openapi';
const config: ConfigFile = {
schemaFile: '../schema/imageBuilder.yaml',
schemaFile:
'https://raw.githubusercontent.com/osbuild/image-builder/main/internal/v1/api.yaml',
apiFile: '../../src/store/service/emptyImageBuilderApi.ts',
apiImport: 'emptyImageBuilderApi',
outputFile: '../../src/store/service/imageBuilderApi.ts',

View file

@ -1,7 +1,7 @@
import type { ConfigFile } from '@rtk-query/codegen-openapi';
const config: ConfigFile = {
schemaFile: '../schema/provisioning.json',
schemaFile: 'https://console.redhat.com/api/provisioning/v1/openapi.json',
apiFile: '../../src/store/service/emptyProvisioningApi.ts',
apiImport: 'emptyProvisioningApi',
outputFile: '../../src/store/service/provisioningApi.ts',

View file

@ -1,7 +1,7 @@
import type { ConfigFile } from '@rtk-query/codegen-openapi';
const config: ConfigFile = {
schemaFile: '../schema/rhsm.json',
schemaFile: 'https://console.redhat.com/api/rhsm/v2/openapi.json',
apiFile: '../../src/store/service/emptyRhsmApi.ts',
apiImport: 'emptyRhsmApi',
outputFile: '../../src/store/service/rhsmApi.ts',

View file

@ -1,10 +0,0 @@
#!/bin/bash
# Download the most up-to-date imageBuilder.yaml file and overwrite the existing one
curl https://raw.githubusercontent.com/osbuild/image-builder/main/internal/v1/api.yaml -o ./api/schema/imageBuilder.yaml
curl https://console.redhat.com/api/compliance/v2/openapi.json -o ./api/schema/compliance.json
curl https://console.redhat.com/api/content-sources/v1/openapi.json -o ./api/schema/contentSources.json
curl https://raw.githubusercontent.com/osbuild/osbuild-composer/main/internal/cloudapi/v2/openapi.v2.yml -o ./api/schema/composerCloudApi.v2.yaml

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

@ -1 +1 @@
Subproject commit 75adad05c9e22ff84c7d3b43564554a26f55a8a9
Subproject commit b496d0a8c1755608bd256a6960869b14a7689d38

View file

@ -9,7 +9,7 @@ export COMPONENT="image-builder"
export IMAGE="quay.io/cloudservices/image-builder-frontend"
export APP_ROOT=$(pwd)
export WORKSPACE=${WORKSPACE:-$APP_ROOT} # if running in jenkins, use the build's workspace
export NODE_BUILD_VERSION=18
export NODE_BUILD_VERSION=22
COMMON_BUILDER=https://raw.githubusercontent.com/RedHatInsights/insights-frontend-builder-common/master
set -exv

View file

@ -1 +1,3 @@
TODO
# cockpit-image-builder
The "cockpit-image-builder" provides an on-premise frontend for image building, designed to integrate with [Cockpit](https://cockpit-project.org/) as a plugin. It allows users to create, manage, and compose custom operating system images, with images stored locally.

View file

@ -1,5 +1,5 @@
Name: cockpit-image-builder
Version: 68
Version: 76
Release: 1%{?dist}
Summary: Image builder plugin for Cockpit

View file

@ -1,15 +1,15 @@
<!doctype html>
<!DOCTYPE html>
<html lang="en-us" class="layout-pf pf-m-redhat-font">
<head>
<meta charset="utf-8">
<head>
<meta charset="utf-8" />
<title>Image-Builder</title>
<!-- js dependencies -->
<script type="text/javascript" src="../base1/cockpit.js"></script>
<link href="main.css" rel="stylesheet">
</head>
<body>
<div id="main"></div>
<script defer src="main.js"></script>
</body>
<script defer src="main.js"></script>
<link href="main.css" rel="stylesheet" />
</head>
<body>
<div class="ct-page-fill" id="main"></div>
</body>
</html>

View file

@ -75,7 +75,15 @@ module.exports = {
},
{
test: /\.scss$/,
use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader'],
use: [
MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: { url: false },
},
'sass-loader',
],
},
],
},

View file

@ -1,183 +0,0 @@
---
apiVersion: template.openshift.io/v1
kind: Template
metadata:
name: image-builder-frontend-tests
objects:
- apiVersion: batch/v1
kind: Job
metadata:
name: image-builder-frontend-${TEST_TYPE}-tests-${IMAGE_TAG}-${UID}
annotations:
"ignore-check.kube-linter.io/no-liveness-probe": "probes not required on Job pods"
"ignore-check.kube-linter.io/no-readiness-probe": "probes not required on Job pods"
spec:
backoffLimit: 0
template:
spec:
imagePullSecrets:
- name: quay-cloudservices-pull
restartPolicy: Never
volumes:
- name: sel-shm
emptyDir:
medium: Memory
- name: sel-downloads
emptyDir:
medium: Memory
sizeLimit: 64Mi
containers:
- name: image-builder-frontend-iqe-${TEST_TYPE}-tests-${IMAGE_TAG}-${UID}
image: ${IQE_IMAGE}
imagePullPolicy: Always
args:
- run
env:
- name: ENV_FOR_DYNACONF
value: ${ENV_FOR_DYNACONF}
- name: DYNACONF_MAIN__use_beta
value: ${USE_BETA}
- name: IQE_IBUTSU_SOURCE
value: image-builder-${IMAGE_TAG}-tests-${UID}-${ENV_FOR_DYNACONF}
- name: IQE_BROWSERLOG
value: ${IQE_BROWSERLOG}
- name: IQE_NETLOG
value: ${IQE_NETLOG}
- name: IQE_PLUGINS
value: ${IQE_PLUGINS}
- name: IQE_MARKER_EXPRESSION
value: ${IQE_MARKER_EXPRESSION}
- name: IQE_FILTER_EXPRESSION
value: ${IQE_FILTER_EXPRESSION}
- name: IQE_LOG_LEVEL
value: ${IQE_LOG_LEVEL}
- name: IQE_REQUIREMENTS
value: ${IQE_REQUIREMENTS}
- name: IQE_PARALLEL_ENABLED
value: ${IQE_PARALLEL_ENABLED}
- name: IQE_REQUIREMENTS_PRIORITY
value: ${IQE_REQUIREMENTS_PRIORITY}
- name: IQE_TEST_IMPORTANCE
value: ${IQE_TEST_IMPORTANCE}
- name: DYNACONF_IQE_VAULT_LOADER_ENABLED
value: "true"
- name: DYNACONF_IQE_VAULT_VERIFY
value: "true"
- name: DYNACONF_IQE_VAULT_URL
valueFrom:
secretKeyRef:
key: url
name: iqe-vault
optional: true
- name: DYNACONF_IQE_VAULT_MOUNT_POINT
valueFrom:
secretKeyRef:
key: mountPoint
name: iqe-vault
optional: true
- name: DYNACONF_IQE_VAULT_ROLE_ID
valueFrom:
secretKeyRef:
key: roleId
name: iqe-vault
optional: true
- name: DYNACONF_IQE_VAULT_SECRET_ID
valueFrom:
secretKeyRef:
key: secretId
name: iqe-vault
optional: true
resources:
limits:
cpu: ${IQE_CPU_LIMIT}
memory: ${IQE_MEMORY_LIMIT}
requests:
cpu: ${IQE_CPU_REQUEST}
memory: ${IQE_MEMORY_REQUEST}
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
volumeMounts:
- name: sel-downloads
mountPath: /sel-downloads
- name: image-builder-sel-${TEST_TYPE}-tests-${IMAGE_TAG}-${UID}
image: ${IQE_SEL_IMAGE}
env:
- name: _JAVA_OPTIONS
value: ${SELENIUM_JAVA_OPTS}
- name: VNC_GEOMETRY
value: ${VNC_GEOMETRY}
- name: SE_NODE_SESSION_TIMEOUT
value: ${SE_NODE_SESSION_TIMEOUT}
resources:
limits:
cpu: ${SELENIUM_CPU_LIMIT}
memory: ${SELENIUM_MEMORY_LIMIT}
requests:
cpu: ${SELENIUM_CPU_REQUEST}
memory: ${SELENIUM_MEMORY_REQUEST}
volumeMounts:
- name: sel-shm
mountPath: /dev/shm
- name: sel-downloads
mountPath: /home/selenium/Downloads
parameters:
- name: IMAGE_TAG
value: ''
required: true
- name: UID
description: "Unique job name suffix"
generate: expression
from: "[a-z0-9]{6}"
- name: IQE_IMAGE
description: "container image path for the iqe plugin"
value: quay.io/cloudservices/iqe-tests:insights-experiences
- name: ENV_FOR_DYNACONF
value: stage_proxy
- name: USE_BETA
value: "true"
- name: IQE_PLUGINS
value: insights_experiences
- name: IQE_MARKER_EXPRESSION
value: 'image_builder'
- name: IQE_FILTER_EXPRESSION
value: ''
- name: IQE_LOG_LEVEL
value: info
- name: IQE_REQUIREMENTS
value: ''
- name: IQE_REQUIREMENTS_PRIORITY
value: ''
- name: IQE_TEST_IMPORTANCE
value: ''
- name: IQE_SEL_IMAGE
value: 'quay.io/redhatqe/selenium-standalone:ff_91.9.1esr_chrome_103.0.5060.114'
- name: IQE_BROWSERLOG
value: "1"
- name: IQE_NETLOG
value: "1"
- name: TEST_TYPE
value: ''
- name: IQE_CPU_LIMIT
value: "1"
- name: IQE_MEMORY_LIMIT
value: 1.5Gi
- name: IQE_CPU_REQUEST
value: 250m
- name: IQE_MEMORY_REQUEST
value: 1Gi
- name: SELENIUM_CPU_LIMIT
value: 500m
- name: SELENIUM_MEMORY_LIMIT
value: 2Gi
- name: SELENIUM_CPU_REQUEST
value: 100m
- name: SELENIUM_MEMORY_REQUEST
value: 1Gi
- name: SELENIUM_JAVA_OPTS
value: ''
- name: VNC_GEOMETRY
value: '1920x1080'
- name: IQE_PARALLEL_ENABLED
value: "false"
- name: SE_NODE_SESSION_TIMEOUT
value: "600"

View file

@ -1,12 +0,0 @@
FROM node:18
WORKDIR /app
COPY . .
RUN npm ci
EXPOSE 8002
EXPOSE 1337
CMD [ "npm", "run", "devel" ]

174
eslint.config.js Normal file
View file

@ -0,0 +1,174 @@
const js = require('@eslint/js');
const tseslint = require('typescript-eslint');
const pluginReact = require('eslint-plugin-react');
const pluginReactHooks = require('eslint-plugin-react-hooks');
const pluginReactRedux = require('eslint-plugin-react-redux');
const pluginImport = require('eslint-plugin-import');
const fecConfig = require('@redhat-cloud-services/eslint-config-redhat-cloud-services');
const pluginJsxA11y = require('eslint-plugin-jsx-a11y');
const disableAutofix = require('eslint-plugin-disable-autofix');
const pluginPrettier = require('eslint-plugin-prettier');
const jestDom = require('eslint-plugin-jest-dom');
const pluginTestingLibrary = require('eslint-plugin-testing-library');
const pluginPlaywright = require('eslint-plugin-playwright');
const { defineConfig } = require('eslint/config');
const globals = require('globals');
module.exports = defineConfig([
{ // Ignore programatically generated files
ignores: [
'**/mockServiceWorker.js',
'**/imageBuilderApi.ts',
'**/contentSourcesApi.ts',
'**/rhsmApi.ts',
'**/provisioningApi.ts',
'**/complianceApi.ts',
'**/composerCloudApi.ts'
]
},
{ // Base config for js/ts files
files: ['**/*.{js,ts,jsx,tsx}'],
languageOptions: {
parser: tseslint.parser,
parserOptions: {
project: './tsconfig.json'
},
globals: {
...globals.browser,
// node
'JSX': 'readonly',
'process': 'readonly',
'__dirname': 'readonly',
'require': 'readonly',
// vitest
'describe': 'readonly',
'it': 'readonly',
'test': 'readonly',
'expect': 'readonly',
'vi': 'readonly',
'beforeAll': 'readonly',
'beforeEach': 'readonly',
'afterAll': 'readonly',
'afterEach': 'readonly'
},
},
plugins: {
js,
'@typescript-eslint': tseslint.plugin,
react: pluginReact,
'react-hooks': pluginReactHooks,
'react-redux': pluginReactRedux,
import: pluginImport,
jsxA11y: pluginJsxA11y,
'disable-autofix': disableAutofix,
prettier: pluginPrettier,
},
rules: {
...js.configs.recommended.rules,
...tseslint.configs.recommended.rules,
...pluginReact.configs.flat.recommended.rules,
...pluginReactHooks.configs.recommended.rules,
...pluginReactRedux.configs.recommended.rules,
...fecConfig.rules,
'import/order': ['error', {
groups: ['builtin', 'external', 'internal', 'sibling', 'parent', 'index'],
alphabetize: {
order: 'asc',
caseInsensitive: true
},
'newlines-between': 'always',
pathGroups: [ // ensures the import of React is always on top
{
pattern: 'react',
group: 'builtin',
position: 'before'
}
],
pathGroupsExcludedImportTypes: ['react']
}],
'sort-imports': ['error', {
ignoreCase: true,
ignoreDeclarationSort: true,
ignoreMemberSort: false,
}],
'no-duplicate-imports': 'error',
'prefer-const': ['error', {
destructuring: 'any',
}],
'no-console': 'error',
'eqeqeq': 'error',
'array-callback-return': 'warn',
'@typescript-eslint/ban-ts-comment': ['error', {
'ts-expect-error': 'allow-with-description',
'ts-ignore': 'allow-with-description',
'ts-nocheck': true,
'ts-check': true,
minimumDescriptionLength: 5,
}],
'@typescript-eslint/ban-types': 'off',
'@typescript-eslint/no-explicit-any': 'error',
'@typescript-eslint/no-unsafe-function-type': 'error',
'@typescript-eslint/no-require-imports': 'error',
'disable-autofix/@typescript-eslint/no-unnecessary-condition': 'warn',
'no-unused-vars': 'off', // disable js rule in favor of @typescript-eslint's rule
'@typescript-eslint/no-unused-vars': [
'error',
{
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
},
],
'jsx-a11y/no-autofocus': 'off',
'prettier/prettier': ['error', {
semi: true,
tabWidth: 2,
singleQuote: true,
jsxSingleQuote: true,
bracketSpacing: true,
tsxSingleQuote: true,
tsSingleQuote: true,
printWidth: 80,
trailingComma: 'all',
}],
},
settings: {
react: {
version: 'detect', // Automatically detect React version
},
},
},
{ // Override for test files
files: ['src/test/**/*.{ts,tsx}'],
plugins: {
'jest-dom': jestDom,
'testing-library': pluginTestingLibrary,
},
rules: {
...jestDom.configs.recommended.rules,
...pluginTestingLibrary.configs.react.rules,
'react/display-name': 'off',
'react/prop-types': 'off',
'testing-library/no-debugging-utils': 'error'
},
},
{ // Override for Playwright tests
files: ['playwright/**/*.ts'],
plugins: {
playwright: pluginPlaywright,
},
rules: {
...pluginPlaywright.configs.recommended.rules,
'playwright/no-conditional-in-test': 'off',
'playwright/no-conditional-expect': 'off',
'playwright/no-skipped-test': [
'error',
{
'allowConditional': true
}
]
},
},
]);

4646
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -8,17 +8,18 @@
},
"dependencies": {
"@ltd/j-toml": "1.38.0",
"@patternfly/patternfly": "5.4.1",
"@patternfly/react-code-editor": "5.4.1",
"@patternfly/react-core": "5.4.12",
"@patternfly/react-table": "5.4.14",
"@redhat-cloud-services/frontend-components": "5.2.6",
"@redhat-cloud-services/frontend-components-notifications": "4.1.20",
"@redhat-cloud-services/frontend-components-utilities": "5.0.11",
"@patternfly/patternfly": "6.3.1",
"@patternfly/react-code-editor": "6.3.1",
"@patternfly/react-core": "6.3.1",
"@patternfly/react-table": "6.3.1",
"@redhat-cloud-services/frontend-components": "7.0.3",
"@redhat-cloud-services/frontend-components-notifications": "6.1.5",
"@redhat-cloud-services/frontend-components-utilities": "7.0.3",
"@redhat-cloud-services/types": "3.0.1",
"@reduxjs/toolkit": "2.8.2",
"@scalprum/react-core": "0.9.5",
"@sentry/webpack-plugin": "3.4.0",
"@unleash/proxy-client-react": "5.0.0",
"@sentry/webpack-plugin": "4.1.1",
"@unleash/proxy-client-react": "5.0.1",
"classnames": "2.5.1",
"jwt-decode": "4.0.0",
"lodash": "4.17.21",
@ -30,68 +31,72 @@
"redux-promise-middleware": "6.2.0"
},
"devDependencies": {
"@babel/core": "7.26.10",
"@babel/preset-env": "7.27.2",
"@babel/core": "7.28.0",
"@babel/preset-env": "7.28.0",
"@babel/preset-react": "7.27.1",
"@babel/preset-typescript": "7.27.0",
"@currents/playwright": "1.13.2",
"@patternfly/react-icons": "5.4.2",
"@babel/preset-typescript": "7.27.1",
"@currents/playwright": "1.15.3",
"@eslint/js": "9.32.0",
"@patternfly/react-icons": "6.3.1",
"@playwright/test": "1.51.1",
"@redhat-cloud-services/eslint-config-redhat-cloud-services": "2.0.12",
"@redhat-cloud-services/eslint-config-redhat-cloud-services": "3.0.0",
"@redhat-cloud-services/frontend-components-config": "6.3.8",
"@redhat-cloud-services/tsc-transform-imports": "1.0.24",
"@redhat-cloud-services/tsc-transform-imports": "1.0.25",
"@rtk-query/codegen-openapi": "2.0.0",
"@testing-library/dom": "10.4.0",
"@testing-library/jest-dom": "6.6.3",
"@testing-library/dom": "10.4.1",
"@testing-library/jest-dom": "6.6.4",
"@testing-library/react": "16.3.0",
"@testing-library/user-event": "14.6.1",
"@types/node": "22.15.1",
"@types/node": "24.3.0",
"@types/react": "18.3.12",
"@types/react-dom": "18.3.1",
"@types/react-redux": "7.1.34",
"@types/uuid": "10.0.0",
"@typescript-eslint/eslint-plugin": "8.32.1",
"@typescript-eslint/parser": "8.32.1",
"@vitejs/plugin-react": "4.4.1",
"@vitest/coverage-v8": "3.1.2",
"@typescript-eslint/eslint-plugin": "8.40.0",
"@typescript-eslint/parser": "8.40.0",
"@vitejs/plugin-react": "4.7.0",
"@vitest/coverage-v8": "3.2.4",
"babel-loader": "10.0.0",
"chart.js": "4.4.9",
"chart.js": "4.5.0",
"chartjs-adapter-moment": "1.0.1",
"chartjs-plugin-annotation": "3.1.0",
"copy-webpack-plugin": "13.0.0",
"css-loader": "7.1.2",
"eslint": "8.57.1",
"eslint": "9.33.0",
"eslint-plugin-disable-autofix": "5.0.1",
"eslint-plugin-import": "2.31.0",
"eslint-plugin-import": "2.32.0",
"eslint-plugin-jest-dom": "5.5.0",
"eslint-plugin-jsx-a11y": "6.10.2",
"eslint-plugin-playwright": "2.2.0",
"eslint-plugin-playwright": "2.2.2",
"eslint-plugin-prettier": "5.5.4",
"eslint-plugin-react": "7.37.5",
"eslint-plugin-react-hooks": "5.2.0",
"eslint-plugin-react-redux": "4.2.2",
"eslint-plugin-testing-library": "7.2.2",
"eslint-plugin-testing-library": "7.6.6",
"git-revision-webpack-plugin": "5.0.0",
"globals": "16.3.0",
"history": "5.3.0",
"identity-obj-proxy": "3.0.0",
"jsdom": "26.1.0",
"madge": "8.0.0",
"mini-css-extract-plugin": "2.9.2",
"moment": "2.30.1",
"msw": "2.7.5",
"msw": "2.10.5",
"npm-run-all": "4.1.5",
"path-browserify": "1.0.1",
"postcss-scss": "4.0.9",
"react-chartjs-2": "5.3.0",
"redux-mock-store": "1.5.5",
"sass": "1.88.0",
"sass": "1.90.0",
"sass-loader": "16.0.5",
"stylelint": "16.18.0",
"stylelint-config-recommended-scss": "14.1.0",
"stylelint": "16.23.1",
"stylelint-config-recommended-scss": "16.0.0",
"ts-node": "10.9.2",
"ts-patch": "3.3.0",
"typescript": "5.8.3",
"typescript-eslint": "8.40.0",
"uuid": "11.1.0",
"vitest": "3.1.2",
"vitest": "3.2.4",
"vitest-canvas-mock": "0.3.3",
"webpack-bundle-analyzer": "4.10.2",
"whatwg-fetch": "3.6.20"
@ -112,9 +117,7 @@
"test:cockpit": "src/test/cockpit-tests.sh",
"build": "fec build",
"build:cockpit": "webpack --config cockpit/webpack.config.ts",
"api": "npm-run-all api:pull api:generate",
"api:generate": "bash api/codegen.sh",
"api:pull": "bash api/pull.sh",
"api": "bash api/codegen.sh",
"verify": "npm-run-all build lint test",
"postinstall": "ts-patch install",
"circular": "madge --circular ./src --extensions js,ts,tsx",

View file

@ -16,6 +16,15 @@ srpm_build_deps:
- npm
jobs:
- job: tests
identifier: self
trigger: pull_request
tmt_plan: /plans/all/main
targets:
- centos-stream-10
- fedora-41
- fedora-42
- job: copr_build
trigger: pull_request
targets: &build_targets
@ -24,7 +33,6 @@ jobs:
- centos-stream-10
- centos-stream-10-aarch64
- fedora-all
- fedora-all-aarch64
- job: copr_build
trigger: commit

14
plans/all.fmf Normal file
View file

@ -0,0 +1,14 @@
summary: cockpit-image-builder playwright tests
prepare:
how: install
package:
- cockpit-image-builder
discover:
how: fmf
execute:
how: tmt
/main:
summary: playwright tests
discover+:
test: /schutzbot/playwright

View file

@ -34,9 +34,14 @@ export default defineConfig({
ignoreHTTPSErrors: true,
},
projects: [
{ name: 'setup', testMatch: /.*\.setup\.ts/ },
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
use: {
...devices['Desktop Chrome'],
storageState: '.auth/user.json',
},
dependencies: ['setup'],
},
],
});

View file

@ -0,0 +1,214 @@
import { expect } from '@playwright/test';
import { v4 as uuidv4 } from 'uuid';
import { test } from '../fixtures/customizations';
import { isHosted } from '../helpers/helpers';
import { ensureAuthenticated } from '../helpers/login';
import {
ibFrame,
navigateToLandingPage,
navigateToOptionalSteps,
} from '../helpers/navHelpers';
import {
createBlueprint,
deleteBlueprint,
exportBlueprint,
fillInDetails,
fillInImageOutputGuest,
importBlueprint,
registerLater,
} from '../helpers/wizardHelpers';
const validCallbackUrl =
'https://controller.url/api/controller/v2/job_templates/9/callback/';
const validHttpCallbackUrl =
'http://controller.url/api/controller/v2/job_templates/9/callback/';
const validHostConfigKey = 'hostconfigkey';
const validCertificate = `-----BEGIN CERTIFICATE-----
MIIDXTCCAkWgAwIBAgIJAOEzx5ezZ9EIMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV
BAYTAklOMQswCQYDVQQIDAJLUjEMMAoGA1UEBwwDS1JHMRAwDgYDVQQKDAdUZXN0
IENBMB4XDTI1MDUxNTEyMDAwMFoXDTI2MDUxNTEyMDAwMFowRTELMAkGA1UEBhMC
SU4xCzAJBgNVBAgMAktSMQwwCgYDVQQHDANSR0sxEDAOBgNVBAoMB1Rlc3QgQ0Ew
ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC+R4gfN5pyJQo5qBTTtN+7
eE9CSXZJ8SVVaE3U54IgqQoqsSoBY5QtExy7v5C6l6mW4E6dzK/JecmvTTO/BvlG
A5k2hxB6bOQxtxYwfgElH+RFWN9P4xxhtEiQgHoG1rDfnXuDJk1U3YEkCQELUebz
fF3EIDU1yR0Sz2bA+Sl2VXe8og1MEZfytq8VZUVltxtn2PfW7zI5gOllBR2sKeUc
K6h8HXN7qMgfEvsLIXxTw7fU/zA3ibcxfRCl3m6QhF8hwRh6F9Wtz2s8hCzGegV5
z0M39nY7X8C3GZQ4Ly8v8DdY+FbEix7K3SSBRbWtdPfAHRFlX9Er2Wf8DAr7O2hH
AgMBAAGjUDBOMB0GA1UdDgQWBBTXXz2eIDgK+BhzDUAGzptn0OMcpDAfBgNVHSME
GDAWgBTXXz2eIDgK+BhzDUAGzptn0OMcpDAMBgNVHRMEBTADAQH/MA0GCSqGSIb3
DQEBCwUAA4IBAQAoUgY4jsuBMB3el9cc7JS2rcOhhJzn47Hj2UANfJq52g5lbjo7
XDc7Wb3VDcV+1LzjdzayT1qO1WzHb6FDPW9L9f6h4s8lj6MvJ+xhOWgD11srdIt3
vbQaQW4zDfeVRcKXzqbcUX8BLXAdzJPqVwZ+Z4EDjYrJ7lF9k+IqfZm0MsYX7el9
kvdRHbLuF4Q0sZ05CXMFkhM0Ulhu4MZ+1FcsQa7nWfZzTmbjHOuWJPB4z5WwrB7z
U8YYvWJ3qxToWGbATqJxkRKGGqLrNrmwcfzgPqkpuCRYi0Kky6gJ1RvL+DRopY9x
uD+ckf3oH2wYAB6RpPRMkfVxe7lGMvq/yEZ6
-----END CERTIFICATE-----`;
const invalidCertificate = `-----BEGIN CERTIFICATE-----
ThisIs*Not+Valid/Base64==
-----END CERTIFICATE-----`;
test('Create a blueprint with AAP registration customization', async ({
page,
cleanup,
}) => {
const blueprintName = 'test-' + uuidv4();
// Skip entirely in Cockpit/on-premise where AAP customization is unavailable
test.skip(!isHosted(), 'AAP customization is not available in the plugin');
// Delete the blueprint after the run fixture
await cleanup.add(() => deleteBlueprint(page, blueprintName));
await ensureAuthenticated(page);
// Navigate to IB landing page and get the frame
await navigateToLandingPage(page);
const frame = await ibFrame(page);
await test.step('Navigate to optional steps in Wizard', async () => {
await navigateToOptionalSteps(frame);
await registerLater(frame);
});
await test.step('Select and fill the AAP step with valid configuration', async () => {
await frame
.getByRole('button', { name: 'Ansible Automation Platform' })
.click();
await frame
.getByRole('textbox', { name: 'ansible callback url' })
.fill(validCallbackUrl);
await frame
.getByRole('textbox', { name: 'host config key' })
.fill(validHostConfigKey);
await frame
.getByRole('textbox', { name: 'File upload' })
.fill(validCertificate);
await expect(frame.getByRole('button', { name: 'Next' })).toBeEnabled();
});
await test.step('Test TLS confirmation checkbox for HTTPS URLs', async () => {
// TLS confirmation checkbox should appear for HTTPS URLs
await expect(
frame.getByRole('checkbox', {
name: 'Insecure',
}),
).toBeVisible();
// Check TLS confirmation and verify CA input is hidden
await frame
.getByRole('checkbox', {
name: 'Insecure',
})
.check();
await expect(
frame.getByRole('textbox', { name: 'File upload' }),
).toBeHidden();
await frame
.getByRole('checkbox', {
name: 'Insecure',
})
.uncheck();
await expect(
frame.getByRole('textbox', { name: 'File upload' }),
).toBeVisible();
});
await test.step('Test certificate validation', async () => {
await frame.getByRole('textbox', { name: 'File upload' }).clear();
await frame
.getByRole('textbox', { name: 'File upload' })
.fill(invalidCertificate);
await expect(frame.getByText(/Certificate.*is not valid/)).toBeVisible();
await frame.getByRole('textbox', { name: 'File upload' }).clear();
await frame
.getByRole('textbox', { name: 'File upload' })
.fill(validCertificate);
await expect(frame.getByText('Certificate was uploaded')).toBeVisible();
});
await test.step('Test HTTP URL behavior', async () => {
await frame.getByRole('textbox', { name: 'ansible callback url' }).clear();
await frame
.getByRole('textbox', { name: 'ansible callback url' })
.fill(validHttpCallbackUrl);
// TLS confirmation checkbox should NOT appear for HTTP URLs
await expect(
frame.getByRole('checkbox', {
name: 'Insecure',
}),
).toBeHidden();
await expect(
frame.getByRole('textbox', { name: 'File upload' }),
).toBeVisible();
await frame.getByRole('textbox', { name: 'ansible callback url' }).clear();
await frame
.getByRole('textbox', { name: 'ansible callback url' })
.fill(validCallbackUrl);
});
await test.step('Complete AAP configuration and proceed to review', async () => {
await frame.getByRole('button', { name: 'Review and finish' }).click();
});
await test.step('Fill the BP details', async () => {
await fillInDetails(frame, blueprintName);
});
await test.step('Create BP', async () => {
await createBlueprint(frame, blueprintName);
});
await test.step('Edit BP and verify AAP configuration persists', async () => {
await frame.getByRole('button', { name: 'Edit blueprint' }).click();
await frame.getByLabel('Revisit Ansible Automation Platform step').click();
await expect(
frame.getByRole('textbox', { name: 'ansible callback url' }),
).toHaveValue(validCallbackUrl);
await expect(
frame.getByRole('textbox', { name: 'host config key' }),
).toHaveValue(validHostConfigKey);
await expect(
frame.getByRole('textbox', { name: 'File upload' }),
).toHaveValue(validCertificate);
await frame.getByRole('button', { name: 'Review and finish' }).click();
await frame
.getByRole('button', { name: 'Save changes to blueprint' })
.click();
});
// This is for hosted service only as these features are not available in cockpit plugin
await test.step('Export BP', async (step) => {
step.skip(!isHosted(), 'Exporting is not available in the plugin');
await exportBlueprint(page, blueprintName);
});
await test.step('Import BP', async (step) => {
step.skip(!isHosted(), 'Importing is not available in the plugin');
await importBlueprint(page, blueprintName);
});
await test.step('Review imported BP', async (step) => {
step.skip(!isHosted(), 'Importing is not available in the plugin');
await fillInImageOutputGuest(page);
await page
.getByRole('button', { name: 'Ansible Automation Platform' })
.click();
await expect(
page.getByRole('textbox', { name: 'ansible callback url' }),
).toHaveValue(validCallbackUrl);
await expect(
page.getByRole('textbox', { name: 'host config key' }),
).toBeEmpty();
await expect(
page.getByRole('textbox', { name: 'File upload' }),
).toHaveValue(validCertificate);
await page.getByRole('button', { name: 'Cancel' }).click();
});
});

View file

@ -0,0 +1,230 @@
import { expect } from '@playwright/test';
import { v4 as uuidv4 } from 'uuid';
import { FILE_SYSTEM_CUSTOMIZATION_URL } from '../../src/constants';
import { test } from '../fixtures/cleanup';
import { isHosted } from '../helpers/helpers';
import { ensureAuthenticated } from '../helpers/login';
import {
ibFrame,
navigateToLandingPage,
navigateToOptionalSteps,
} from '../helpers/navHelpers';
import {
createBlueprint,
deleteBlueprint,
exportBlueprint,
fillInDetails,
fillInImageOutputGuest,
importBlueprint,
registerLater,
} from '../helpers/wizardHelpers';
test('Create a blueprint with Filesystem customization', async ({
page,
cleanup,
}) => {
const blueprintName = 'test-' + uuidv4();
// Delete the blueprint after the run fixture
await cleanup.add(() => deleteBlueprint(page, blueprintName));
await ensureAuthenticated(page);
// Login, navigate to IB and get the frame
await navigateToLandingPage(page);
const frame = await ibFrame(page);
await test.step('Navigate to optional steps in Wizard', async () => {
await navigateToOptionalSteps(frame);
await registerLater(frame);
});
await test.step('Check URLs for documentation', async () => {
await frame
.getByRole('button', { name: 'File system configuration' })
.click();
await frame
.getByRole('radio', { name: 'Use automatic partitioning' })
.click();
const [newPageAutomatic] = await Promise.all([
page.context().waitForEvent('page'),
frame
.getByRole('link', {
name: 'Customizing file systems during the image creation',
})
.click(),
]);
await newPageAutomatic.waitForLoadState();
const finalUrlAutomatic = newPageAutomatic.url();
expect(finalUrlAutomatic).toContain(FILE_SYSTEM_CUSTOMIZATION_URL);
await newPageAutomatic.close();
await frame
.getByRole('radio', { name: 'Manually configure partitions' })
.click();
const [newPageManual] = await Promise.all([
page.context().waitForEvent('page'),
frame
.getByRole('link', {
name: 'Read more about manual configuration here',
})
.click(),
]);
await newPageManual.waitForLoadState();
const finalUrlManual = newPageManual.url();
expect(finalUrlManual).toContain(FILE_SYSTEM_CUSTOMIZATION_URL);
await newPageManual.close();
});
await test.step('Fill manually selected partitions', async () => {
await expect(frame.getByRole('button', { name: '/' })).toBeDisabled();
const closeRootButton = frame
.getByRole('row', {
name: 'Draggable row draggable button / xfs 10 GiB',
})
.getByRole('button')
.nth(3);
await expect(closeRootButton).toBeDisabled();
await frame.getByRole('button', { name: 'Add partition' }).click();
await frame.getByRole('button', { name: '/home' }).click();
await frame.getByRole('option', { name: '/tmp' }).click();
await frame
.getByRole('textbox', { name: 'mountpoint suffix' })
.fill('/usb');
await frame
.getByRole('gridcell', { name: '1', exact: true })
.getByPlaceholder('File system')
.fill('1000');
await frame.getByRole('button', { name: 'GiB' }).nth(1).click();
await frame.getByRole('option', { name: 'KiB' }).click();
const closeTmpButton = frame
.getByRole('row', {
name: 'Draggable row draggable button /tmp /usb xfs 1000 KiB',
})
.getByRole('button')
.nth(3);
await expect(closeTmpButton).toBeEnabled();
});
await test.step('Fill the BP details', async () => {
await frame.getByRole('button', { name: 'Review and finish' }).click();
await fillInDetails(frame, blueprintName);
});
await test.step('Create BP', async () => {
await createBlueprint(frame, blueprintName);
});
await test.step('Edit BP', async () => {
await frame.getByRole('button', { name: 'Edit blueprint' }).click();
await frame.getByLabel('Revisit File system configuration step').click();
const closeRootButton = frame
.getByRole('row', {
name: 'Draggable row draggable button / xfs 10 GiB',
})
.getByRole('button')
.nth(3);
await expect(closeRootButton).toBeDisabled();
const closeTmpButton = frame
.getByRole('row', {
name: 'Draggable row draggable button /tmp /usb xfs 1000 KiB',
})
.getByRole('button')
.nth(3);
await expect(closeTmpButton).toBeEnabled();
const usbTextbox = frame.getByRole('textbox', {
name: 'mountpoint suffix',
});
await expect(usbTextbox).toHaveValue('/usb');
await frame
.getByRole('gridcell', { name: '1000', exact: true })
.getByPlaceholder('File system')
.click();
await frame
.getByRole('gridcell', { name: '1000', exact: true })
.getByPlaceholder('File system')
.fill('1024');
await frame.getByRole('button', { name: '/tmp' }).click();
await frame.getByRole('option', { name: '/usr' }).click();
await expect(
frame.getByText(
'Sub-directories for the /usr mount point are no longer supported',
),
).toBeVisible();
await frame.getByRole('button', { name: '/usr' }).click();
await frame.getByRole('option', { name: '/srv' }).click();
await frame
.getByRole('textbox', { name: 'mountpoint suffix' })
.fill('/data');
await frame.getByRole('button', { name: 'KiB' }).click();
await frame.getByRole('option', { name: 'MiB' }).click();
await frame.getByRole('button', { name: 'Review and finish' }).click();
await frame
.getByRole('button', { name: 'Save changes to blueprint' })
.click();
});
// This is for hosted service only as these features are not available in cockpit plugin
await test.step('Export BP', async (step) => {
step.skip(!isHosted(), 'Exporting is not available in the plugin');
await exportBlueprint(page, blueprintName);
});
await test.step('Import BP', async (step) => {
step.skip(!isHosted(), 'Importing is not available in the plugin');
await importBlueprint(page, blueprintName);
});
await test.step('Review imported BP', async (step) => {
step.skip(!isHosted(), 'Importing is not available in the plugin');
await fillInImageOutputGuest(page);
await frame
.getByRole('button', { name: 'File system configuration' })
.click();
const closeRootButton = frame
.getByRole('row', {
name: 'Draggable row draggable button / xfs 10 GiB',
})
.getByRole('button')
.nth(3);
await expect(closeRootButton).toBeDisabled();
const closeTmpButton = frame
.getByRole('row', {
name: 'Draggable row draggable button /srv /data xfs 1 GiB',
})
.getByRole('button')
.nth(3);
await expect(closeTmpButton).toBeEnabled();
const dataTextbox = frame.getByRole('textbox', {
name: 'mountpoint suffix',
});
await expect(dataTextbox).toHaveValue('/data');
const size = frame
.getByRole('gridcell', { name: '1', exact: true })
.getByPlaceholder('File system');
await expect(size).toHaveValue('1');
const unitButton = frame.getByRole('button', { name: 'GiB' }).nth(1);
await expect(unitButton).toBeVisible();
await page.getByRole('button', { name: 'Cancel' }).click();
});
});

View file

@ -1,18 +1,22 @@
import { expect } from '@playwright/test';
import { v4 as uuidv4 } from 'uuid';
import { test } from '../fixtures/cleanup';
import { test } from '../fixtures/customizations';
import { isHosted } from '../helpers/helpers';
import { login } from '../helpers/login';
import { navigateToOptionalSteps, ibFrame } from '../helpers/navHelpers';
import { ensureAuthenticated } from '../helpers/login';
import {
ibFrame,
navigateToLandingPage,
navigateToOptionalSteps,
} from '../helpers/navHelpers';
import {
registerLater,
fillInDetails,
createBlueprint,
fillInImageOutputGuest,
deleteBlueprint,
exportBlueprint,
fillInDetails,
fillInImageOutputGuest,
importBlueprint,
registerLater,
} from '../helpers/wizardHelpers';
test('Create a blueprint with Firewall customization', async ({
@ -24,8 +28,10 @@ test('Create a blueprint with Firewall customization', async ({
// Delete the blueprint after the run fixture
await cleanup.add(() => deleteBlueprint(page, blueprintName));
// Login, navigate to IB and get the frame
await login(page);
await ensureAuthenticated(page);
// Navigate to IB landing page and get the frame
await navigateToLandingPage(page);
const frame = await ibFrame(page);
await test.step('Navigate to optional steps in Wizard', async () => {
@ -57,19 +63,29 @@ test('Create a blueprint with Firewall customization', async ({
await test.step('Select and incorrectly fill the ports in Firewall step', async () => {
await frame.getByPlaceholder('Add ports').fill('x');
await frame.getByRole('button', { name: 'Add ports' }).click();
await expect(frame.getByText('Invalid format.').nth(0)).toBeVisible();
await expect(
frame
.getByText(
'Expected format: <port/port-name>:<protocol>. Example: 8080:tcp, ssh:tcp',
)
.nth(0),
).toBeVisible();
});
await test.step('Select and incorrectly fill the disabled services in Firewall step', async () => {
await frame.getByPlaceholder('Add disabled service').fill('1');
await frame.getByRole('button', { name: 'Add disabled service' }).click();
await expect(frame.getByText('Invalid format.').nth(1)).toBeVisible();
await expect(
frame.getByText('Expected format: <service-name>. Example: sshd').nth(0),
).toBeVisible();
});
await test.step('Select and incorrectly fill the enabled services in Firewall step', async () => {
await frame.getByPlaceholder('Add enabled service').fill('ťčš');
await frame.getByRole('button', { name: 'Add enabled service' }).click();
await expect(frame.getByText('Invalid format.').nth(2)).toBeVisible();
await expect(
frame.getByText('Expected format: <service-name>. Example: sshd').nth(1),
).toBeVisible();
});
await test.step('Fill the BP details', async () => {

View file

@ -1,18 +1,22 @@
import { expect } from '@playwright/test';
import { v4 as uuidv4 } from 'uuid';
import { test } from '../fixtures/cleanup';
import { test } from '../fixtures/customizations';
import { isHosted } from '../helpers/helpers';
import { login } from '../helpers/login';
import { navigateToOptionalSteps, ibFrame } from '../helpers/navHelpers';
import { ensureAuthenticated } from '../helpers/login';
import {
ibFrame,
navigateToLandingPage,
navigateToOptionalSteps,
} from '../helpers/navHelpers';
import {
registerLater,
fillInDetails,
createBlueprint,
fillInImageOutputGuest,
deleteBlueprint,
exportBlueprint,
fillInDetails,
fillInImageOutputGuest,
importBlueprint,
registerLater,
} from '../helpers/wizardHelpers';
test('Create a blueprint with Hostname customization', async ({
@ -25,8 +29,10 @@ test('Create a blueprint with Hostname customization', async ({
// Delete the blueprint after the run fixture
await cleanup.add(() => deleteBlueprint(page, blueprintName));
// Login, navigate to IB and get the frame
await login(page);
await ensureAuthenticated(page);
// Navigate to IB landing page and get the frame
await navigateToLandingPage(page);
const frame = await ibFrame(page);
await test.step('Navigate to optional steps in Wizard', async () => {
@ -77,7 +83,7 @@ test('Create a blueprint with Hostname customization', async ({
await fillInImageOutputGuest(page);
await page.getByRole('button', { name: 'Hostname' }).click();
await expect(
page.getByRole('textbox', { name: 'hostname input' })
page.getByRole('textbox', { name: 'hostname input' }),
).toHaveValue(hostname + 'edited');
await page.getByRole('button', { name: 'Cancel' }).click();
});

View file

@ -0,0 +1,133 @@
import { expect } from '@playwright/test';
import { v4 as uuidv4 } from 'uuid';
import { test } from '../fixtures/customizations';
import { isHosted } from '../helpers/helpers';
import { ensureAuthenticated } from '../helpers/login';
import {
ibFrame,
navigateToLandingPage,
navigateToOptionalSteps,
} from '../helpers/navHelpers';
import {
createBlueprint,
deleteBlueprint,
exportBlueprint,
fillInDetails,
fillInImageOutputGuest,
importBlueprint,
registerLater,
} from '../helpers/wizardHelpers';
test('Create a blueprint with Kernel customization', async ({
page,
cleanup,
}) => {
const blueprintName = 'test-' + uuidv4();
// Delete the blueprint after the run fixture
await cleanup.add(() => deleteBlueprint(page, blueprintName));
await ensureAuthenticated(page);
// Navigate to IB landing page and get the frame
await navigateToLandingPage(page);
const frame = await ibFrame(page);
await test.step('Navigate to optional steps in Wizard', async () => {
await navigateToOptionalSteps(frame);
await registerLater(frame);
});
await test.step('Select and fill the Kernel step', async () => {
await frame.getByRole('button', { name: 'Kernel' }).click();
await frame.getByRole('button', { name: 'Menu toggle' }).click();
await frame.getByRole('option', { name: 'kernel', exact: true }).click();
await frame.getByPlaceholder('Add kernel argument').fill('rootwait');
await frame.getByRole('button', { name: 'Add kernel argument' }).click();
await frame
.getByPlaceholder('Add kernel argument')
.fill('invalid$argument');
await frame.getByRole('button', { name: 'Add kernel argument' }).click();
await expect(
frame.getByText(
'Expected format: <kernel-argument>. Example: console=tty0',
),
).toBeVisible();
await frame.getByPlaceholder('Select kernel package').fill('new-package');
await frame
.getByRole('option', { name: 'Custom kernel package "new-' })
.click();
await expect(
frame.getByRole('heading', { name: 'Warning alert: Custom kernel' }),
).toBeVisible();
await frame.getByRole('button', { name: 'Clear input' }).first().click();
await frame.getByRole('button', { name: 'Menu toggle' }).click();
await expect(
frame.getByRole('option', { name: 'new-package' }),
).toBeVisible();
await frame.getByPlaceholder('Select kernel package').fill('f');
await expect(
frame.getByRole('option', {
name: '"f" is not a valid kernel package name',
}),
).toBeVisible();
await frame.getByPlaceholder('Add kernel argument').fill('console=tty0');
await frame.getByRole('button', { name: 'Add kernel argument' }).click();
await frame.getByPlaceholder('Add kernel argument').fill('xxnosmp');
await frame.getByRole('button', { name: 'Add kernel argument' }).click();
await frame
.getByPlaceholder('Add kernel argument')
.fill('console=ttyS0,115200n8');
await frame.getByRole('button', { name: 'Add kernel argument' }).click();
await frame.getByRole('button', { name: 'Review and finish' }).click();
});
await test.step('Fill the BP details', async () => {
await fillInDetails(frame, blueprintName);
});
await test.step('Create BP', async () => {
await createBlueprint(frame, blueprintName);
});
await test.step('Edit BP', async () => {
await frame.getByRole('button', { name: 'Edit blueprint' }).click();
await frame.getByLabel('Revisit Kernel step').click();
await frame.getByRole('button', { name: 'Menu toggle' }).click();
await frame.getByRole('option', { name: 'kernel', exact: true }).click();
await frame.getByPlaceholder('Add kernel argument').fill('new=argument');
await frame.getByRole('button', { name: 'Add kernel argument' }).click();
await frame.getByRole('button', { name: 'Close xxnosmp' }).click();
await frame.getByRole('button', { name: 'Review and finish' }).click();
await frame
.getByRole('button', { name: 'Save changes to blueprint' })
.click();
});
// This is for hosted service only as these features are not available in cockpit plugin
await test.step('Export BP', async (step) => {
step.skip(!isHosted(), 'Exporting is not available in the plugin');
await exportBlueprint(page, blueprintName);
});
await test.step('Import BP', async (step) => {
step.skip(!isHosted(), 'Importing is not available in the plugin');
await importBlueprint(page, blueprintName);
});
await test.step('Review imported BP', async (step) => {
step.skip(!isHosted(), 'Importing is not available in the plugin');
await fillInImageOutputGuest(frame);
await frame.getByRole('button', { name: 'Kernel' }).click();
await expect(frame.getByPlaceholder('Select kernel package')).toHaveValue(
'kernel',
);
await expect(frame.getByText('rootwait')).toBeVisible();
await expect(frame.getByText('console=tty0')).toBeVisible();
await expect(frame.getByText('console=ttyS0,115200n8')).toBeVisible();
await expect(frame.getByText('new=argument')).toBeVisible();
await expect(frame.getByText('xxnosmp')).toBeHidden();
await frame.getByRole('button', { name: 'Cancel' }).click();
});
});

View file

@ -1,18 +1,22 @@
import { expect } from '@playwright/test';
import { v4 as uuidv4 } from 'uuid';
import { test } from '../fixtures/cleanup';
import { test } from '../fixtures/customizations';
import { isHosted } from '../helpers/helpers';
import { login } from '../helpers/login';
import { navigateToOptionalSteps, ibFrame } from '../helpers/navHelpers';
import { ensureAuthenticated } from '../helpers/login';
import {
ibFrame,
navigateToLandingPage,
navigateToOptionalSteps,
} from '../helpers/navHelpers';
import {
registerLater,
fillInDetails,
createBlueprint,
fillInImageOutputGuest,
deleteBlueprint,
exportBlueprint,
fillInDetails,
fillInImageOutputGuest,
importBlueprint,
registerLater,
} from '../helpers/wizardHelpers';
test('Create a blueprint with Locale customization', async ({
@ -24,8 +28,10 @@ test('Create a blueprint with Locale customization', async ({
// Delete the blueprint after the run fixture
await cleanup.add(() => deleteBlueprint(page, blueprintName));
// Login, navigate to IB and get the frame
await login(page);
await ensureAuthenticated(page);
// Navigate to IB landing page and get the frame
await navigateToLandingPage(page);
const frame = await ibFrame(page);
await test.step('Navigate to optional steps in Wizard', async () => {
@ -36,27 +42,45 @@ test('Create a blueprint with Locale customization', async ({
await test.step('Select and fill the Locale step', async () => {
await frame.getByRole('button', { name: 'Locale' }).click();
await frame.getByPlaceholder('Select a language').fill('fy');
await frame.getByRole('option', { name: 'fy_DE.UTF-' }).click();
await frame
.getByRole('option', { name: 'Western Frisian - Germany (fy_DE.UTF-8)' })
.click();
await expect(
frame.getByRole('button', { name: 'Close fy_DE.UTF-' })
frame.getByRole('button', {
name: 'Close Western Frisian - Germany (fy_DE.UTF-8)',
}),
).toBeEnabled();
await frame.getByRole('button', { name: 'Close fy_DE.UTF-' }).click();
await frame
.getByRole('button', {
name: 'Close Western Frisian - Germany (fy_DE.UTF-8)',
})
.click();
await expect(
frame.getByRole('button', { name: 'Close fy_DE.UTF-' })
frame.getByRole('button', {
name: 'Close Western Frisian - Germany (fy_DE.UTF-8)',
}),
).toBeHidden();
await frame.getByPlaceholder('Select a language').fill('fy');
await frame.getByRole('option', { name: 'fy_DE.UTF-' }).click();
await frame
.getByRole('option', { name: 'Western Frisian - Germany (fy_DE.UTF-8)' })
.click();
await expect(
frame.getByRole('button', { name: 'Close fy_DE.UTF-' })
frame.getByRole('button', {
name: 'Close Western Frisian - Germany (fy_DE.UTF-8)',
}),
).toBeEnabled();
await frame.getByPlaceholder('Select a language').fill('aa');
await frame.getByRole('option', { name: 'aa_DJ.UTF-' }).click();
await frame
.getByRole('option', { name: 'aa - Djibouti (aa_DJ.UTF-8)' })
.click();
await expect(
frame.getByRole('button', { name: 'Close aa_DJ.UTF-' })
frame.getByRole('button', { name: 'Close aa - Djibouti (aa_DJ.UTF-8)' }),
).toBeEnabled();
await frame.getByPlaceholder('Select a language').fill('aa');
await expect(
frame.getByText('aa_DJ.UTF-8Language already addedaa_ER.UTF-8aa_ET.UTF-')
frame.getByText(
'aa - Djibouti (aa_DJ.UTF-8)Language already addedaa - Eritrea (aa_ER.UTF-8)aa - Ethiopia (aa_ET.UTF-8)',
),
).toBeAttached();
await frame.getByPlaceholder('Select a language').fill('xxx');
await expect(frame.getByText('No results found for')).toBeAttached();
@ -78,15 +102,19 @@ test('Create a blueprint with Locale customization', async ({
await frame.getByRole('button', { name: 'Edit blueprint' }).click();
await frame.getByLabel('Revisit Locale step').click();
await expect(
frame.getByRole('button', { name: 'Close fy_DE.UTF-' })
frame.getByRole('button', {
name: 'Close Western Frisian - Germany (fy_DE.UTF-8)',
}),
).toBeEnabled();
await expect(
frame.getByRole('button', { name: 'Close aa_DJ.UTF-' })
frame.getByRole('button', { name: 'Close aa - Djibouti (aa_DJ.UTF-8)' }),
).toBeEnabled();
await frame.getByPlaceholder('Select a language').fill('aa');
await frame.getByRole('option', { name: 'aa_ER.UTF-' }).click();
await frame
.getByRole('option', { name: 'aa - Eritrea (aa_ER.UTF-8)' })
.click();
await expect(
frame.getByRole('button', { name: 'Close aa_ER.UTF-' })
frame.getByRole('button', { name: 'Close aa - Eritrea (aa_ER.UTF-8)' }),
).toBeEnabled();
await frame.getByRole('button', { name: 'Clear input' }).click();
await frame.getByRole('button', { name: 'Menu toggle' }).nth(1).click();
@ -113,16 +141,18 @@ test('Create a blueprint with Locale customization', async ({
await fillInImageOutputGuest(page);
await page.getByRole('button', { name: 'Locale' }).click();
await expect(
frame.getByRole('button', { name: 'Close fy_DE.UTF-' })
frame.getByRole('button', {
name: 'Close Western Frisian - Germany (fy_DE.UTF-8)',
}),
).toBeEnabled();
await expect(
frame.getByRole('button', { name: 'Close aa_DJ.UTF-' })
frame.getByRole('button', { name: 'Close aa - Djibouti (aa_DJ.UTF-8)' }),
).toBeEnabled();
await expect(
frame.getByRole('button', { name: 'Close aa_ER.UTF-' })
frame.getByRole('button', { name: 'Close aa - Eritrea (aa_ER.UTF-8)' }),
).toBeEnabled();
await expect(frame.getByPlaceholder('Select a keyboard')).toHaveValue(
'ANSI-dvorak'
'ANSI-dvorak',
);
await page.getByRole('button', { name: 'Cancel' }).click();
});

View file

@ -0,0 +1,189 @@
import { expect } from '@playwright/test';
import { v4 as uuidv4 } from 'uuid';
import { test } from '../fixtures/cleanup';
import { isHosted } from '../helpers/helpers';
import { ensureAuthenticated } from '../helpers/login';
import { ibFrame, navigateToLandingPage } from '../helpers/navHelpers';
import {
createBlueprint,
deleteBlueprint,
exportBlueprint,
fillInDetails,
fillInImageOutputGuest,
importBlueprint,
registerLater,
} from '../helpers/wizardHelpers';
test('Create a blueprint with OpenSCAP customization', async ({
page,
cleanup,
}) => {
const blueprintName = 'test-' + uuidv4();
test.skip(!isHosted(), 'Exporting is not available in the plugin');
// Delete the blueprint after the run fixture
await cleanup.add(() => deleteBlueprint(page, blueprintName));
await ensureAuthenticated(page);
// Navigate to IB landing page and get the frame
await navigateToLandingPage(page);
const frame = await ibFrame(page);
await test.step('Select RHEL 9 and go to optional steps in Wizard', async () => {
await frame.getByRole('button', { name: 'Create image blueprint' }).click();
await frame.getByTestId('release_select').click();
await frame
.getByRole('option', {
name: 'Red Hat Enterprise Linux (RHEL) 9 Full support ends: May 2027 | Maintenance',
})
.click();
await frame.getByRole('checkbox', { name: 'Virtualization' }).click();
await frame.getByRole('button', { name: 'Next' }).click();
await registerLater(frame);
});
await test.step('Select only OpenSCAP, and check if dependencies are preselected', async () => {
await frame.getByRole('button', { name: 'Compliance' }).click();
await frame.getByRole('textbox', { name: 'Type to filter' }).fill('cis');
await frame
.getByRole('option', {
name: 'CIS Red Hat Enterprise Linux 9 Benchmark for Level 1 - Server This profile',
})
.click();
await frame
.getByRole('button', { name: 'File system configuration' })
.click();
await expect(
frame
.getByRole('row', {
name: 'Draggable row draggable button /tmp xfs 1 GiB',
})
.getByRole('button')
.nth(3),
).toBeVisible();
await frame.getByRole('button', { name: 'Additional packages' }).click();
await frame.getByRole('button', { name: 'Selected (8)' }).click();
await expect(frame.getByRole('gridcell', { name: 'aide' })).toBeVisible();
await expect(frame.getByRole('gridcell', { name: 'chrony' })).toBeVisible();
await expect(
frame.getByRole('gridcell', { name: 'firewalld' }),
).toBeVisible();
await expect(
frame.getByRole('gridcell', { name: 'libpwquality' }),
).toBeVisible();
await expect(
frame.getByRole('gridcell', { name: 'libselinux' }),
).toBeVisible();
await expect(
frame.getByRole('gridcell', { name: 'nftables' }),
).toBeVisible();
await expect(frame.getByRole('gridcell', { name: 'sudo' })).toBeVisible();
await expect(
frame.getByRole('gridcell', { name: 'systemd-journal-remote' }),
).toBeVisible();
await frame.getByRole('button', { name: 'Systemd services' }).click();
await expect(
frame.getByText('Required by OpenSCAPcrondfirewalldsystemd-journald'),
).toBeVisible();
await frame.getByPlaceholder('Add masked service').fill('nftables');
await frame.getByPlaceholder('Add masked service').press('Enter');
await expect(
frame.getByText('Masked service already exists'),
).toBeVisible();
await expect(frame.getByText('Required by OpenSCAPcupsnfs-')).toBeVisible();
await expect(frame.getByText('nfs-server')).toBeVisible();
await expect(frame.getByText('rpcbind')).toBeVisible();
await expect(frame.getByText('avahi-daemon')).toBeVisible();
await expect(frame.getByText('autofs')).toBeVisible();
await expect(frame.getByText('bluetooth')).toBeVisible();
await expect(frame.getByText('nftables')).toBeVisible();
await frame.getByRole('button', { name: 'Review and finish' }).click();
});
await test.step('Fill the BP details', async () => {
await fillInDetails(frame, blueprintName);
});
await test.step('Create BP', async () => {
await createBlueprint(frame, blueprintName);
});
await test.step('Edit BP', async () => {
await frame.getByRole('button', { name: 'Edit blueprint' }).click();
await frame.getByRole('button', { name: 'Compliance' }).click();
await expect(frame.getByText('Level 1 - Server')).toBeVisible();
await frame.getByRole('textbox', { name: 'Type to filter' }).fill('cis');
await frame
.getByRole('option', {
name: 'CIS Red Hat Enterprise Linux 9 Benchmark for Level 2 - Server This profile',
})
.click();
await frame.getByRole('button', { name: 'Kernel' }).click();
await expect(
frame.getByText('Required by OpenSCAPaudit_backlog_limit=8192audit='),
).toBeVisible();
await frame.getByRole('button', { name: 'Additional packages' }).click();
await frame.getByRole('button', { name: 'Selected (10)' }).click();
await expect(frame.getByRole('gridcell', { name: 'aide' })).toBeVisible();
await expect(
frame.getByRole('gridcell', { name: 'audit-libs' }),
).toBeVisible();
await expect(frame.getByRole('gridcell', { name: 'chrony' })).toBeVisible();
await expect(
frame.getByRole('gridcell', { name: 'firewalld' }),
).toBeVisible();
await expect(
frame.getByRole('gridcell', { name: 'libpwquality' }),
).toBeVisible();
await expect(
frame.getByRole('gridcell', { name: 'libselinux' }),
).toBeVisible();
await expect(
frame.getByRole('gridcell', { name: 'nftables' }),
).toBeVisible();
await expect(frame.getByRole('gridcell', { name: 'sudo' })).toBeVisible();
await frame.getByRole('button', { name: 'Systemd services' }).click();
await expect(
frame.getByText(
'Required by OpenSCAPauditdcrondfirewalldsystemd-journald',
),
).toBeVisible();
await frame.getByPlaceholder('Add masked service').fill('nftables');
await frame.getByPlaceholder('Add masked service').press('Enter');
await expect(
frame.getByText('Masked service already exists'),
).toBeVisible();
await expect(frame.getByText('Required by OpenSCAPcupsnfs-')).toBeVisible();
await expect(frame.getByText('nfs-server')).toBeVisible();
await expect(frame.getByText('rpcbind')).toBeVisible();
await expect(frame.getByText('avahi-daemon')).toBeVisible();
await expect(frame.getByText('autofs')).toBeVisible();
await expect(frame.getByText('bluetooth')).toBeVisible();
await expect(frame.getByText('nftables')).toBeVisible();
await frame.getByRole('button', { name: 'Review and finish' }).click();
await frame
.getByRole('button', { name: 'Save changes to blueprint' })
.click();
});
// This is for hosted service only as these features are not available in cockpit plugin
await test.step('Export BP', async () => {
await exportBlueprint(page, blueprintName);
});
await test.step('Import BP', async () => {
await importBlueprint(page, blueprintName);
});
await test.step('Review imported BP', async () => {
await fillInImageOutputGuest(page);
await page.getByRole('button', { name: 'Compliance' }).click();
await expect(frame.getByText('Level 2 - Server')).toBeVisible();
await page.getByRole('button', { name: 'Cancel' }).click();
});
});

View file

@ -1,18 +1,22 @@
import { expect } from '@playwright/test';
import { v4 as uuidv4 } from 'uuid';
import { test } from '../fixtures/cleanup';
import { test } from '../fixtures/customizations';
import { isHosted } from '../helpers/helpers';
import { login } from '../helpers/login';
import { navigateToOptionalSteps, ibFrame } from '../helpers/navHelpers';
import { ensureAuthenticated } from '../helpers/login';
import {
ibFrame,
navigateToLandingPage,
navigateToOptionalSteps,
} from '../helpers/navHelpers';
import {
registerLater,
fillInDetails,
createBlueprint,
fillInImageOutputGuest,
deleteBlueprint,
exportBlueprint,
fillInDetails,
fillInImageOutputGuest,
importBlueprint,
registerLater,
} from '../helpers/wizardHelpers';
test('Create a blueprint with Systemd customization', async ({
@ -24,8 +28,10 @@ test('Create a blueprint with Systemd customization', async ({
// Delete the blueprint after the run fixture
await cleanup.add(() => deleteBlueprint(page, blueprintName));
// Login, navigate to IB and get the frame
await login(page);
await ensureAuthenticated(page);
// Navigate to IB landing page and get the frame
await navigateToLandingPage(page);
const frame = await ibFrame(page);
await test.step('Navigate to optional steps in Wizard', async () => {
@ -58,15 +64,21 @@ test('Create a blueprint with Systemd customization', async ({
await test.step('Select and incorrectly fill all of the service fields', async () => {
await frame.getByPlaceholder('Add disabled service').fill('&&');
await frame.getByRole('button', { name: 'Add disabled service' }).click();
await expect(frame.getByText('Invalid format.').nth(0)).toBeVisible();
await expect(
frame.getByText('Expected format: <service-name>. Example: sshd').nth(0),
).toBeVisible();
await frame.getByPlaceholder('Add enabled service').fill('áá');
await frame.getByRole('button', { name: 'Add enabled service' }).click();
await expect(frame.getByText('Invalid format.').nth(1)).toBeVisible();
await expect(
frame.getByText('Expected format: <service-name>. Example: sshd').nth(1),
).toBeVisible();
await frame.getByPlaceholder('Add masked service').fill('78');
await frame.getByRole('button', { name: 'Add masked service' }).click();
await expect(frame.getByText('Invalid format.').nth(2)).toBeVisible();
await expect(
frame.getByText('Expected format: <service-name>. Example: sshd').nth(2),
).toBeVisible();
});
await test.step('Fill the BP details', async () => {

View file

@ -1,18 +1,22 @@
import { expect } from '@playwright/test';
import { v4 as uuidv4 } from 'uuid';
import { test } from '../fixtures/cleanup';
import { test } from '../fixtures/customizations';
import { isHosted } from '../helpers/helpers';
import { login } from '../helpers/login';
import { navigateToOptionalSteps, ibFrame } from '../helpers/navHelpers';
import { ensureAuthenticated } from '../helpers/login';
import {
ibFrame,
navigateToLandingPage,
navigateToOptionalSteps,
} from '../helpers/navHelpers';
import {
registerLater,
fillInDetails,
createBlueprint,
fillInImageOutputGuest,
deleteBlueprint,
exportBlueprint,
fillInDetails,
fillInImageOutputGuest,
importBlueprint,
registerLater,
} from '../helpers/wizardHelpers';
test('Create a blueprint with Timezone customization', async ({
@ -24,8 +28,10 @@ test('Create a blueprint with Timezone customization', async ({
// Delete the blueprint after the run fixture
await cleanup.add(() => deleteBlueprint(page, blueprintName));
// Login, navigate to IB and get the frame
await login(page);
await ensureAuthenticated(page);
// Navigate to IB landing page and get the frame
await navigateToLandingPage(page);
const frame = await ibFrame(page);
await test.step('Navigate to optional steps in Wizard', async () => {
@ -49,7 +55,11 @@ test('Create a blueprint with Timezone customization', async ({
await expect(frame.getByText('NTP server already exists.')).toBeVisible();
await frame.getByPlaceholder('Add NTP servers').fill('xxxx');
await frame.getByRole('button', { name: 'Add NTP server' }).click();
await expect(frame.getByText('Invalid format.')).toBeVisible();
await expect(
frame
.getByText('Expected format: <ntp-server>. Example: time.redhat.com')
.nth(0),
).toBeVisible();
await frame.getByPlaceholder('Add NTP servers').fill('0.cz.pool.ntp.org');
await frame.getByRole('button', { name: 'Add NTP server' }).click();
await expect(frame.getByText('0.cz.pool.ntp.org')).toBeVisible();
@ -76,12 +86,12 @@ test('Create a blueprint with Timezone customization', async ({
await frame.getByLabel('Revisit Timezone step').click();
await expect(frame.getByText('Canada/Saskatchewan')).toBeHidden();
await expect(frame.getByPlaceholder('Select a timezone')).toHaveValue(
'Europe/Stockholm'
'Europe/Stockholm',
);
await frame.getByPlaceholder('Select a timezone').fill('Europe');
await frame.getByRole('option', { name: 'Europe/Oslo' }).click();
await expect(frame.getByPlaceholder('Select a timezone')).toHaveValue(
'Europe/Oslo'
'Europe/Oslo',
);
await expect(frame.getByText('0.nl.pool.ntp.org')).toBeVisible();
await expect(frame.getByText('0.de.pool.ntp.org')).toBeVisible();
@ -108,7 +118,7 @@ test('Create a blueprint with Timezone customization', async ({
await fillInImageOutputGuest(page);
await frame.getByRole('button', { name: 'Timezone' }).click();
await expect(frame.getByPlaceholder('Select a timezone')).toHaveValue(
'Europe/Oslo'
'Europe/Oslo',
);
await expect(frame.getByText('0.nl.pool.ntp.org')).toBeVisible();
await expect(frame.getByText('0.de.pool.ntp.org')).toBeVisible();

View file

@ -11,7 +11,6 @@ export interface Cleanup {
}
export const test = oldTest.extend<WithCleanup>({
// eslint-disable-next-line no-empty-pattern
cleanup: async ({}, use) => {
const cleanupFns: Map<symbol, () => Promise<unknown>> = new Map();
@ -39,7 +38,7 @@ export const test = oldTest.extend<WithCleanup>({
async () => {
await Promise.all(Array.from(cleanupFns).map(([, fn]) => fn()));
},
{ box: true }
{ box: true },
);
},
});

View file

@ -0,0 +1,8 @@
// This is a common fixture for the customizations tests
import { mergeTests } from '@playwright/test';
import { test as cleanupTest } from './cleanup';
import { test as popupTest } from './popupHandler';
// Combine the fixtures into one
export const test = mergeTests(cleanupTest, popupTest);

View file

@ -0,0 +1,18 @@
import { test as base } from '@playwright/test';
import { closePopupsIfExist } from '../helpers/helpers';
export interface PopupHandlerFixture {
popupHandler: void;
}
// This fixture will close any popups that might get opened during the test execution
export const test = base.extend<PopupHandlerFixture>({
popupHandler: [
async ({ page }, use) => {
await closePopupsIfExist(page);
await use(undefined);
},
{ auto: true },
],
});

View file

@ -0,0 +1,12 @@
import { test as setup } from '@playwright/test';
import { login, storeStorageStateAndToken } from './helpers/login';
setup.describe('Setup', () => {
setup.describe.configure({ retries: 3 });
setup('Authenticate', async ({ page }) => {
await login(page);
await storeStorageStateAndToken(page);
});
});

View file

@ -1,4 +1,7 @@
import { type Page, expect } from '@playwright/test';
import { execSync } from 'child_process';
import { readFileSync } from 'node:fs';
import { expect, type Page } from '@playwright/test';
export const togglePreview = async (page: Page) => {
const toggleSwitch = page.locator('#preview-toggle');
@ -21,19 +24,64 @@ export const isHosted = (): boolean => {
export const closePopupsIfExist = async (page: Page) => {
const locatorsToCheck = [
page.locator('.pf-v5-c-alert.notification-item button'), // This closes all toast pop-ups
page.locator('.pf-v6-c-alert.notification-item button'), // This closes all toast pop-ups
page.locator(`button[id^="pendo-close-guide-"]`), // This closes the pendo guide pop-up
page.locator(`button[id="truste-consent-button"]`), // This closes the trusted consent pop-up
page.getByLabel('close-notification'), // This closes a one off info notification (May be covered by the toast above, needs recheck.)
page
.locator('iframe[name="intercom-modal-frame"]')
.contentFrame()
.getByRole('button', { name: 'Close' }),
.getByRole('button', { name: 'Close' }), // This closes the intercom pop-up
page
.locator('iframe[name="intercom-notifications-frame"]')
.contentFrame()
.getByRole('button', { name: 'Profile image for Rob Rob' })
.last(), // This closes the intercom pop-up notification at the bottom of the screen, the last notification is displayed first if stacked (different from the modal popup handled above)
];
for (const locator of locatorsToCheck) {
await page.addLocatorHandler(locator, async () => {
await locator.first().click(); // There can be multiple toast pop-ups
await locator.first().click({ timeout: 10_000, noWaitAfter: true }); // There can be multiple toast pop-ups
});
}
};
// copied over from constants
const ON_PREM_RELEASES = new Map([
['centos-10', 'CentOS Stream 10'],
['fedora-41', 'Fedora Linux 41'],
['fedora-42', 'Fedora Linux 42'],
['rhel-10', 'Red Hat Enterprise Linux (RHEL) 10'],
]);
/* eslint-disable @typescript-eslint/no-explicit-any */
export const getHostDistroName = (): string => {
const osRelData = readFileSync('/etc/os-release');
const lines = osRelData
.toString('utf-8')
.split('\n')
.filter((l) => l !== '');
const osRel = {};
for (const l of lines) {
const lineData = l.split('=');
(osRel as any)[lineData[0]] = lineData[1].replace(/"/g, '');
}
// strip minor version from rhel
const distro = ON_PREM_RELEASES.get(
`${(osRel as any)['ID']}-${(osRel as any)['VERSION_ID'].split('.')[0]}`,
);
if (distro === undefined) {
/* eslint-disable no-console */
console.error('getHostDistroName failed, os-release config:', osRel);
throw new Error('getHostDistroName failed, distro undefined');
}
return distro;
};
export const getHostArch = (): string => {
return execSync('uname -m').toString('utf-8').replace(/\s/g, '');
};

View file

@ -1,4 +1,6 @@
import { type Page, expect } from '@playwright/test';
import path from 'path';
import { expect, type Page } from '@playwright/test';
import { closePopupsIfExist, isHosted, togglePreview } from './helpers';
import { ibFrame } from './navHelpers';
@ -21,6 +23,38 @@ export const login = async (page: Page) => {
return loginCockpit(page, user, password);
};
/**
* Checks if the user is already authenticated, if not, logs them in
* @param page - the page object
*/
export const ensureAuthenticated = async (page: Page) => {
// Navigate to the target page
if (isHosted()) {
await page.goto('/insights/image-builder/landing');
} else {
await page.goto('/cockpit-image-builder');
}
// Check for authentication success indicator
const successIndicator = isHosted()
? page.getByRole('heading', { name: 'All images' })
: ibFrame(page).getByRole('heading', { name: 'All images' });
let isAuthenticated = false;
try {
// Give it a 30 second period to load, it's less expensive than having to rerun the test
await expect(successIndicator).toBeVisible({ timeout: 30000 });
isAuthenticated = true;
} catch {
isAuthenticated = false;
}
if (!isAuthenticated) {
// Not authenticated, need to login
await login(page);
}
};
const loginCockpit = async (page: Page, user: string, password: string) => {
await page.goto('/cockpit-image-builder');
@ -31,46 +65,74 @@ const loginCockpit = async (page: Page, user: string, password: string) => {
// image-builder lives inside an iframe
const frame = ibFrame(page);
// cockpit-image-builder needs superuser, expect an error message
// when the user does not have admin priviliges
await expect(
frame.getByRole('heading', { name: 'Access is limited' })
).toBeVisible();
await page.getByRole('button', { name: 'Limited access' }).click();
try {
// Check if the user already has administrative access
await expect(
page.getByRole('button', { name: 'Administrative access' }),
).toBeVisible();
} catch {
// If not, try to gain it
// cockpit-image-builder needs superuser, expect an error message
// when the user does not have admin priviliges
await expect(
frame.getByRole('heading', { name: 'Access is limited' }),
).toBeVisible();
await page.getByRole('button', { name: 'Limited access' }).click();
// different popup opens based on type of account (can be passwordless)
const authenticateButton = page.getByRole('button', { name: 'Authenticate' });
const closeButton = page.getByText('Close');
await expect(authenticateButton.or(closeButton)).toBeVisible();
// different popup opens based on type of account (can be passwordless)
const authenticateButton = page.getByRole('button', {
name: 'Authenticate',
});
const closeButton = page.getByText('Close');
await expect(authenticateButton.or(closeButton)).toBeVisible();
if (await authenticateButton.isVisible()) {
// with password
await page.getByRole('textbox', { name: 'Password' }).fill(password);
await authenticateButton.click();
}
if (await closeButton.isVisible()) {
// passwordless
await closeButton.click();
if (await authenticateButton.isVisible()) {
// with password
await page.getByRole('textbox', { name: 'Password' }).fill(password);
await authenticateButton.click();
}
if (await closeButton.isVisible()) {
// passwordless
await closeButton.click();
}
}
// expect to have administrative access
await expect(
page.getByRole('button', { name: 'Administrative access' })
page.getByRole('button', { name: 'Administrative access' }),
).toBeVisible();
await expect(
frame.getByRole('heading', { name: 'All images' })
frame.getByRole('heading', { name: 'All images' }),
).toBeVisible();
};
const loginConsole = async (page: Page, user: string, password: string) => {
await closePopupsIfExist(page);
await page.goto('/insights/image-builder/landing');
await page
.getByRole('textbox', { name: 'Red Hat login or email' })
.fill(user);
await page.getByRole('textbox', { name: 'Red Hat login' }).fill(user);
await page.getByRole('button', { name: 'Next' }).click();
await page.getByRole('textbox', { name: 'Password' }).fill(password);
await page.getByRole('button', { name: 'Log in' }).click();
await togglePreview(page);
await expect(page.getByRole('heading', { name: 'All images' })).toBeVisible();
};
export const storeStorageStateAndToken = async (page: Page) => {
const { cookies } = await page
.context()
.storageState({ path: path.join(__dirname, '../../.auth/user.json') });
if (isHosted()) {
// For hosted service, look for cs_jwt token
process.env.TOKEN = `Bearer ${
cookies.find((cookie) => cookie.name === 'cs_jwt')?.value
}`;
} else {
// For Cockpit, we don't need a TOKEN but we can still store it for consistency
const cockpitCookie = cookies.find((cookie) => cookie.name === 'cockpit');
if (cockpitCookie) {
process.env.TOKEN = cockpitCookie.value;
}
}
// eslint-disable-next-line playwright/no-wait-for-timeout
await page.waitForTimeout(100);
};

View file

@ -1,13 +1,20 @@
import type { FrameLocator, Page } from '@playwright/test';
import { expect, FrameLocator, Page } from '@playwright/test';
import { isHosted } from './helpers';
import { getHostArch, getHostDistroName, isHosted } from './helpers';
/**
* Opens the wizard, fills out the "Image Output" step, and navigates to the optional steps
* @param page - the page object
*/
export const navigateToOptionalSteps = async (page: Page | FrameLocator) => {
await page.getByRole('button', { name: 'Create blueprint' }).click();
await page.getByRole('button', { name: 'Create image blueprint' }).click();
if (!isHosted()) {
// wait until the distro and architecture aligns with the host
await expect(page.getByTestId('release_select')).toHaveText(
getHostDistroName(),
);
await expect(page.getByTestId('arch_select')).toHaveText(getHostArch());
}
await page.getByRole('checkbox', { name: 'Virtualization' }).click();
await page.getByRole('button', { name: 'Next' }).click();
};
@ -24,3 +31,15 @@ export const ibFrame = (page: Page): FrameLocator | Page => {
.locator('iframe[name="cockpit1\\:localhost\\/cockpit-image-builder"]')
.contentFrame();
};
/**
* Navigates to the landing page of the Image Builder
* @param page - the page object
*/
export const navigateToLandingPage = async (page: Page) => {
if (isHosted()) {
await page.goto('/insights/image-builder/landing');
} else {
await page.goto('/cockpit-image-builder');
}
};

View file

@ -1,7 +1,7 @@
import { expect, FrameLocator, type Page, test } from '@playwright/test';
import { isHosted } from './helpers';
import { ibFrame } from './navHelpers';
import { closePopupsIfExist, isHosted } from './helpers';
import { ibFrame, navigateToLandingPage } from './navHelpers';
/**
* Clicks the create button, handles the modal, clicks the button again and selecets the BP in the list
@ -10,13 +10,15 @@ import { ibFrame } from './navHelpers';
*/
export const createBlueprint = async (
page: Page | FrameLocator,
blueprintName: string
blueprintName: string,
) => {
await page.getByRole('button', { name: 'Create blueprint' }).click();
await page.getByRole('button', { name: 'Close' }).first().click();
await page.getByRole('button', { name: 'Create blueprint' }).click();
await page.getByRole('textbox', { name: 'Search input' }).fill(blueprintName);
await page.getByTestId('blueprint-card').getByText(blueprintName).click();
// the clickable blueprint cards are a bit awkward, so use the
// button's id instead
await page.locator(`button[id="${blueprintName}"]`).click();
};
/**
@ -29,7 +31,7 @@ export const createBlueprint = async (
*/
export const fillInDetails = async (
page: Page | FrameLocator,
blueprintName: string
blueprintName: string,
) => {
await page.getByRole('listitem').filter({ hasText: 'Details' }).click();
await page
@ -65,27 +67,41 @@ export const fillInImageOutputGuest = async (page: Page | FrameLocator) => {
/**
* Delete the blueprint with the given name
* Will locate to the Image Builder page and search for the blueprint first
* If the blueprint is not found, it will fail gracefully
* @param page - the page object
* @param blueprintName - the name of the blueprint to delete
*/
export const deleteBlueprint = async (page: Page, blueprintName: string) => {
// Since new browser is opened during the BP cleanup, we need to call the popup closer again
await closePopupsIfExist(page);
await test.step(
'Delete the blueprint with name: ' + blueprintName,
async () => {
// Locate back to the Image Builder page every time because the test can fail at any stage
await navigateToLandingPage(page);
const frame = await ibFrame(page);
await frame
.getByRole('textbox', { name: 'Search input' })
.fill(blueprintName);
await frame
.getByTestId('blueprint-card')
.getByText(blueprintName)
.click();
// Check if no blueprints found -> that means no blueprint was created -> fail gracefully and do not raise error
try {
await expect(
frame.getByRole('heading', { name: 'No blueprints found' }),
).toBeVisible({ timeout: 5_000 }); // Shorter timeout to avoid hanging uncessarily
return; // Fail gracefully, no blueprint to delete
// eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (error) {
// If the No BP heading was not found, it means the blueprint (possibly) was created -> continue with deletion
}
// the clickable blueprint cards are a bit awkward, so use the
// button's id instead
await frame.locator(`button[id="${blueprintName}"]`).click();
await frame.getByRole('button', { name: 'Menu toggle' }).click();
await frame.getByRole('menuitem', { name: 'Delete blueprint' }).click();
await frame.getByRole('button', { name: 'Delete' }).click();
},
{ box: true }
{ box: true },
);
};
@ -113,7 +129,7 @@ export const exportBlueprint = async (page: Page, blueprintName: string) => {
*/
export const importBlueprint = async (
page: Page | FrameLocator,
blueprintName: string
blueprintName: string,
) => {
if (isHosted()) {
await page.getByRole('button', { name: 'Import' }).click();
@ -122,7 +138,7 @@ export const importBlueprint = async (
.locator('input[type=file]')
.setInputFiles('../../downloads/' + blueprintName + '.json');
await expect(
page.getByRole('textbox', { name: 'File upload' })
page.getByRole('textbox', { name: 'File upload' }),
).not.toBeEmpty();
await page.getByRole('button', { name: 'Review and Finish' }).click();
}

View file

@ -1,75 +1,88 @@
import { readFileSync } from 'node:fs';
import TOML from '@ltd/j-toml';
import { expect, test } from '@playwright/test';
import { v4 as uuidv4 } from 'uuid';
import { isHosted } from './helpers/helpers';
import { login } from './helpers/login';
import { ibFrame } from './helpers/navHelpers';
import { closePopupsIfExist, isHosted } from './helpers/helpers';
import { ensureAuthenticated } from './helpers/login';
import { ibFrame, navigateToLandingPage } from './helpers/navHelpers';
test.describe.serial('test', () => {
const blueprintName = uuidv4();
test('create blueprint', async ({ page }) => {
await login(page);
await ensureAuthenticated(page);
await closePopupsIfExist(page);
// Navigate to IB landing page and get the frame
await navigateToLandingPage(page);
const frame = await ibFrame(page);
await frame.getByRole('heading', { name: 'Images About image builder' });
await frame.getByRole('heading', { name: 'Blueprints' });
frame.getByRole('heading', { name: 'Images About image builder' });
frame.getByRole('heading', { name: 'Blueprints' });
await frame.getByTestId('blueprints-create-button').click();
await frame.getByRole('heading', { name: 'Image output' });
await frame.getByTestId('checkbox-guest-image').click();
frame.getByRole('heading', { name: 'Image output' });
await frame
.getByRole('checkbox', { name: /Virtualization guest image/i })
.click();
await frame.getByRole('button', { name: 'Next', exact: true }).click();
if (isHosted()) {
await frame.getByRole('heading', {
frame.getByRole('heading', {
name: 'Register systems using this image',
});
await page.getByTestId('register-later-radio').click();
await page.getByRole('radio', { name: /Register later/i }).click();
await frame.getByRole('button', { name: 'Next', exact: true }).click();
}
await frame.getByRole('heading', { name: 'Compliance' });
frame.getByRole('heading', { name: 'Compliance' });
await frame.getByRole('button', { name: 'Next', exact: true }).click();
await frame.getByRole('heading', { name: 'File system configuration' });
frame.getByRole('heading', { name: 'File system configuration' });
await frame.getByRole('button', { name: 'Next', exact: true }).click();
if (isHosted()) {
await frame.getByRole('heading', { name: 'Repository snapshot' });
frame.getByRole('heading', { name: 'Repository snapshot' });
await frame.getByRole('button', { name: 'Next', exact: true }).click();
await frame.getByRole('heading', { name: 'Custom repositories' });
frame.getByRole('heading', { name: 'Custom repositories' });
await frame.getByRole('button', { name: 'Next', exact: true }).click();
}
await frame.getByRole('heading', { name: 'Additional packages' });
frame.getByRole('heading', { name: 'Additional packages' });
await frame.getByRole('button', { name: 'Next', exact: true }).click();
await frame.getByRole('heading', { name: 'Users' });
frame.getByRole('heading', { name: 'Users' });
await frame.getByRole('button', { name: 'Next', exact: true }).click();
await frame.getByRole('heading', { name: 'Timezone' });
frame.getByRole('heading', { name: 'Timezone' });
await frame.getByRole('button', { name: 'Next', exact: true }).click();
await frame.getByRole('heading', { name: 'Locale' });
frame.getByRole('heading', { name: 'Locale' });
await frame.getByRole('button', { name: 'Next', exact: true }).click();
await frame.getByRole('heading', { name: 'Hostname' });
frame.getByRole('heading', { name: 'Hostname' });
await frame.getByRole('button', { name: 'Next', exact: true }).click();
await frame.getByRole('heading', { name: 'Kernel' });
frame.getByRole('heading', { name: 'Kernel' });
await frame.getByRole('button', { name: 'Next', exact: true }).click();
await frame.getByRole('heading', { name: 'Firewall' });
frame.getByRole('heading', { name: 'Firewall' });
await frame.getByRole('button', { name: 'Next', exact: true }).click();
await frame.getByRole('heading', { name: 'Systemd services' });
frame.getByRole('heading', { name: 'Systemd services' });
await frame.getByRole('button', { name: 'Next', exact: true }).click();
if (isHosted()) {
await frame.getByRole('heading', { name: 'First boot configuration' });
frame.getByRole('heading', { name: 'Ansible Automation Platform' });
await frame.getByRole('button', { name: 'Next', exact: true }).click();
}
await frame.getByRole('heading', { name: 'Details' });
if (isHosted()) {
frame.getByRole('heading', { name: 'First boot configuration' });
await frame.getByRole('button', { name: 'Next', exact: true }).click();
}
frame.getByRole('heading', { name: 'Details' });
await frame.getByTestId('blueprint').fill(blueprintName);
await expect(frame.getByTestId('blueprint')).toHaveValue(blueprintName);
await frame.getByRole('button', { name: 'Next', exact: true }).click();
@ -79,22 +92,32 @@ test.describe.serial('test', () => {
await frame.getByRole('button', { name: 'Create blueprint' }).click();
await expect(
frame.locator('.pf-v5-c-card__title-text').getByText(blueprintName)
frame.locator('.pf-v6-c-card__title-text').getByText(
// if the name is too long, the blueprint card will have a truncated name.
blueprintName.length > 24
? blueprintName.slice(0, 24) + '...'
: blueprintName,
),
).toBeVisible();
});
test('edit blueprint', async ({ page }) => {
await ensureAuthenticated(page);
await closePopupsIfExist(page);
// package searching is really slow the first time in cockpit
if (!isHosted()) {
test.setTimeout(300000);
}
await login(page);
// Navigate to IB landing page and get the frame
await navigateToLandingPage(page);
const frame = await ibFrame(page);
await frame
.getByRole('textbox', { name: 'Search input' })
.fill(blueprintName);
await frame.getByText(blueprintName, { exact: true }).first().click();
// the clickable blueprint cards are a bit awkward, so use the
// button's id instead
await frame.locator(`button[id="${blueprintName}"]`).click();
await frame.getByRole('button', { name: 'Edit blueprint' }).click();
await frame.getByRole('button', { name: 'Additional packages' }).click();
@ -102,30 +125,37 @@ test.describe.serial('test', () => {
.getByTestId('packages-search-input')
.locator('input')
.fill('osbuild-composer');
await frame.getByTestId('packages-table').getByText('Searching');
await frame.getByRole('gridcell', { name: 'osbuild-composer' }).first();
frame.getByTestId('packages-table').getByText('Searching');
frame.getByRole('gridcell', { name: 'osbuild-composer' }).first();
await frame.getByRole('checkbox', { name: 'Select row 0' }).check();
await frame.getByRole('button', { name: 'Review and finish' }).click();
await frame.getByRole('button', { name: 'About packages' }).click();
await frame.getByRole('gridcell', { name: 'osbuild-composer' });
frame.getByRole('gridcell', { name: 'osbuild-composer' });
await frame.getByRole('button', { name: 'Close', exact: true }).click();
await frame
.getByRole('button', { name: 'Save changes to blueprint' })
.click();
await frame.getByRole('button', { name: 'Edit blueprint' }).click();
await frame.getByRole('button', { name: 'About packages' }).click();
await frame.getByRole('gridcell', { name: 'osbuild-composer' });
frame.getByRole('gridcell', { name: 'osbuild-composer' });
await frame.getByRole('button', { name: 'Close', exact: true }).click();
await frame.getByRole('button', { name: 'Cancel', exact: true }).click();
await frame.getByRole('heading', { name: 'All images' });
frame.getByRole('heading', { name: 'All images' });
});
test('build blueprint', async ({ page }) => {
await login(page);
await ensureAuthenticated(page);
await closePopupsIfExist(page);
// Navigate to IB landing page and get the frame
await navigateToLandingPage(page);
const frame = await ibFrame(page);
await frame
.getByRole('textbox', { name: 'Search input' })
.fill(blueprintName);
await frame.getByText(blueprintName, { exact: true }).first().click();
// the clickable blueprint cards are a bit awkward, so use the
// button's id instead
await frame.locator(`button[id="${blueprintName}"]`).click();
await frame.getByTestId('blueprint-build-image-menu-option').click();
// make sure the image is present
@ -133,18 +163,152 @@ test.describe.serial('test', () => {
.getByTestId('images-table')
.getByRole('button', { name: 'Details' })
.click();
await frame.getByText('Build Information');
frame.getByText('Build Information');
});
test('delete blueprint', async ({ page }) => {
await login(page);
await ensureAuthenticated(page);
await closePopupsIfExist(page);
// Navigate to IB landing page and get the frame
await navigateToLandingPage(page);
const frame = await ibFrame(page);
await frame
.getByRole('textbox', { name: 'Search input' })
.fill(blueprintName);
await frame.getByText(blueprintName, { exact: true }).first().click();
await frame.getByTestId('blueprint-action-menu-toggle').click();
// the clickable blueprint cards are a bit awkward, so use the
// button's id instead
await frame.locator(`button[id="${blueprintName}"]`).click();
await frame.getByRole('button', { name: /blueprint menu toggle/i }).click();
await frame.getByRole('menuitem', { name: 'Delete blueprint' }).click();
await frame.getByRole('button', { name: 'Delete' }).click();
});
test('cockpit worker config', async ({ page }) => {
if (isHosted()) {
return;
}
await ensureAuthenticated(page);
await closePopupsIfExist(page);
// Navigate to IB landing page and get the frame
await navigateToLandingPage(page);
await page.goto('/cockpit-image-builder');
const frame = ibFrame(page);
const header = frame.getByText('Configure AWS Uploads');
if (!(await header.isVisible())) {
await frame
.getByRole('button', { name: 'Configure Cloud Providers' })
.click();
await expect(header).toBeVisible();
}
const bucket = 'cockpit-ib-playwright-bucket';
const credentials = '/test/credentials';
const switchInput = frame.locator('#aws-config-switch');
await expect(switchInput).toBeVisible();
// introduce a wait time, since it takes some time to load the
// worker config file.
await page.waitForTimeout(1000);
// If this test fails for any reason, the config should already be loaded
// and visible on the retury. If it is go back to the landing page
if (await switchInput.isChecked()) {
await frame.getByRole('button', { name: 'Cancel' }).click();
await expect(
frame.getByRole('heading', { name: 'All images' }),
).toBeVisible();
} else {
const switchToggle = frame.locator('.pf-v6-c-switch');
await switchToggle.click();
await frame
.getByPlaceholder('AWS bucket')
// this doesn't need to exist, we're just testing that
// the form works as expected
.fill(bucket);
await frame.getByPlaceholder('Path to AWS credentials').fill(credentials);
await frame.getByRole('button', { name: 'Submit' }).click();
await expect(
frame.getByRole('heading', { name: 'All images' }),
).toBeVisible();
}
await frame
.getByRole('button', { name: 'Configure Cloud Providers' })
.click();
await expect(header).toBeVisible();
// introduce a wait time, since it takes some time to load the
// worker config file.
await page.waitForTimeout(1500);
await expect(frame.locator('#aws-config-switch')).toBeChecked();
await expect(frame.getByPlaceholder('AWS bucket')).toHaveValue(bucket);
await expect(frame.getByPlaceholder('Path to AWS credentials')).toHaveValue(
credentials,
);
await frame.getByRole('button', { name: 'Cancel' }).click();
const config = readFileSync('/etc/osbuild-worker/osbuild-worker.toml');
// this is for testing, the field `aws` should exist
// eslint-disable-next-line
const parsed = TOML.parse(config) as any;
expect(parsed.aws?.bucket).toBe(bucket);
expect(parsed.aws?.credentials).toBe(credentials);
});
const cockpitBlueprintname = uuidv4();
test('cockpit cloud upload', async ({ page }) => {
if (isHosted()) {
return;
}
await ensureAuthenticated(page);
await closePopupsIfExist(page);
// Navigate to IB landing page and get the frame
await navigateToLandingPage(page);
await page.goto('/cockpit-image-builder');
const frame = ibFrame(page);
frame.getByRole('heading', { name: 'Images About image builder' });
frame.getByRole('heading', { name: 'Blueprints' });
await frame.getByTestId('blueprints-create-button').click();
frame.getByRole('heading', { name: 'Image output' });
// the first card should be the AWS card
await frame.locator('.pf-v6-c-card').first().click();
await frame.getByRole('button', { name: 'Next', exact: true }).click();
await frame.getByRole('button', { name: 'Next', exact: true }).click();
await frame.getByRole('button', { name: 'Review and finish' }).click();
await frame.getByRole('button', { name: 'Back', exact: true }).click();
frame.getByRole('heading', { name: 'Details' });
await frame.getByTestId('blueprint').fill(cockpitBlueprintname);
await expect(frame.getByTestId('blueprint')).toHaveValue(
cockpitBlueprintname,
);
await frame.getByRole('button', { name: 'Next', exact: true }).click();
await frame.getByRole('button', { name: 'Create blueprint' }).click();
await frame.getByTestId('close-button-saveandbuild-modal').click();
await frame.getByRole('button', { name: 'Create blueprint' }).click();
await frame
.getByRole('textbox', { name: 'Search input' })
.fill(cockpitBlueprintname);
// the clickable blueprint cards are a bit awkward, so use the
// button's id instead
await frame.locator(`button[id="${cockpitBlueprintname}"]`).click();
await frame.getByTestId('blueprint-build-image-menu-option').click();
// make sure the image is present
await frame
.getByTestId('images-table')
.getByRole('button', { name: 'Details' })
.click();
frame.getByText('Build Information');
});
});

View file

@ -1,57 +0,0 @@
#!/bin/bash
# --------------------------------------------
# Export vars for helper scripts to use
# --------------------------------------------
# name of app-sre "application" folder this component lives in; needs to match for quay
export COMPONENT_NAME="image-builder-frontend"
# IMAGE should match the quay repo set by app.yaml in app-interface
export IMAGE="quay.io/cloudservices/image-builder-frontend"
export WORKSPACE=${WORKSPACE:-$APP_ROOT} # if running in jenkins, use the build's workspace
export APP_ROOT=$(pwd)
#16 is the default Node version. Change this to override it.
export NODE_BUILD_VERSION=20
# skip unit tests on frontend-build
export SKIP_VERIFY=True
COMMON_BUILDER=https://raw.githubusercontent.com/RedHatInsights/insights-frontend-builder-common/master
# --------------------------------------------
# Options that must be configured by app owner
# --------------------------------------------
export IQE_PLUGINS="image-builder"
export IQE_CJI_TIMEOUT="90m"
export IQE_MARKER_EXPRESSION="fe_pr_check"
export IQE_SELENIUM="true"
export IQE_ENV="ephemeral"
export IQE_IMAGE_TAG="image-builder"
export IQE_PARALLEL_ENABLED="false"
export RESERVE_DURATION="2h"
# bootstrap bonfire and it's config
CICD_URL=https://raw.githubusercontent.com/RedHatInsights/bonfire/master/cicd
curl -s "$CICD_URL"/bootstrap.sh >.cicd_bootstrap.sh && source .cicd_bootstrap.sh
# # source is preferred to | bash -s in this case to avoid a subshell
source <(curl -sSL $COMMON_BUILDER/src/frontend-build.sh)
# reserve ephemeral namespace
export DEPLOY_FRONTENDS="true"
export EXTRA_DEPLOY_ARGS="provisioning sources rhsm-api-proxy --set-template-ref rhsm-api-proxy=master"
export APP_NAME="image-builder-crc"
export DEPLOY_TIMEOUT="1200"
export REF_ENV="insights-stage"
# overwrites any resource limits imposed by bonfire
export COMPONENTS_W_RESOURCES="compliance notifications-backend notifications-engine"
source "$CICD_ROOT"/deploy_ephemeral_env.sh
# Run smoke tests using a ClowdJobInvocation (preferred)
# The contents of this script can be found at:
# https://raw.githubusercontent.com/RedHatInsights/bonfire/master/cicd/cji_smoke_test.sh
export COMPONENT_NAME="image-builder"
source "$CICD_ROOT"/cji_smoke_test.sh
# Post a comment with test run IDs to the PR
# The contents of this script can be found at:
# https://raw.githubusercontent.com/RedHatInsights/bonfire/master/cicd/post_test_results.sh
source "$CICD_ROOT"/post_test_results.sh

8
schutzbot/playwright.fmf Normal file
View file

@ -0,0 +1,8 @@
summary: run playwright tests
test: ./playwright_tests.sh
require:
- cockpit-image-builder
- podman
- nodejs
- nodejs-npm
duration: 30m

View file

@ -1,16 +1,16 @@
#!/bin/bash
set -euo pipefail
# As playwright isn't supported on fedora/el, install dependencies
# beforehand.
sudo dnf install -y \
alsa-lib \
libXrandr-devel \
libXdamage-devel \
libXcomposite-devel \
at-spi2-atk-devel \
cups \
atk
TMT_SOURCE_DIR=${TMT_SOURCE_DIR:-}
if [ -n "$TMT_SOURCE_DIR" ]; then
# Move to the directory with sources
cd "${TMT_SOURCE_DIR}/cockpit-image-builder"
npm ci
elif [ "${CI:-}" != "true" ]; then
# packit drops us into the schutzbot directory
cd ../
npm ci
fi
sudo systemctl enable --now cockpit.socket
@ -19,10 +19,13 @@ sudo usermod -aG wheel admin
echo "admin ALL=(ALL:ALL) NOPASSWD: ALL" | sudo tee "/etc/sudoers.d/admin-nopasswd"
function upload_artifacts {
mkdir -p /tmp/artifacts/extra-screenshots
USER="$(whoami)"
sudo chown -R "$USER:$USER" playwright-report
mv playwright-report /tmp/artifacts/
if [ -n "${TMT_TEST_DATA:-}" ]; then
mv playwright-report "$TMT_TEST_DATA"/playwright-report
else
USER="$(whoami)"
sudo chown -R "$USER:$USER" playwright-report
mv playwright-report /tmp/artifacts/
fi
}
trap upload_artifacts EXIT
@ -73,10 +76,12 @@ sudo podman run \
-e "CI=true" \
-e "PLAYWRIGHT_USER=admin" \
-e "PLAYWRIGHT_PASSWORD=foobar" \
-e "CURRENTS_PROJECT_ID=$CURRENTS_PROJECT_ID" \
-e "CURRENTS_RECORD_KEY=$CURRENTS_RECORD_KEY" \
-e "CURRENTS_PROJECT_ID=${CURRENTS_PROJECT_ID:-}" \
-e "CURRENTS_RECORD_KEY=${CURRENTS_RECORD_KEY:-}" \
--net=host \
-v "$PWD:/tests" \
-v '/etc:/etc' \
-v '/etc/os-release:/etc/os-release' \
--privileged \
--rm \
--init \

View file

@ -1 +1 @@
7b4735d287dd0950e0a6f47dde65b62b0f239da1
cf0a810fd3b75fa27139746c4dfe72222e13dcba

View file

@ -1,7 +1,7 @@
#!/bin/bash
# if a user is logged in to the runner, wait until they're done
while (( $(who -s | wc -l) > 0 )); do
while (( $(who -u | grep -v '?' | wc -l) > 0 )); do
echo "Waiting for user(s) to log off"
sleep 30
done

View file

@ -1,7 +1,7 @@
import React, { useEffect } from 'react';
import { useChrome } from '@redhat-cloud-services/frontend-components/useChrome';
import NotificationsPortal from '@redhat-cloud-services/frontend-components-notifications/NotificationPortal';
import useChrome from '@redhat-cloud-services/frontend-components/useChrome';
import NotificationsProvider from '@redhat-cloud-services/frontend-components-notifications/NotificationsProvider';
import '@patternfly/patternfly/patternfly-addons.css';
import { Router } from './Router';
@ -14,10 +14,21 @@ const App = () => {
hideGlobalFilter(true);
}, [hideGlobalFilter, updateDocumentTitle]);
// Necessary for in-page wizard overflow to behave properly
// The .chr-render class is defined in Insights Chrome:
// https://github.com/RedHatInsights/insights-chrome/blob/fe573705020ff64003ac9e6101aa978b471fe6f2/src/sass/chrome.scss#L82
useEffect(() => {
const chrRenderDiv = document.querySelector('.chr-render');
if (chrRenderDiv) {
(chrRenderDiv as HTMLElement).style.overflow = 'auto';
}
}, []);
return (
<React.Fragment>
<NotificationsPortal />
<Router />
<NotificationsProvider>
<Router />
</NotificationsProvider>
</React.Fragment>
);
};

78
src/AppCockpit.scss Normal file
View file

@ -0,0 +1,78 @@
@font-face {
font-family: 'Red Hat Text';
font-style: normal;
font-weight: 400 500;
src: url('/cockpit/static/fonts/RedHatText/RedHatTextVF.woff2')
format('woff2-variations');
font-display: fallback;
}
@font-face {
font-family: 'Red Hat Text';
font-style: italic;
font-weight: 400 500;
src: url('/cockpit/static/fonts/RedHatText/RedHatTextVF-Italic.woff2')
format('woff2-variations');
font-display: fallback;
}
@font-face {
font-family: 'Red Hat Display';
font-style: normal;
font-weight: 400 700;
src: url('/cockpit/static/fonts/RedHatDisplay/RedHatDisplayVF.woff2')
format('woff2-variations');
font-display: fallback;
}
@font-face {
font-family: 'Red Hat Display';
font-style: italic;
font-weight: 400 700;
src: url('/cockpit/static/fonts/RedHatDisplay/RedHatDisplayVF-Italic.woff2')
format('woff2-variations');
font-display: fallback;
}
@font-face {
font-family: RedHatText;
font-style: normal;
font-weight: 400;
src: url('/cockpit/static/fonts/RedHatText-Regular.woff2') format('woff2');
font-display: fallback;
}
@font-face {
font-family: RedHatText;
font-style: normal;
font-weight: 700;
src: url('/cockpit/static/fonts/RedHatText-Medium.woff2') format('woff2');
font-display: fallback;
}
// Override as PF Page doesn't allow empty masthead and sidebar
@media (min-width: 75rem) {
.pf-v6-c-page.no-masthead-sidebar {
/* custom class to scope this style to a specific page component instance */
--pf-v6-c-page__main-container--GridArea: var(
--pf-v6-c-page--masthead--main-container--GridArea
);
}
}
.pf-v6-c-page__main-section {
padding-inline: 0;
padding-block-start: 0;
}
.pf-v6-c-page__main > section.pf-v6-c-page__main-section:not(.pf-m-padding) {
padding-inline: 0;
}
.pf-v6-c-card {
&.pf-m-clickable::before,
&.pf-m-selectable::before {
border: var(--pf-v6-c-card--BorderColor) var(--pf-v6-c-card--BorderStyle)
var(--pf-v6-c-card--BorderWidth) !important;
}
}

View file

@ -3,11 +3,14 @@ import '@patternfly/patternfly/patternfly-addons.css';
import React from 'react';
import NotificationsPortal from '@redhat-cloud-services/frontend-components-notifications/NotificationPortal';
import 'cockpit-dark-theme';
import { Page, PageSection } from '@patternfly/react-core';
import NotificationsProvider from '@redhat-cloud-services/frontend-components-notifications/NotificationsProvider';
import { createRoot } from 'react-dom/client';
import { Provider } from 'react-redux';
import { HashRouter } from 'react-router-dom';
import './AppCockpit.scss';
import { NotReady, RequireAdmin } from './Components/Cockpit';
import { Router } from './Router';
import { onPremStore as store } from './store';
@ -28,16 +31,21 @@ const Application = () => {
return (
<React.Fragment>
<NotificationsPortal />
<HashRouter>
<Router />
</HashRouter>
<NotificationsProvider>
<HashRouter>
<Router />
</HashRouter>
</NotificationsProvider>
</React.Fragment>
);
};
const ImageBuilder = () => (
<Provider store={store}>
<Application />
<Page className='no-masthead-sidebar' isContentFilled>
<PageSection>
<Application />
</PageSection>
</Page>
</Provider>
);

View file

@ -30,7 +30,7 @@ export const BlueprintActionsMenu: React.FunctionComponent<
setShowBlueprintActionsMenu(!showBlueprintActionsMenu);
};
const importExportFlag = useFlagWithEphemDefault(
'image-builder.import.enabled'
'image-builder.import.enabled',
);
const [trigger] = useLazyExportBlueprintQuery();
@ -58,11 +58,10 @@ export const BlueprintActionsMenu: React.FunctionComponent<
ref={toggleRef}
isExpanded={showBlueprintActionsMenu}
onClick={() => setShowBlueprintActionsMenu(!showBlueprintActionsMenu)}
variant="plain"
aria-label="blueprint menu toggle"
data-testid="blueprint-action-menu-toggle"
variant='plain'
aria-label='blueprint menu toggle'
>
<EllipsisVIcon aria-hidden="true" />
<EllipsisVIcon aria-hidden='true' />
</MenuToggle>
)}
>
@ -82,7 +81,7 @@ export const BlueprintActionsMenu: React.FunctionComponent<
async function handleExportBlueprint(
blueprintName: string,
blueprint: BlueprintExportResponse
blueprint: BlueprintExportResponse,
) {
const jsonData = JSON.stringify(blueprint, null, 2);
const blob = new Blob([jsonData], { type: 'application/json' });

View file

@ -3,22 +3,20 @@ import React from 'react';
import {
Badge,
Card,
CardHeader,
CardTitle,
CardBody,
CardFooter,
CardHeader,
CardTitle,
Spinner,
} from '@patternfly/react-core';
import { useDeleteBPWithNotification as useDeleteBlueprintMutation } from '../../Hooks';
import {
selectSelectedBlueprintId,
setBlueprintId,
} from '../../store/BlueprintSlice';
import { useAppDispatch, useAppSelector } from '../../store/hooks';
import {
BlueprintItem,
useDeleteBlueprintMutation,
} from '../../store/imageBuilderApi';
import { BlueprintItem } from '../../store/imageBuilderApi';
type blueprintProps = {
blueprint: BlueprintItem;
@ -28,28 +26,45 @@ const BlueprintCard = ({ blueprint }: blueprintProps) => {
const selectedBlueprintId = useAppSelector(selectSelectedBlueprintId);
const dispatch = useAppDispatch();
const [, { isLoading }] = useDeleteBlueprintMutation({
const { isLoading } = useDeleteBlueprintMutation({
fixedCacheKey: 'delete-blueprint',
});
return (
<>
<Card
isSelected={blueprint.id === selectedBlueprintId}
isClicked={blueprint.id === selectedBlueprintId}
data-testid={`blueprint-card`}
isCompact
isClickable
onClick={() => dispatch(setBlueprintId(blueprint.id))}
isSelectableRaised
hasSelectableInput
selectableInputAriaLabel={`Select blueprint ${blueprint.name}`}
>
<CardHeader data-testid={blueprint.id}>
<CardTitle>
<CardHeader
data-testid={blueprint.id}
selectableActions={{
name: blueprint.name,
// use the name rather than the id. This helps us
// chose the correct item in the playwright tests
selectableActionId: blueprint.name,
selectableActionAriaLabel: blueprint.name,
onChange: () => dispatch(setBlueprintId(blueprint.id)),
}}
>
<CardTitle aria-label={blueprint.name}>
{isLoading && blueprint.id === selectedBlueprintId && (
<Spinner size="md" />
<Spinner size='md' />
)}
{blueprint.name}
{
// NOTE: This might be an issue with the pf6 truncate component.
// Since we're not really using the popover, we can just
// use vanilla js to truncate the string rather than use the
// Truncate component. We can match the behaviour of the component
// by also splitting on 24 characters.
// https://github.com/patternfly/patternfly-react/issues/11964
blueprint.name && blueprint.name.length > 24
? blueprint.name.slice(0, 24) + '...'
: blueprint.name
}
</CardTitle>
</CardHeader>
<CardBody>{blueprint.description}</CardBody>

View file

@ -1,7 +1,14 @@
import React from 'react';
import { DiffEditor } from '@monaco-editor/react';
import { Button, Modal, ModalVariant } from '@patternfly/react-core';
import {
Button,
Modal,
ModalBody,
ModalFooter,
ModalHeader,
ModalVariant,
} from '@patternfly/react-core';
import { BuildImagesButton } from './BuildImagesButton';
@ -27,11 +34,11 @@ const BlueprintDiffModal = ({
const { data: baseBlueprint } = useGetBlueprintQuery(
{ id: selectedBlueprintId as string, version: baseVersion || -1 },
{ skip: !selectedBlueprintId || !baseVersion }
{ skip: !selectedBlueprintId || !baseVersion },
);
const { data: blueprint } = useGetBlueprintQuery(
{ id: selectedBlueprintId as string },
{ skip: !selectedBlueprintId }
{ skip: !selectedBlueprintId },
);
if (!baseBlueprint || !blueprint) {
@ -39,32 +46,32 @@ const BlueprintDiffModal = ({
}
return (
<Modal
variant={ModalVariant.large}
titleIconVariant={'info'}
isOpen={isOpen}
onClose={onClose}
title={`Compare ${blueprintName || ''} versions`}
actions={[
<BuildImagesButton key="build-button">
<Modal variant={ModalVariant.large} isOpen={isOpen} onClose={onClose}>
<ModalHeader
title={`Compare ${blueprintName || ''} versions`}
titleIconVariant={'info'}
/>
<ModalBody>
<DiffEditor
height='90vh'
language='json'
original={JSON.stringify(baseBlueprint, undefined, 2)}
modified={JSON.stringify(blueprint, undefined, 2)}
/>
</ModalBody>
<ModalFooter>
<BuildImagesButton key='build-button'>
Synchronize images
</BuildImagesButton>,
</BuildImagesButton>
<Button
key="cancel-button"
variant="link"
type="button"
key='cancel-button'
variant='link'
type='button'
onClick={onClose}
>
Cancel
</Button>,
]}
>
<DiffEditor
height="90vh"
language="json"
original={JSON.stringify(baseBlueprint, undefined, 2)}
modified={JSON.stringify(blueprint, undefined, 2)}
/>
</Button>
</ModalFooter>
</Modal>
);
};

View file

@ -10,9 +10,9 @@ import { MenuToggleElement } from '@patternfly/react-core/dist/esm/components/Me
import { FilterIcon } from '@patternfly/react-icons';
import {
versionFilterType,
selectBlueprintVersionFilter,
setBlueprintVersionFilter,
versionFilterType,
} from '../../store/BlueprintSlice';
import { useAppDispatch, useAppSelector } from '../../store/hooks';
@ -33,7 +33,7 @@ const BlueprintVersionFilter: React.FC<blueprintVersionFilterProps> = ({
const onSelect = (
_event: React.MouseEvent<Element, MouseEvent> | undefined,
value: versionFilterType
value: versionFilterType,
) => {
dispatch(setBlueprintVersionFilter(value));
if (onFilterChange) onFilterChange();
@ -58,10 +58,10 @@ const BlueprintVersionFilter: React.FC<blueprintVersionFilterProps> = ({
shouldFocusToggleOnSelect
>
<DropdownList>
<DropdownItem value={'all'} key="all">
<DropdownItem value={'all'} key='all'>
All versions
</DropdownItem>
<DropdownItem value={'latest'} key="newest">
<DropdownItem value={'latest'} key='newest'>
Newest
</DropdownItem>
</DropdownList>

View file

@ -50,8 +50,8 @@ const BlueprintsPagination = () => {
page={currPage}
onSetPage={onSetPage}
onPerPageSelect={onPerPageSelect}
widgetId="blueprints-pagination-bottom"
data-testid="blueprints-pagination-bottom"
widgetId='blueprints-pagination-bottom'
data-testid='blueprints-pagination-bottom'
isCompact
/>
);

View file

@ -1,4 +1,4 @@
import React, { useCallback, useEffect, useState } from 'react';
import React, { useCallback } from 'react';
import {
Bullseye,
@ -7,8 +7,6 @@ import {
EmptyStateActions,
EmptyStateBody,
EmptyStateFooter,
EmptyStateHeader,
EmptyStateIcon,
Flex,
FlexItem,
SearchInput,
@ -19,7 +17,6 @@ import {
import { PlusCircleIcon, SearchIcon } from '@patternfly/react-icons';
import { SVGIconProps } from '@patternfly/react-icons/dist/esm/createIcon';
import useChrome from '@redhat-cloud-services/frontend-components/useChrome';
import { ChromeUser } from '@redhat-cloud-services/types';
import debounce from 'lodash/debounce';
import { Link } from 'react-router-dom';
@ -28,9 +25,10 @@ import BlueprintsPagination from './BlueprintsPagination';
import {
DEBOUNCED_SEARCH_WAIT_TIME,
PAGINATION_OFFSET,
PAGINATION_LIMIT,
PAGINATION_OFFSET,
} from '../../constants';
import { useGetUser } from '../../Hooks';
import { useGetBlueprintsQuery } from '../../store/backendApi';
import {
selectBlueprintSearchInput,
@ -48,7 +46,6 @@ import {
} from '../../store/imageBuilderApi';
import { imageBuilderApi } from '../../store/service/enhancedImageBuilderApi';
import { resolveRelPath } from '../../Utilities/path';
import { useGetEnvironment } from '../../Utilities/useGetEnvironment';
type blueprintSearchProps = {
blueprintsTotal: number;
@ -63,9 +60,8 @@ type emptyBlueprintStateProps = {
};
const BlueprintsSidebar = () => {
const [userData, setUserData] = useState<ChromeUser | void>(undefined);
const { isFedoraEnv } = useGetEnvironment();
const { analytics, auth } = useChrome();
const { userData } = useGetUser(auth);
const selectedBlueprintId = useAppSelector(selectSelectedBlueprintId);
const blueprintSearchInput = useAppSelector(selectBlueprintSearchInput);
@ -77,13 +73,6 @@ const BlueprintsSidebar = () => {
offset: blueprintsOffset,
};
useEffect(() => {
(async () => {
const data = await auth?.getUser();
setUserData(data);
})();
}, [auth]);
if (blueprintSearchInput) {
searchParams.search = blueprintSearchInput;
}
@ -101,7 +90,7 @@ const BlueprintsSidebar = () => {
if (isLoading) {
return (
<Bullseye>
<Spinner size="xl" />
<Spinner size='xl' />
</Bullseye>
);
}
@ -115,8 +104,8 @@ const BlueprintsSidebar = () => {
<EmptyBlueprintState
icon={PlusCircleIcon}
action={<Link to={resolveRelPath('imagewizard')}>Add blueprint</Link>}
titleText="No blueprints yet"
bodyText="Add a blueprint and optionally build related images."
titleText='No blueprints yet'
bodyText='Add a blueprint and optionally build related images.'
/>
);
}
@ -126,8 +115,8 @@ const BlueprintsSidebar = () => {
dispatch(setBlueprintId(undefined));
};
if (!process.env.IS_ON_PREMISE && !isFedoraEnv) {
const orgId = userData?.identity?.internal?.org_id;
if (!process.env.IS_ON_PREMISE) {
const orgId = userData?.identity.internal?.org_id;
analytics.group(orgId, {
imagebuilder_blueprint_count: blueprintsData?.meta.count,
@ -148,7 +137,7 @@ const BlueprintsSidebar = () => {
<Flex justifyContent={{ default: 'justifyContentCenter' }}>
<FlexItem>
<Button
variant="link"
variant='link'
isDisabled={!selectedBlueprintId}
onClick={handleClickViewAll}
>
@ -164,14 +153,14 @@ const BlueprintsSidebar = () => {
icon={SearchIcon}
action={
<Button
variant="link"
variant='link'
onClick={() => dispatch(setBlueprintSearchInput(undefined))}
>
Clear all filters
</Button>
}
titleText="No blueprints found"
bodyText="No blueprints match your search criteria. Try a different search."
titleText='No blueprints found'
bodyText='No blueprints match your search criteria. Try a different search.'
/>
)}
{blueprintsTotal > 0 &&
@ -195,7 +184,7 @@ const BlueprintSearch = ({ blueprintsTotal }: blueprintSearchProps) => {
dispatch(imageBuilderApi.util.invalidateTags([{ type: 'Blueprints' }]));
dispatch(setBlueprintSearchInput(filter.length > 0 ? filter : undefined));
}, DEBOUNCED_SEARCH_WAIT_TIME),
[]
[],
);
React.useEffect(() => {
return () => {
@ -213,7 +202,7 @@ const BlueprintSearch = ({ blueprintsTotal }: blueprintSearchProps) => {
return (
<SearchInput
value={blueprintSearchInput || ''}
placeholder="Search by name or description"
placeholder='Search by name or description'
onChange={(_event, value) => onChange(value)}
onClear={() => onChange('')}
resultsCount={`${blueprintsTotal} blueprints`}
@ -227,12 +216,7 @@ const EmptyBlueprintState = ({
icon,
action,
}: emptyBlueprintStateProps) => (
<EmptyState variant="sm">
<EmptyStateHeader
titleText={titleText}
headingLevel="h4"
icon={<EmptyStateIcon icon={icon} />}
/>
<EmptyState headingLevel='h4' icon={icon} titleText={titleText} variant='sm'>
<EmptyStateBody>{bodyText}</EmptyStateBody>
<EmptyStateFooter>
<EmptyStateActions>{action}</EmptyStateActions>

View file

@ -1,32 +1,31 @@
import React, { useEffect, useState } from 'react';
import React, { useState } from 'react';
import {
Button,
ButtonProps,
Dropdown,
MenuToggle,
Menu,
MenuContent,
MenuList,
MenuItem,
Flex,
FlexItem,
Spinner,
Menu,
MenuContent,
MenuItem,
MenuList,
MenuToggle,
MenuToggleAction,
ButtonProps,
Button,
Spinner,
} from '@patternfly/react-core';
import { MenuToggleElement } from '@patternfly/react-core/dist/esm/components/MenuToggle/MenuToggle';
import useChrome from '@redhat-cloud-services/frontend-components/useChrome';
import { addNotification } from '@redhat-cloud-services/frontend-components-notifications/redux';
import { ChromeUser } from '@redhat-cloud-services/types';
import { skipToken } from '@reduxjs/toolkit/query';
import { AMPLITUDE_MODULE_NAME, targetOptions } from '../../constants';
import {
useGetBlueprintQuery,
useComposeBlueprintMutation,
} from '../../store/backendApi';
useComposeBPWithNotification as useComposeBlueprintMutation,
useGetUser,
} from '../../Hooks';
import { useGetBlueprintQuery } from '../../store/backendApi';
import { selectSelectedBlueprintId } from '../../store/BlueprintSlice';
import { useAppDispatch, useAppSelector } from '../../store/hooks';
import { useAppSelector } from '../../store/hooks';
import { ImageTypes } from '../../store/imageBuilderApi';
type BuildImagesButtonPropTypes = {
@ -37,44 +36,27 @@ type BuildImagesButtonPropTypes = {
export const BuildImagesButton = ({ children }: BuildImagesButtonPropTypes) => {
const selectedBlueprintId = useAppSelector(selectSelectedBlueprintId);
const [deselectedTargets, setDeselectedTargets] = useState<ImageTypes[]>([]);
const [buildBlueprint, { isLoading: imageBuildLoading }] =
const { trigger: buildBlueprint, isLoading: imageBuildLoading } =
useComposeBlueprintMutation();
const dispatch = useAppDispatch();
const { analytics, auth } = useChrome();
const [userData, setUserData] = useState<ChromeUser | void>(undefined);
useEffect(() => {
(async () => {
const data = await auth?.getUser();
setUserData(data);
})();
}, [auth]);
const { userData } = useGetUser(auth);
const onBuildHandler = async () => {
if (selectedBlueprintId) {
try {
await buildBlueprint({
id: selectedBlueprintId,
body: {
image_types: blueprintImageType?.filter(
(target) => !deselectedTargets.includes(target)
),
},
});
await buildBlueprint({
id: selectedBlueprintId,
body: {
image_types: blueprintImageType?.filter(
(target) => !deselectedTargets.includes(target),
),
},
});
if (!process.env.IS_ON_PREMISE) {
analytics.track(`${AMPLITUDE_MODULE_NAME} - Image Requested`, {
module: AMPLITUDE_MODULE_NAME,
trigger: 'synchronize images',
account_id: userData?.identity.internal?.account_id || 'Not found',
});
} catch (imageBuildError) {
dispatch(
addNotification({
variant: 'warning',
title: 'No blueprint was build',
description: imageBuildError?.data?.error?.message,
})
);
}
}
};
@ -83,21 +65,21 @@ export const BuildImagesButton = ({ children }: BuildImagesButtonPropTypes) => {
setIsOpen(!isOpen);
};
const { data: blueprintDetails } = useGetBlueprintQuery(
selectedBlueprintId ? { id: selectedBlueprintId } : skipToken
selectedBlueprintId ? { id: selectedBlueprintId } : skipToken,
);
const blueprintImageType = blueprintDetails?.image_requests.map(
(image) => image.image_type
(image) => image.image_type,
);
const onSelect = (
_event: React.MouseEvent<Element, MouseEvent>,
itemId: number
itemId: number,
) => {
const imageType = blueprintImageType?.[itemId];
if (imageType && deselectedTargets.includes(imageType)) {
setDeselectedTargets(
deselectedTargets.filter((target) => target !== imageType)
deselectedTargets.filter((target) => target !== imageType),
);
} else if (imageType) {
setDeselectedTargets([...deselectedTargets, imageType]);
@ -110,43 +92,40 @@ export const BuildImagesButton = ({ children }: BuildImagesButtonPropTypes) => {
onOpenChange={(isOpen: boolean) => setIsOpen(isOpen)}
toggle={(toggleRef: React.Ref<MenuToggleElement>) => (
<MenuToggle
variant="primary"
data-testid="blueprint-build-image-menu"
variant='primary'
data-testid='blueprint-build-image-menu'
ref={toggleRef}
onClick={onToggleClick}
isExpanded={isOpen}
splitButtonOptions={{
variant: 'action',
items: [
<MenuToggleAction
data-testid="blueprint-build-image-menu-option"
key="split-action"
onClick={onBuildHandler}
id="wizard-build-image-btn"
isDisabled={
!selectedBlueprintId ||
deselectedTargets.length === blueprintImageType?.length
}
>
<Flex display={{ default: 'inlineFlex' }}>
{imageBuildLoading && (
<FlexItem>
<Spinner
style={
{
'--pf-v5-c-spinner--Color': '#fff',
} as React.CSSProperties
}
isInline
size="md"
/>
</FlexItem>
)}
<FlexItem>{children ? children : 'Build images'}</FlexItem>
</Flex>
</MenuToggleAction>,
],
}}
splitButtonItems={[
<MenuToggleAction
data-testid='blueprint-build-image-menu-option'
key='split-action'
onClick={onBuildHandler}
id='wizard-build-image-btn'
isDisabled={
!selectedBlueprintId ||
deselectedTargets.length === blueprintImageType?.length
}
>
<Flex display={{ default: 'inlineFlex' }}>
{imageBuildLoading && (
<FlexItem>
<Spinner
style={
{
'--pf-v6-c-spinner--Color': '#fff',
} as React.CSSProperties
}
isInline
size='md'
/>
</FlexItem>
)}
<FlexItem>{children ? children : 'Build images'}</FlexItem>
</Flex>
</MenuToggleAction>,
]}
></MenuToggle>
)}
>
@ -183,7 +162,7 @@ export const BuildImagesButtonEmptyState = ({
children,
}: BuildImagesButtonEmptyStatePropTypes) => {
const selectedBlueprintId = useAppSelector(selectSelectedBlueprintId);
const [buildBlueprint, { isLoading: imageBuildLoading }] =
const { trigger: buildBlueprint, isLoading: imageBuildLoading } =
useComposeBlueprintMutation();
const onBuildHandler = async () => {
if (selectedBlueprintId) {

View file

@ -1,13 +1,14 @@
import React, { useEffect, useState } from 'react';
import React from 'react';
import {
ActionGroup,
Button,
Modal,
ModalBody,
ModalFooter,
ModalHeader,
ModalVariant,
} from '@patternfly/react-core';
import useChrome from '@redhat-cloud-services/frontend-components/useChrome';
import { ChromeUser } from '@redhat-cloud-services/types';
import {
AMPLITUDE_MODULE_NAME,
@ -15,10 +16,10 @@ import {
PAGINATION_OFFSET,
} from '../../constants';
import {
backendApi,
useDeleteBlueprintMutation,
useGetBlueprintsQuery,
} from '../../store/backendApi';
useDeleteBPWithNotification as useDeleteBlueprintMutation,
useGetUser,
} from '../../Hooks';
import { backendApi, useGetBlueprintsQuery } from '../../store/backendApi';
import {
selectBlueprintSearchInput,
selectLimit,
@ -43,14 +44,7 @@ export const DeleteBlueprintModal: React.FunctionComponent<
const blueprintsLimit = useAppSelector(selectLimit) || PAGINATION_LIMIT;
const dispatch = useAppDispatch();
const { analytics, auth } = useChrome();
const [userData, setUserData] = useState<ChromeUser | void>(undefined);
useEffect(() => {
(async () => {
const data = await auth?.getUser();
setUserData(data);
})();
}, [auth]);
const { userData } = useGetUser(auth);
const searchParams: GetBlueprintsApiArg = {
limit: blueprintsLimit,
@ -65,19 +59,21 @@ export const DeleteBlueprintModal: React.FunctionComponent<
selectFromResult: ({ data }) => ({
blueprintName: data?.data.find(
(blueprint: { id: string | undefined }) =>
blueprint.id === selectedBlueprintId
blueprint.id === selectedBlueprintId,
)?.name,
}),
});
const [deleteBlueprint] = useDeleteBlueprintMutation({
const { trigger: deleteBlueprint } = useDeleteBlueprintMutation({
fixedCacheKey: 'delete-blueprint',
});
const handleDelete = async () => {
if (selectedBlueprintId) {
analytics.track(`${AMPLITUDE_MODULE_NAME} - Blueprint Deleted`, {
module: AMPLITUDE_MODULE_NAME,
account_id: userData?.identity.internal?.account_id || 'Not found',
});
if (!process.env.IS_ON_PREMISE) {
analytics.track(`${AMPLITUDE_MODULE_NAME} - Blueprint Deleted`, {
module: AMPLITUDE_MODULE_NAME,
account_id: userData?.identity.internal?.account_id || 'Not found',
});
}
setShowDeleteModal(false);
await deleteBlueprint({ id: selectedBlueprintId });
dispatch(setBlueprintId(undefined));
@ -88,22 +84,20 @@ export const DeleteBlueprintModal: React.FunctionComponent<
setShowDeleteModal(false);
};
return (
<Modal
variant={ModalVariant.small}
titleIconVariant="warning"
isOpen={isOpen}
onClose={onDeleteClose}
title={'Delete blueprint?'}
description={`All versions of ${blueprintName} and its associated images will be deleted.`}
>
<ActionGroup>
<Button variant="danger" type="button" onClick={handleDelete}>
<Modal variant={ModalVariant.small} isOpen={isOpen} onClose={onDeleteClose}>
<ModalHeader title={'Delete blueprint?'} titleIconVariant='warning' />
<ModalBody>
All versions of {blueprintName} and its associated images will be
deleted.
</ModalBody>
<ModalFooter>
<Button variant='danger' type='button' onClick={handleDelete}>
Delete
</Button>
<Button variant="link" type="button" onClick={onDeleteClose}>
<Button variant='link' type='button' onClick={onDeleteClose}>
Cancel
</Button>
</ActionGroup>
</ModalFooter>
</Modal>
);
};

View file

@ -16,7 +16,7 @@ export const EditBlueprintButton = () => {
onClick={() =>
navigate(resolveRelPath(`imagewizard/${selectedBlueprintId}`))
}
variant="secondary"
variant='secondary'
>
Edit blueprint
</Button>

View file

@ -2,7 +2,6 @@ import React from 'react';
import { parse } from '@ltd/j-toml';
import {
ActionGroup,
Button,
Checkbox,
FileUpload,
@ -12,12 +11,15 @@ import {
HelperText,
HelperTextItem,
Modal,
ModalBody,
ModalFooter,
ModalHeader,
ModalVariant,
Popover,
} from '@patternfly/react-core';
import { DropEvent } from '@patternfly/react-core/dist/esm/helpers';
import { HelpIcon } from '@patternfly/react-icons';
import { addNotification } from '@redhat-cloud-services/frontend-components-notifications/redux';
import { useAddNotification } from '@redhat-cloud-services/frontend-components-notifications/hooks';
import { useNavigate } from 'react-router-dom';
import { mapOnPremToHosted } from './helpers/onPremToHostedBlueprintMapper';
@ -27,7 +29,6 @@ import {
ApiRepositoryRequest,
useBulkImportRepositoriesMutation,
} from '../../store/contentSourcesApi';
import { useAppDispatch } from '../../store/hooks';
import {
BlueprintExportResponse,
BlueprintItem,
@ -65,24 +66,24 @@ export const ImportBlueprintModal: React.FunctionComponent<
const [isRejected, setIsRejected] = React.useState(false);
const [isOnPrem, setIsOnPrem] = React.useState(false);
const [isCheckedImportRepos, setIsCheckedImportRepos] = React.useState(true);
const dispatch = useAppDispatch();
const addNotification = useAddNotification();
const [importRepositories] = useBulkImportRepositoriesMutation();
const handleFileInputChange = (
_event: React.ChangeEvent<HTMLInputElement> | React.DragEvent<HTMLElement>,
file: File
file: File,
) => {
setFileContent('');
setFilename(file.name);
};
async function handleRepositoryImport(
blueprintExportedResponse: BlueprintExportResponse
blueprintExportedResponse: BlueprintExportResponse,
): Promise<CustomRepository[] | undefined> {
if (isCheckedImportRepos && blueprintExportedResponse.content_sources) {
const customRepositories: ApiRepositoryRequest[] =
blueprintExportedResponse.content_sources.map(
(item) => item as ApiRepositoryRequest
(item) => item as ApiRepositoryRequest,
);
try {
@ -97,40 +98,34 @@ export const ImportBlueprintModal: React.FunctionComponent<
repository as ApiRepositoryImportResponseRead;
if (contentSourcesRepo.uuid) {
newCustomRepos.push(
...mapToCustomRepositories(contentSourcesRepo)
...mapToCustomRepositories(contentSourcesRepo),
);
}
if (repository.warnings?.length === 0 && repository.url) {
importedRepositoryNames.push(repository.url);
return;
}
dispatch(
addNotification({
variant: 'warning',
title: 'Failed to import custom repositories',
description: JSON.stringify(repository.warnings),
})
);
addNotification({
variant: 'warning',
title: 'Failed to import custom repositories',
description: JSON.stringify(repository.warnings),
});
});
if (importedRepositoryNames.length !== 0) {
dispatch(
addNotification({
variant: 'info',
title: 'Successfully imported custom repositories',
description: importedRepositoryNames.join(', '),
})
);
addNotification({
variant: 'info',
title: 'Successfully imported custom repositories',
description: importedRepositoryNames.join(', '),
});
}
return newCustomRepos;
}
} catch {
dispatch(
addNotification({
variant: 'danger',
title: 'Custom repositories import failed',
})
);
addNotification({
variant: 'danger',
title: 'Custom repositories import failed',
});
}
}
}
@ -144,11 +139,11 @@ export const ImportBlueprintModal: React.FunctionComponent<
if (isToml) {
const tomlBlueprint = parse(fileContent);
const blueprintFromFile = mapOnPremToHosted(
tomlBlueprint as BlueprintItem
tomlBlueprint as BlueprintItem,
);
const importBlueprintState = mapExportRequestToState(
blueprintFromFile,
[]
[],
);
setIsOnPrem(true);
setImportedBlueprint(importBlueprintState);
@ -160,9 +155,8 @@ export const ImportBlueprintModal: React.FunctionComponent<
blueprintFromFile.content_sources &&
blueprintFromFile.content_sources.length > 0
) {
const imported = await handleRepositoryImport(
blueprintFromFile
);
const imported =
await handleRepositoryImport(blueprintFromFile);
customRepos = imported ?? [];
}
@ -180,7 +174,7 @@ export const ImportBlueprintModal: React.FunctionComponent<
undefined;
const importBlueprintState = mapExportRequestToState(
blueprintExportedResponse,
blueprintFromFile.image_requests || []
blueprintFromFile.image_requests || [],
);
setIsOnPrem(false);
@ -190,7 +184,7 @@ export const ImportBlueprintModal: React.FunctionComponent<
mapOnPremToHosted(blueprintFromFile);
const importBlueprintState = mapExportRequestToState(
blueprintFromFileMapped,
[]
[],
);
setIsOnPrem(true);
setImportedBlueprint(importBlueprintState);
@ -198,13 +192,11 @@ export const ImportBlueprintModal: React.FunctionComponent<
}
} catch (error) {
setIsInvalidFormat(true);
dispatch(
addNotification({
variant: 'warning',
title: 'File is not a valid blueprint',
description: error?.data?.error?.message,
})
);
addNotification({
variant: 'warning',
title: 'File is not a valid blueprint',
description: error?.data?.error?.message,
});
}
};
parseAndImport();
@ -249,95 +241,98 @@ export const ImportBlueprintModal: React.FunctionComponent<
<Modal
variant={ModalVariant.medium}
isOpen={isOpen}
title={
<>
Import pipeline
<Popover
bodyContent={
<div>
You can import the blueprints you created by using the Red Hat
image builder into Insights images to create customized images.
</div>
}
>
<Button
variant="plain"
aria-label="About import"
className="pf-v5-u-pl-sm"
isInline
>
<HelpIcon />
</Button>
</Popover>
</>
}
onClose={onImportClose}
>
<Form>
<FormGroup fieldId="checkbox-import-custom-repositories">
<Checkbox
label="Import missing custom repositories after file upload."
isChecked={isCheckedImportRepos}
onChange={() => setIsCheckedImportRepos((prev) => !prev)}
aria-label="Import Custom Repositories checkbox"
id="checkbox-import-custom-repositories"
name="Import Repositories"
/>
</FormGroup>
<FormGroup fieldId="import-blueprint-file-upload">
<FileUpload
id="import-blueprint-file-upload"
type="text"
value={fileContent}
filename={filename}
filenamePlaceholder="Drag and drop a file or upload one"
onFileInputChange={handleFileInputChange}
onDataChange={handleDataChange}
onReadStarted={handleFileReadStarted}
onReadFinished={handleFileReadFinished}
onClearClick={handleClear}
isLoading={isLoading}
isReadOnly={true}
browseButtonText="Upload"
dropzoneProps={{
accept: { 'text/json': ['.json'], 'text/plain': ['.toml'] },
maxSize: 512000,
onDropRejected: handleFileRejected,
}}
validated={isRejected || isInvalidFormat ? 'error' : 'default'}
/>
<FormHelperText>
<HelperText>
<HelperTextItem variant={variantSwitch()}>
{isRejected
? 'Must be a valid Blueprint JSON/TOML file no larger than 512 KB'
: isInvalidFormat
? 'Not compatible with the blueprints format.'
: isOnPrem
? 'Importing on-premises blueprints is currently in beta. Results may vary.'
: 'Upload your blueprint file. Supported formats: JSON, TOML.'}
</HelperTextItem>
</HelperText>
</FormHelperText>
</FormGroup>
<ActionGroup>
<Button
type="button"
isDisabled={isRejected || isInvalidFormat || !fileContent}
onClick={() =>
navigate(resolveRelPath(`imagewizard/import`), {
state: { blueprint: importedBlueprint },
})
}
data-testid="import-blueprint-finish"
>
Review and finish
</Button>
<Button variant="link" type="button" onClick={onImportClose}>
Cancel
</Button>
</ActionGroup>
</Form>
<ModalHeader
title={
<>
Import pipeline
<Popover
bodyContent={
<div>
You can import the blueprints you created by using the Red Hat
image builder into Insights images to create customized
images.
</div>
}
>
<Button
icon={<HelpIcon />}
variant='plain'
aria-label='About import'
className='pf-v6-u-pl-sm'
isInline
/>
</Popover>
</>
}
/>
<ModalBody>
<Form>
<FormGroup fieldId='checkbox-import-custom-repositories'>
<Checkbox
label='Import missing custom repositories after file upload.'
isChecked={isCheckedImportRepos}
onChange={() => setIsCheckedImportRepos((prev) => !prev)}
aria-label='Import Custom Repositories checkbox'
id='checkbox-import-custom-repositories'
name='Import Repositories'
/>
</FormGroup>
<FormGroup fieldId='import-blueprint-file-upload'>
<FileUpload
id='import-blueprint-file-upload'
type='text'
value={fileContent}
filename={filename}
filenamePlaceholder='Drag and drop a file or upload one'
onFileInputChange={handleFileInputChange}
onDataChange={handleDataChange}
onReadStarted={handleFileReadStarted}
onReadFinished={handleFileReadFinished}
onClearClick={handleClear}
isLoading={isLoading}
isReadOnly={true}
browseButtonText='Upload'
dropzoneProps={{
accept: { 'text/json': ['.json'], 'text/plain': ['.toml'] },
maxSize: 512000,
onDropRejected: handleFileRejected,
}}
validated={isRejected || isInvalidFormat ? 'error' : 'default'}
/>
<FormHelperText>
<HelperText>
<HelperTextItem variant={variantSwitch()}>
{isRejected
? 'Must be a valid Blueprint JSON/TOML file no larger than 512 KB'
: isInvalidFormat
? 'Not compatible with the blueprints format.'
: isOnPrem
? 'Importing on-premises blueprints is currently in beta. Results may vary.'
: 'Upload your blueprint file. Supported formats: JSON, TOML.'}
</HelperTextItem>
</HelperText>
</FormHelperText>
</FormGroup>
</Form>
</ModalBody>
<ModalFooter>
<Button
type='button'
isDisabled={isRejected || isInvalidFormat || !fileContent}
onClick={() =>
navigate(resolveRelPath(`imagewizard/import`), {
state: { blueprint: importedBlueprint },
})
}
>
Review and finish
</Button>
<Button variant='link' type='button' onClick={onImportClose}>
Cancel
</Button>
</ModalFooter>
</Modal>
);
};

View file

@ -101,13 +101,13 @@ export type SshKeyOnPrem = {
};
export const mapOnPremToHosted = (
blueprint: BlueprintOnPrem
blueprint: BlueprintOnPrem,
): BlueprintExportResponse => {
const users = blueprint.customizations?.user?.map((u) => ({
name: u.name,
ssh_key: u.key,
groups: u.groups,
isAdministrator: u.groups?.includes('wheel') || false,
isAdministrator: u.groups.includes('wheel') || false,
}));
const user_keys = blueprint.customizations?.sshkey?.map((k) => ({
name: k.user,
@ -132,7 +132,7 @@ export const mapOnPremToHosted = (
({ baseurls, ...fs }) => ({
baseurl: baseurls,
...fs,
})
}),
),
packages:
packages !== undefined || groups !== undefined
@ -147,7 +147,7 @@ export const mapOnPremToHosted = (
({ minsize, ...fs }) => ({
min_size: minsize,
...fs,
})
}),
),
fips:
blueprint.customizations?.fips !== undefined
@ -189,14 +189,14 @@ export const mapOnPremToHosted = (
};
export const mapHostedToOnPrem = (
blueprint: CreateBlueprintRequest
blueprint: CreateBlueprintRequest,
): CloudApiBlueprint => {
const result: CloudApiBlueprint = {
name: blueprint.name,
customizations: {},
};
if (blueprint.customizations?.packages) {
if (blueprint.customizations.packages) {
result.packages = blueprint.customizations.packages.map((pkg) => {
return {
name: pkg,
@ -205,30 +205,30 @@ export const mapHostedToOnPrem = (
});
}
if (blueprint.customizations?.containers) {
if (blueprint.customizations.containers) {
result.containers = blueprint.customizations.containers;
}
if (blueprint.customizations?.directories) {
if (blueprint.customizations.directories) {
result.customizations!.directories = blueprint.customizations.directories;
}
if (blueprint.customizations?.files) {
if (blueprint.customizations.files) {
result.customizations!.files = blueprint.customizations.files;
}
if (blueprint.customizations?.filesystem) {
if (blueprint.customizations.filesystem) {
result.customizations!.filesystem = blueprint.customizations.filesystem.map(
(fs) => {
return {
mountpoint: fs.mountpoint,
minsize: fs.min_size,
};
}
},
);
}
if (blueprint.customizations?.users) {
if (blueprint.customizations.users) {
result.customizations!.user = blueprint.customizations.users.map((u) => {
return {
name: u.name,
@ -239,54 +239,54 @@ export const mapHostedToOnPrem = (
});
}
if (blueprint.customizations?.services) {
if (blueprint.customizations.services) {
result.customizations!.services = blueprint.customizations.services;
}
if (blueprint.customizations?.hostname) {
if (blueprint.customizations.hostname) {
result.customizations!.hostname = blueprint.customizations.hostname;
}
if (blueprint.customizations?.kernel) {
if (blueprint.customizations.kernel) {
result.customizations!.kernel = blueprint.customizations.kernel;
}
if (blueprint.customizations?.timezone) {
if (blueprint.customizations.timezone) {
result.customizations!.timezone = blueprint.customizations.timezone;
}
if (blueprint.customizations?.locale) {
if (blueprint.customizations.locale) {
result.customizations!.locale = blueprint.customizations.locale;
}
if (blueprint.customizations?.firewall) {
if (blueprint.customizations.firewall) {
result.customizations!.firewall = blueprint.customizations.firewall;
}
if (blueprint.customizations?.installation_device) {
if (blueprint.customizations.installation_device) {
result.customizations!.installation_device =
blueprint.customizations.installation_device;
}
if (blueprint.customizations?.fdo) {
if (blueprint.customizations.fdo) {
result.customizations!.fdo = blueprint.customizations.fdo;
}
if (blueprint.customizations?.ignition) {
if (blueprint.customizations.ignition) {
result.customizations!.ignition = blueprint.customizations.ignition;
}
if (blueprint.customizations?.partitioning_mode) {
if (blueprint.customizations.partitioning_mode) {
result.customizations!.partitioning_mode =
blueprint.customizations.partitioning_mode;
}
if (blueprint.customizations?.fips) {
if (blueprint.customizations.fips) {
result.customizations!.fips =
blueprint.customizations.fips?.enabled || false;
blueprint.customizations.fips.enabled || false;
}
if (blueprint.customizations?.installer) {
if (blueprint.customizations.installer) {
result.customizations!.installer = blueprint.customizations.installer;
}

View file

@ -0,0 +1,210 @@
import React from 'react';
import {
Button,
Content,
Form,
FormGroup,
Popover,
Switch,
TextInput,
} from '@patternfly/react-core';
import { HelpIcon } from '@patternfly/react-icons';
import { isAwsBucketValid, isAwsCredsPathValid } from './validators';
import {
changeAWSBucketName,
changeAWSCredsPath,
reinitializeAWSConfig,
selectAWSBucketName,
selectAWSCredsPath,
} from '../../store/cloudProviderConfigSlice';
import {
AWSWorkerConfig,
WorkerConfigResponse,
} from '../../store/cockpit/types';
import { useAppDispatch, useAppSelector } from '../../store/hooks';
import { ValidatedInput } from '../CreateImageWizard/ValidatedInput';
type FormGroupProps<T> = {
value: T | undefined;
onChange: (value: T) => void;
isDisabled?: boolean;
};
type ToggleGroupProps = Omit<FormGroupProps<boolean>, 'isDisabled'>;
const AWSConfigToggle = ({ value, onChange }: ToggleGroupProps) => {
const handleChange = (
_event: React.FormEvent<HTMLInputElement>,
checked: boolean,
) => {
onChange(checked);
};
return (
<FormGroup label='Configure AWS Uploads'>
<Switch
id='aws-config-switch'
ouiaId='aws-config-switch'
aria-label='aws-config-switch'
// empty label so there is no icon
label=''
isChecked={value}
onChange={handleChange}
/>
</FormGroup>
);
};
const DisabledInputGroup = ({
value,
label,
ariaLabel,
}: {
value: string | undefined;
label: React.ReactNode;
ariaLabel: string;
}) => {
return (
<FormGroup label={label}>
<TextInput aria-label={ariaLabel} value={value || ''} isDisabled />
</FormGroup>
);
};
const AWSBucket = ({ value, onChange, isDisabled }: FormGroupProps<string>) => {
const label = 'AWS Bucket';
if (isDisabled) {
return (
<DisabledInputGroup label={label} value={value} ariaLabel='aws-bucket' />
);
}
return (
<FormGroup label={label}>
<ValidatedInput
placeholder='AWS bucket'
ariaLabel='aws-bucket'
value={value || ''}
validator={isAwsBucketValid}
onChange={(_event, value) => onChange(value)}
helperText='Invalid AWS bucket name'
/>
</FormGroup>
);
};
const CredsPathPopover = () => {
return (
<Popover
minWidth='35rem'
headerContent={'What is the AWS Credentials Path?'}
bodyContent={
<Content>
<Content>
This is the path to your AWS credentials file which contains your
aws access key id and secret access key. This path to the file is
normally in the home directory in the credentials file in the .aws
directory, <br /> i.e. /home/USERNAME/.aws/credentials
</Content>
</Content>
}
>
<Button
icon={<HelpIcon />}
variant='plain'
aria-label='Credentials Path Info'
className='pf-v6-u-pl-sm header-button'
/>
</Popover>
);
};
const AWSCredsPath = ({
value,
onChange,
isDisabled,
}: FormGroupProps<string>) => {
const label = (
<>
AWS Credentials Filepath <CredsPathPopover />
</>
);
if (isDisabled) {
return (
<DisabledInputGroup
value={value}
label={label}
ariaLabel='aws-creds-path'
/>
);
}
return (
<FormGroup label={label}>
<ValidatedInput
placeholder='Path to AWS credentials'
ariaLabel='aws-creds-path'
value={value || ''}
validator={isAwsCredsPathValid}
onChange={(_event, value) => onChange(value)}
helperText='Invalid filepath for AWS credentials'
/>
</FormGroup>
);
};
type AWSConfigProps = {
enabled: boolean;
setEnabled: (enabled: boolean) => void;
reinit: (config: AWSWorkerConfig | undefined) => void;
refetch: () => Promise<{
data?: WorkerConfigResponse | undefined;
}>;
};
export const AWSConfig = ({
enabled,
setEnabled,
refetch,
reinit,
}: AWSConfigProps) => {
const dispatch = useAppDispatch();
const bucket = useAppSelector(selectAWSBucketName);
const credentials = useAppSelector(selectAWSCredsPath);
const onToggle = async (v: boolean) => {
if (v) {
try {
const { data } = await refetch();
reinit(data?.aws);
setEnabled(v);
return;
} catch {
return;
}
}
dispatch(reinitializeAWSConfig());
setEnabled(v);
};
return (
<Form>
<AWSConfigToggle value={enabled} onChange={onToggle} />
<AWSBucket
value={bucket}
onChange={(v) => dispatch(changeAWSBucketName(v))}
isDisabled={!enabled}
/>
<AWSCredsPath
value={credentials}
onChange={(v) => dispatch(changeAWSCredsPath(v))}
isDisabled={!enabled}
/>
</Form>
);
};

View file

@ -0,0 +1,146 @@
import React, {
MouseEventHandler,
useCallback,
useEffect,
useState,
} from 'react';
import {
Button,
EmptyState,
EmptyStateActions,
EmptyStateBody,
EmptyStateFooter,
EmptyStateVariant,
PageSection,
Skeleton,
Title,
Wizard,
WizardStep,
} from '@patternfly/react-core';
import { ExclamationIcon } from '@patternfly/react-icons';
import { useNavigate } from 'react-router-dom';
import { AWSConfig } from './AWSConfig';
import { isAwsStepValid } from './validators';
import {
changeAWSBucketName,
changeAWSCredsPath,
reinitializeAWSConfig,
selectAWSConfig,
} from '../../store/cloudProviderConfigSlice';
import {
useGetWorkerConfigQuery,
useUpdateWorkerConfigMutation,
} from '../../store/cockpit/cockpitApi';
import { AWSWorkerConfig } from '../../store/cockpit/types';
import { useAppDispatch, useAppSelector } from '../../store/hooks';
import { resolveRelPath } from '../../Utilities/path';
import { ImageBuilderHeader } from '../sharedComponents/ImageBuilderHeader';
const ConfigError = ({
onClose,
}: {
onClose: MouseEventHandler<HTMLButtonElement>;
}) => {
return (
<EmptyState
variant={EmptyStateVariant.xl}
icon={ExclamationIcon}
color='#C9190B'
>
<Title headingLevel='h4' size='lg'>
Error
</Title>
<EmptyStateBody>
There was an error reading the `/etc/osbuild-worker/osbuild-worker.toml`
config file
</EmptyStateBody>
<EmptyStateFooter>
<EmptyStateActions>
<Button variant='primary' onClick={onClose}>
Go back
</Button>
</EmptyStateActions>
</EmptyStateFooter>
</EmptyState>
);
};
export const CloudProviderConfig = () => {
const navigate = useNavigate();
const dispatch = useAppDispatch();
const config = useAppSelector(selectAWSConfig);
const handleClose = () => navigate(resolveRelPath(''));
const [enabled, setEnabled] = useState<boolean>(false);
const [updateConfig] = useUpdateWorkerConfigMutation();
const { data, error, refetch, isLoading } = useGetWorkerConfigQuery({});
const initAWSConfig = useCallback(
(config: AWSWorkerConfig | undefined) => {
if (!config) {
dispatch(reinitializeAWSConfig());
setEnabled(false);
return;
}
setEnabled(true);
const { bucket, credentials } = config;
if (bucket && bucket !== '') {
dispatch(changeAWSBucketName(bucket));
}
if (credentials && credentials !== '') {
dispatch(changeAWSCredsPath(credentials));
}
},
[dispatch, setEnabled],
);
useEffect(() => {
initAWSConfig(data?.aws);
}, [data, initAWSConfig]);
if (isLoading) {
return <Skeleton />;
}
if (error) {
return <ConfigError onClose={handleClose} />;
}
return (
<>
<ImageBuilderHeader inWizard={true} />
<PageSection>
<Wizard onClose={handleClose}>
<WizardStep
name='AWS Config'
id='aws-config'
footer={{
nextButtonText: 'Submit',
isNextDisabled: !isAwsStepValid(config),
isBackDisabled: true,
onNext: () => {
updateConfig({
updateWorkerConfigRequest: { aws: config },
});
navigate(resolveRelPath(''));
},
}}
>
<AWSConfig
refetch={refetch}
reinit={initAWSConfig}
enabled={enabled}
setEnabled={setEnabled}
/>
</WizardStep>
</Wizard>
</PageSection>
</>
);
};

View file

@ -0,0 +1,37 @@
import path from 'path';
import { AWSWorkerConfig } from '../../../store/cockpit/types';
export const isAwsBucketValid = (bucket?: string): boolean => {
if (!bucket || bucket === '') {
return false;
}
const regex = /^[a-z0-9](?:[a-z0-9]|[-.](?=[a-z0-9])){1,61}[a-z0-9]$/;
return regex.test(bucket);
};
export const isAwsCredsPathValid = (credsPath?: string): boolean => {
if (!credsPath || credsPath === '') {
return false;
}
const validPathPattern = /^(\/[^/\0]*)+\/?$/;
return path.isAbsolute(credsPath) && validPathPattern.test(credsPath);
};
export const isAwsStepValid = (
config: AWSWorkerConfig | undefined,
): boolean => {
if (!config) {
return true;
}
if (!config.bucket && !config.credentials) {
return false;
}
return (
isAwsBucketValid(config.bucket) && isAwsCredsPathValid(config.credentials)
);
};

View file

@ -5,8 +5,6 @@ import {
EmptyState,
EmptyStateActions,
EmptyStateFooter,
EmptyStateHeader,
EmptyStateIcon,
EmptyStateVariant,
} from '@patternfly/react-core';
import { CubesIcon } from '@patternfly/react-icons';
@ -14,16 +12,16 @@ import cockpit from 'cockpit';
export const NotReady = ({ enabled }: { enabled: boolean }) => {
return (
<EmptyState variant={EmptyStateVariant.xl}>
<EmptyStateHeader
titleText={`OSBuild Composer is not ${enabled ? 'started' : 'enabled'}`}
headingLevel="h4"
icon={<EmptyStateIcon icon={CubesIcon} />}
/>
<EmptyState
headingLevel='h4'
icon={CubesIcon}
titleText={`OSBuild Composer is not ${enabled ? 'started' : 'enabled'}`}
variant={EmptyStateVariant.xl}
>
<EmptyStateFooter>
<EmptyStateActions>
<Button
variant="primary"
variant='primary'
onClick={(event) => {
event.preventDefault();
cockpit
@ -32,7 +30,7 @@ export const NotReady = ({ enabled }: { enabled: boolean }) => {
{
superuser: 'require',
err: 'message',
}
},
)
.then(() => window.location.reload());
}}
@ -42,12 +40,12 @@ export const NotReady = ({ enabled }: { enabled: boolean }) => {
</EmptyStateActions>
<EmptyStateActions>
<Button
variant="link"
variant='link'
onClick={(event) => {
event.preventDefault();
cockpit.jump(
'/system/services#/osbuild-composer.socket',
cockpit.transport.host
cockpit.transport.host,
);
}}
>

View file

@ -3,20 +3,18 @@ import React from 'react';
import {
EmptyState,
EmptyStateBody,
EmptyStateHeader,
EmptyStateIcon,
EmptyStateVariant,
} from '@patternfly/react-core';
import { LockIcon } from '@patternfly/react-icons';
export const RequireAdmin = () => {
return (
<EmptyState variant={EmptyStateVariant.xl}>
<EmptyStateHeader
titleText="Access is limited."
headingLevel="h4"
icon={<EmptyStateIcon icon={LockIcon} color="#f4c145" />}
/>
<EmptyState
headingLevel='h4'
icon={LockIcon}
titleText='Access is limited.'
variant={EmptyStateVariant.xl}
>
<EmptyStateBody>
Administrative access is required to run the Image Builder frontend.
Click on the icon in the toolbar to grant administrative access.

View file

@ -1,4 +1,4 @@
.pf-v5-c-wizard__nav-list {
.pf-v6-c-wizard__nav-list {
padding-right: 0px;
}
@ -10,45 +10,24 @@
}
.pf-c-form {
--pf-c-form--GridGap: var(--pf-v5-global--spacer--md);
--pf-c-form--GridGap: var(--pf6-global--spacer--md);
}
.pf-c-form__group-label {
--pf-c-form__group-label--PaddingBottom: var(--pf-v5-global--spacer--xs);
}
.tiles {
display: flex;
}
.tile {
flex: 1 0 0px;
max-width: 250px;
}
.pf-c-tile:focus {
--pf-c-tile__title--Color: var(--pf-c-tile__title--Color);
--pf-c-tile__icon--Color: var(---pf-v5-global--Color--100);
--pf-c-tile--before--BorderWidth: var(--pf-v5-global--BorderWidth--sm);
--pf-c-tile--before--BorderColor: var(--pf-v5-global--BorderColor--100);
}
.pf-c-tile.pf-m-selected:focus {
--pf-c-tile__title--Color: var(--pf-c-tile--focus__title--Color);
--pf-c-tile__icon--Color: var(--pf-c-tile--focus__icon--Color);
--pf-c-form__group-label--PaddingBottom: var(--pf-v6-global--spacer--xs);
}
.provider-icon {
width: 1em;
height: 1em;
width: 3.5em;
height: 3.5em;
}
.pf-v5-u-min-width {
--pf-v5-u-min-width--MinWidth: 18ch;
.pf-v6-u-min-width {
--pf-v6-u-min-width--MinWidth: 18ch;
}
.pf-v5-u-max-width {
--pf-v5-u-max-width--MaxWidth: 26rem;
.pf-v6-u-max-width {
--pf-v6-u-max-width--MaxWidth: 26rem;
}
ul.pf-m-plain {
@ -62,15 +41,20 @@ ul.pf-m-plain {
}
.panel-border {
--pf-v5-c-panel--before--BorderColor: #BEE1F4;
--pf-v6-c-panel--before--BorderColor: #BEE1F4;
}
// Targets the alert within the Reviewsteps > content dropdown
// Removes excess top margin padding
div.pf-v5-c-alert.pf-m-inline.pf-m-plain.pf-m-warning {
div.pf-v6-c-alert.pf-m-inline.pf-m-plain.pf-m-warning {
margin-top: 18px;
h4 {
margin-block-start: 0;
}
}
// Ensures the wizard takes up the entire height of the page in Firefox as well
.pf-v6-c-wizard {
flex: 1;
}

View file

@ -2,21 +2,23 @@ import React, { useEffect, useState } from 'react';
import {
Button,
Flex,
PageSection,
PageSectionTypes,
useWizardContext,
Wizard,
WizardFooterWrapper,
WizardNavItem,
WizardStep,
useWizardContext,
PageSection,
PageSectionTypes,
} from '@patternfly/react-core';
import { WizardStepType } from '@patternfly/react-core/dist/esm/components/Wizard';
import useChrome from '@redhat-cloud-services/frontend-components/useChrome';
import { useNavigate, useSearchParams } from 'react-router-dom';
import AAPStep from './steps/AAP';
import DetailsStep from './steps/Details';
import FileSystemStep from './steps/FileSystem';
import { FileSystemContext } from './steps/FileSystem/FileSystemTable';
import { FileSystemContext } from './steps/FileSystem/components/FileSystemTable';
import FirewallStep from './steps/Firewall';
import FirstBootStep from './steps/FirstBoot';
import HostnameStep from './steps/Hostname';
@ -37,60 +39,59 @@ import Gcp from './steps/TargetEnvironment/Gcp';
import TimezoneStep from './steps/Timezone';
import UsersStep from './steps/Users';
import { getHostArch, getHostDistro } from './utilities/getHostInfo';
import { useHasSpecificTargetOnly } from './utilities/hasSpecificTargetOnly';
import {
useFilesystemValidation,
useSnapshotValidation,
useFirstBootValidation,
useAAPValidation,
useDetailsValidation,
useRegistrationValidation,
useFilesystemValidation,
useFirewallValidation,
useFirstBootValidation,
useHostnameValidation,
useKernelValidation,
useUsersValidation,
useTimezoneValidation,
useFirewallValidation,
useServicesValidation,
useLocaleValidation,
useRegistrationValidation,
useServicesValidation,
useSnapshotValidation,
useTimezoneValidation,
useUsersValidation,
} from './utilities/useValidation';
import {
isAwsAccountIdValid,
isAzureTenantGUIDValid,
isAzureSubscriptionIdValid,
isAzureResourceGroupValid,
isAzureSubscriptionIdValid,
isAzureTenantGUIDValid,
isGcpEmailValid,
} from './validators';
import {
RHEL_8,
RHEL_10_BETA,
RHEL_10,
AARCH64,
CENTOS_9,
AMPLITUDE_MODULE_NAME,
RHEL_10,
RHEL_8,
RHEL_9,
} from '../../constants';
import { useAppDispatch, useAppSelector } from '../../store/hooks';
import './CreateImageWizard.scss';
import {
changeDistribution,
addImageType,
changeArchitecture,
changeAwsShareMethod,
changeDistribution,
initializeWizard,
selectAwsAccountId,
selectAwsShareMethod,
selectAwsSourceId,
selectAzureResourceGroup,
selectAzureShareMethod,
selectAzureSource,
selectAzureSubscriptionId,
selectAzureTenantId,
selectDistribution,
selectGcpEmail,
selectGcpShareMethod,
selectImageTypes,
addImageType,
changeRegistrationType,
} from '../../store/wizardSlice';
import isRhel from '../../Utilities/isRhel';
import { resolveRelPath } from '../../Utilities/path';
import { useFlag, useGetEnvironment } from '../../Utilities/useGetEnvironment';
import { useFlag } from '../../Utilities/useGetEnvironment';
import { ImageBuilderHeader } from '../sharedComponents/ImageBuilderHeader';
type CustomWizardFooterPropType = {
@ -115,71 +116,73 @@ export const CustomWizardFooter = ({
const cancelBtnID = 'wizard-cancel-btn';
return (
<WizardFooterWrapper>
<Button
variant="primary"
onClick={() => {
if (!process.env.IS_ON_PREMISE) {
analytics.track(`${AMPLITUDE_MODULE_NAME} - Button Clicked`, {
module: AMPLITUDE_MODULE_NAME,
button_id: nextBtnID,
active_step_id: activeStep.id,
});
}
if (!beforeNext || beforeNext()) goToNextStep();
}}
isDisabled={disableNext}
>
Next
</Button>
<Button
variant="secondary"
onClick={() => {
if (!process.env.IS_ON_PREMISE) {
analytics.track(`${AMPLITUDE_MODULE_NAME} - Button Clicked`, {
module: AMPLITUDE_MODULE_NAME,
button_id: backBtnID,
active_step_id: activeStep.id,
});
}
goToPrevStep();
}}
isDisabled={disableBack || false}
>
Back
</Button>
{optional && (
<Flex columnGap={{ default: 'columnGapSm' }}>
<Button
variant="tertiary"
variant='primary'
onClick={() => {
if (!process.env.IS_ON_PREMISE) {
analytics.track(`${AMPLITUDE_MODULE_NAME} - Button Clicked`, {
module: AMPLITUDE_MODULE_NAME,
button_id: reviewAndFinishBtnID,
button_id: nextBtnID,
active_step_id: activeStep.id,
});
}
if (!beforeNext || beforeNext()) goToStepById('step-review');
if (!beforeNext || beforeNext()) goToNextStep();
}}
isDisabled={disableNext}
>
Review and finish
Next
</Button>
)}
<Button
variant="link"
onClick={() => {
if (!process.env.IS_ON_PREMISE) {
analytics.track(`${AMPLITUDE_MODULE_NAME} - Button Clicked`, {
module: AMPLITUDE_MODULE_NAME,
button_id: cancelBtnID,
active_step_id: activeStep.id,
});
}
close();
}}
>
Cancel
</Button>
<Button
variant='secondary'
onClick={() => {
if (!process.env.IS_ON_PREMISE) {
analytics.track(`${AMPLITUDE_MODULE_NAME} - Button Clicked`, {
module: AMPLITUDE_MODULE_NAME,
button_id: backBtnID,
active_step_id: activeStep.id,
});
}
goToPrevStep();
}}
isDisabled={disableBack || false}
>
Back
</Button>
{optional && (
<Button
variant='tertiary'
onClick={() => {
if (!process.env.IS_ON_PREMISE) {
analytics.track(`${AMPLITUDE_MODULE_NAME} - Button Clicked`, {
module: AMPLITUDE_MODULE_NAME,
button_id: reviewAndFinishBtnID,
active_step_id: activeStep.id,
});
}
if (!beforeNext || beforeNext()) goToStepById('step-review');
}}
isDisabled={disableNext}
>
Review and finish
</Button>
)}
<Button
variant='link'
onClick={() => {
if (!process.env.IS_ON_PREMISE) {
analytics.track(`${AMPLITUDE_MODULE_NAME} - Button Clicked`, {
module: AMPLITUDE_MODULE_NAME,
button_id: cancelBtnID,
active_step_id: activeStep.id,
});
}
close();
}}
>
Cancel
</Button>
</Flex>
</WizardFooterWrapper>
);
};
@ -193,23 +196,19 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
const navigate = useNavigate();
const dispatch = useAppDispatch();
const [searchParams] = useSearchParams();
const { isFedoraEnv } = useGetEnvironment();
// Feature flags
const complianceEnabled = useFlag('image-builder.compliance.enabled');
const isUsersEnabled = useFlag('image-builder.users.enabled');
const isAAPRegistrationEnabled = useFlag('image-builder.aap.enabled');
// IMPORTANT: Ensure the wizard starts with a fresh initial state
useEffect(() => {
dispatch(initializeWizard());
if (isFedoraEnv) {
dispatch(changeDistribution(CENTOS_9));
dispatch(changeRegistrationType('register-later'));
}
if (searchParams.get('release') === 'rhel8') {
dispatch(changeDistribution(RHEL_8));
}
if (searchParams.get('release') === 'rhel10beta') {
dispatch(changeDistribution(RHEL_10_BETA));
if (searchParams.get('release') === 'rhel9') {
dispatch(changeDistribution(RHEL_9));
}
if (searchParams.get('release') === 'rhel10') {
dispatch(changeDistribution(RHEL_10));
@ -234,6 +233,10 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
dispatch(changeArchitecture(arch));
};
if (process.env.IS_ON_PREMISE) {
dispatch(changeAwsShareMethod('manual'));
}
if (process.env.IS_ON_PREMISE && !isEdit) {
if (!searchParams.get('release')) {
initializeHostDistro();
@ -261,11 +264,9 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
const gcpShareMethod = useAppSelector(selectGcpShareMethod);
const gcpEmail = useAppSelector(selectGcpEmail);
// AZURE
const azureShareMethod = useAppSelector(selectAzureShareMethod);
const azureTenantId = useAppSelector(selectAzureTenantId);
const azureSubscriptionId = useAppSelector(selectAzureSubscriptionId);
const azureResourceGroup = useAppSelector(selectAzureResourceGroup);
const azureSource = useAppSelector(selectAzureSource);
// Registration
const registrationValidation = useRegistrationValidation();
// Snapshots
@ -285,6 +286,8 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
const firewallValidation = useFirewallValidation();
// Services
const servicesValidation = useServicesValidation();
// AAP
const aapValidation = useAAPValidation();
// Firstboot
const firstBootValidation = useFirstBootValidation();
// Details
@ -292,45 +295,51 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
// Users
const usersValidation = useUsersValidation();
const hasWslTargetOnly = useHasSpecificTargetOnly('wsl');
let startIndex = 1; // default index
const JUMP_TO_REVIEW_STEP = 23;
if (isEdit) {
startIndex = 22;
startIndex = JUMP_TO_REVIEW_STEP;
}
const [wasRegisterVisited, setWasRegisterVisited] = useState(false);
// Duplicating some of the logic from the Wizard component to allow for custom nav items status
// for original code see https://github.com/patternfly/patternfly-react/blob/184c55f8d10e1d94ffd72e09212db56c15387c5e/packages/react-core/src/components/Wizard/WizardNavInternal.tsx#L128
const customStatusNavItem = (
const CustomStatusNavItem = (
step: WizardStepType,
activeStep: WizardStepType,
steps: WizardStepType[],
goToStepByIndex: (index: number) => void
goToStepByIndex: (index: number) => void,
) => {
const isVisitOptional =
'parentId' in step && step.parentId === 'step-optional-steps';
if (process.env.IS_ON_PREMISE) {
if (step.id === 'step-oscap' && step.isVisited) {
useEffect(() => {
if (process.env.IS_ON_PREMISE) {
if (step.id === 'step-oscap' && step.isVisited) {
setWasRegisterVisited(true);
}
} else if (step.id === 'step-register' && step.isVisited) {
setWasRegisterVisited(true);
}
} else if (step.id === 'step-register' && step.isVisited) {
setWasRegisterVisited(true);
}
}, [step.id, step.isVisited]);
const hasVisitedNextStep = steps.some(
(s) => s.index > step.index && s.isVisited
(s) => s.index > step.index && s.isVisited,
);
// Only this code is different from the original
const status = (step?.id !== activeStep?.id && step?.status) || 'default';
const status = (step.id !== activeStep.id && step.status) || 'default';
return (
<WizardNavItem
key={step?.id}
id={step?.id}
key={step.id}
id={step.id}
content={step.name}
isCurrent={activeStep?.id === step?.id}
isCurrent={activeStep.id === step.id}
isDisabled={
step.isDisabled ||
(!step.isVisited &&
@ -347,7 +356,7 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
{
module: AMPLITUDE_MODULE_NAME,
isPreview: isBeta(),
}
},
);
}
}}
@ -359,15 +368,15 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
return (
<>
<ImageBuilderHeader inWizard />
<PageSection type={PageSectionTypes.wizard}>
<PageSection hasBodyWrapper={false} type={PageSectionTypes.wizard}>
<Wizard
startIndex={startIndex}
onClose={() => navigate(resolveRelPath(''))}
isVisitRequired
>
<WizardStep
name="Image output"
id="step-image-output"
name='Image output'
id='step-image-output'
footer={
<CustomWizardFooter
disableNext={targetEnvironments.length === 0}
@ -378,25 +387,29 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
<ImageOutputStep />
</WizardStep>
<WizardStep
name="Target Environment"
id="step-target-environment"
name='Target Environment'
id='step-target-environment'
isHidden={
!targetEnvironments.find(
(target) =>
target === 'aws' || target === 'gcp' || target === 'azure'
(target: string) =>
target === 'aws' || target === 'gcp' || target === 'azure',
)
}
steps={[
<WizardStep
name="Amazon Web Services"
id="wizard-target-aws"
key="wizard-target-aws"
name='Amazon Web Services'
id='wizard-target-aws'
key='wizard-target-aws'
footer={
<CustomWizardFooter
disableNext={
awsShareMethod === 'manual'
? !isAwsAccountIdValid(awsAccountId)
: awsSourceId === undefined
// we don't need the account id for
// on-prem aws.
process.env.IS_ON_PREMISE
? false
: awsShareMethod === 'manual'
? !isAwsAccountIdValid(awsAccountId)
: awsSourceId === undefined
}
/>
}
@ -405,9 +418,9 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
<Aws />
</WizardStep>,
<WizardStep
name="Google Cloud Platform"
id="wizard-target-gcp"
key="wizard-target-gcp"
name='Google Cloud Platform'
id='wizard-target-gcp'
key='wizard-target-gcp'
footer={
<CustomWizardFooter
disableNext={
@ -421,21 +434,15 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
<Gcp />
</WizardStep>,
<WizardStep
name="Azure"
id="wizard-target-azure"
key="wizard-target-azure"
name='Azure'
id='wizard-target-azure'
key='wizard-target-azure'
footer={
<CustomWizardFooter
disableNext={
azureShareMethod === 'manual'
? !isAzureTenantGUIDValid(azureTenantId) ||
!isAzureSubscriptionIdValid(azureSubscriptionId) ||
!isAzureResourceGroupValid(azureResourceGroup)
: azureShareMethod === 'sources'
? !isAzureTenantGUIDValid(azureTenantId) ||
!isAzureSubscriptionIdValid(azureSubscriptionId) ||
!isAzureResourceGroupValid(azureResourceGroup)
: azureSource === undefined
!isAzureTenantGUIDValid(azureTenantId) ||
!isAzureSubscriptionIdValid(azureSubscriptionId) ||
!isAzureResourceGroupValid(azureResourceGroup)
}
/>
}
@ -446,15 +453,15 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
]}
/>
<WizardStep
name="Optional steps"
id="step-optional-steps"
name='Optional steps'
id='step-optional-steps'
steps={[
<WizardStep
name="Register"
id="step-register"
key="step-register"
name='Register'
id='step-register'
key='step-register'
isHidden={!!process.env.IS_ON_PREMISE || !isRhel(distribution)}
navItem={customStatusNavItem}
navItem={CustomStatusNavItem}
status={
wasRegisterVisited
? registrationValidation.disabledNext
@ -473,10 +480,9 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
</WizardStep>,
<WizardStep
name={complianceEnabled ? 'Compliance' : 'OpenSCAP'}
id="step-oscap"
key="step-oscap"
isHidden={distribution === RHEL_10_BETA}
navItem={customStatusNavItem}
id='step-oscap'
key='step-oscap'
navItem={CustomStatusNavItem}
footer={
<CustomWizardFooter disableNext={false} optional={true} />
}
@ -484,10 +490,11 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
<OscapStep />
</WizardStep>,
<WizardStep
name="File system configuration"
id="step-file-system"
key="step-file-system"
navItem={customStatusNavItem}
name='File system configuration'
id='step-file-system'
key='step-file-system'
navItem={CustomStatusNavItem}
isHidden={hasWslTargetOnly}
footer={
<CustomWizardFooter
beforeNext={() => {
@ -509,16 +516,12 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
</FileSystemContext.Provider>
</WizardStep>,
<WizardStep
name="Repeatable build"
id="wizard-repository-snapshot"
key="wizard-repository-snapshot"
navItem={customStatusNavItem}
name='Repeatable build'
id='wizard-repository-snapshot'
key='wizard-repository-snapshot'
navItem={CustomStatusNavItem}
status={snapshotValidation.disabledNext ? 'error' : 'default'}
isHidden={
distribution === RHEL_10_BETA ||
!!process.env.IS_ON_PREMISE ||
isFedoraEnv
}
isHidden={!!process.env.IS_ON_PREMISE}
footer={
<CustomWizardFooter
disableNext={snapshotValidation.disabledNext}
@ -529,15 +532,11 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
<SnapshotStep />
</WizardStep>,
<WizardStep
name="Custom repositories"
id="wizard-custom-repositories"
key="wizard-custom-repositories"
navItem={customStatusNavItem}
isHidden={
distribution === RHEL_10_BETA ||
!!process.env.IS_ON_PREMISE ||
isFedoraEnv
}
name='Custom repositories'
id='wizard-custom-repositories'
key='wizard-custom-repositories'
navItem={CustomStatusNavItem}
isHidden={!!process.env.IS_ON_PREMISE}
isDisabled={snapshotValidation.disabledNext}
footer={
<CustomWizardFooter disableNext={false} optional={true} />
@ -546,11 +545,10 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
<RepositoriesStep />
</WizardStep>,
<WizardStep
name="Additional packages"
id="wizard-additional-packages"
key="wizard-additional-packages"
navItem={customStatusNavItem}
isHidden={isFedoraEnv}
name='Additional packages'
id='wizard-additional-packages'
key='wizard-additional-packages'
navItem={CustomStatusNavItem}
isDisabled={snapshotValidation.disabledNext}
footer={
<CustomWizardFooter disableNext={false} optional={true} />
@ -559,11 +557,10 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
<PackagesStep />
</WizardStep>,
<WizardStep
name="Users"
id="wizard-users"
key="wizard-users"
navItem={customStatusNavItem}
isHidden={!isUsersEnabled}
name='Users'
id='wizard-users'
key='wizard-users'
navItem={CustomStatusNavItem}
status={usersValidation.disabledNext ? 'error' : 'default'}
footer={
<CustomWizardFooter
@ -575,10 +572,10 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
<UsersStep />
</WizardStep>,
<WizardStep
name="Timezone"
id="wizard-timezone"
key="wizard-timezone"
navItem={customStatusNavItem}
name='Timezone'
id='wizard-timezone'
key='wizard-timezone'
navItem={CustomStatusNavItem}
status={timezoneValidation.disabledNext ? 'error' : 'default'}
footer={
<CustomWizardFooter
@ -590,10 +587,10 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
<TimezoneStep />
</WizardStep>,
<WizardStep
name="Locale"
id="wizard-locale"
key="wizard-locale"
navItem={customStatusNavItem}
name='Locale'
id='wizard-locale'
key='wizard-locale'
navItem={CustomStatusNavItem}
status={localeValidation.disabledNext ? 'error' : 'default'}
footer={
<CustomWizardFooter
@ -605,10 +602,10 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
<LocaleStep />
</WizardStep>,
<WizardStep
name="Hostname"
id="wizard-hostname"
key="wizard-hostname"
navItem={customStatusNavItem}
name='Hostname'
id='wizard-hostname'
key='wizard-hostname'
navItem={CustomStatusNavItem}
status={hostnameValidation.disabledNext ? 'error' : 'default'}
footer={
<CustomWizardFooter
@ -620,10 +617,11 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
<HostnameStep />
</WizardStep>,
<WizardStep
name="Kernel"
id="wizard-kernel"
key="wizard-kernel"
navItem={customStatusNavItem}
name='Kernel'
id='wizard-kernel'
key='wizard-kernel'
navItem={CustomStatusNavItem}
isHidden={hasWslTargetOnly}
status={kernelValidation.disabledNext ? 'error' : 'default'}
footer={
<CustomWizardFooter
@ -635,10 +633,10 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
<KernelStep />
</WizardStep>,
<WizardStep
name="Firewall"
id="wizard-firewall"
key="wizard-firewall"
navItem={customStatusNavItem}
name='Firewall'
id='wizard-firewall'
key='wizard-firewall'
navItem={CustomStatusNavItem}
status={firewallValidation.disabledNext ? 'error' : 'default'}
footer={
<CustomWizardFooter
@ -650,10 +648,10 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
<FirewallStep />
</WizardStep>,
<WizardStep
name="Systemd services"
id="wizard-services"
key="wizard-services"
navItem={customStatusNavItem}
name='Systemd services'
id='wizard-services'
key='wizard-services'
navItem={CustomStatusNavItem}
status={servicesValidation.disabledNext ? 'error' : 'default'}
footer={
<CustomWizardFooter
@ -665,12 +663,28 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
<ServicesStep />
</WizardStep>,
<WizardStep
name="First boot script configuration"
id="wizard-first-boot"
key="wizard-first-boot"
navItem={customStatusNavItem}
name='Ansible Automation Platform'
id='wizard-aap'
isHidden={!isAAPRegistrationEnabled}
key='wizard-aap'
navItem={CustomStatusNavItem}
status={aapValidation.disabledNext ? 'error' : 'default'}
footer={
<CustomWizardFooter
disableNext={aapValidation.disabledNext}
optional={true}
/>
}
>
<AAPStep />
</WizardStep>,
<WizardStep
name='First boot script configuration'
id='wizard-first-boot'
key='wizard-first-boot'
navItem={CustomStatusNavItem}
status={firstBootValidation.disabledNext ? 'error' : 'default'}
isHidden={!!process.env.IS_ON_PREMISE || isFedoraEnv}
isHidden={!!process.env.IS_ON_PREMISE}
footer={
<CustomWizardFooter
disableNext={firstBootValidation.disabledNext}
@ -683,9 +697,9 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
]}
/>
<WizardStep
name="Details"
id="step-details"
navItem={customStatusNavItem}
name='Details'
id='step-details'
navItem={CustomStatusNavItem}
status={detailsValidation.disabledNext ? 'error' : 'default'}
footer={
<CustomWizardFooter
@ -696,8 +710,8 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
<DetailsStep />
</WizardStep>
<WizardStep
name="Review"
id="step-review"
name='Review'
id='step-review'
footer={<ReviewWizardFooter />}
>
<ReviewStep />

View file

@ -1,6 +1,6 @@
import React, { useEffect } from 'react';
import { addNotification } from '@redhat-cloud-services/frontend-components-notifications/redux';
import { useAddNotification } from '@redhat-cloud-services/frontend-components-notifications/hooks';
import { useLocation, useNavigate } from 'react-router-dom';
import CreateImageWizard from './CreateImageWizard';
@ -13,19 +13,18 @@ const ImportImageWizard = () => {
const dispatch = useAppDispatch();
const navigate = useNavigate();
const location = useLocation();
const addNotification = useAddNotification();
const locationState = location.state as { blueprint?: wizardState };
const blueprint = locationState?.blueprint;
const blueprint = locationState.blueprint;
useEffect(() => {
if (blueprint) {
dispatch(loadWizardState(blueprint));
} else {
navigate(resolveRelPath(''));
dispatch(
addNotification({
variant: 'warning',
title: 'No blueprint was imported',
})
);
addNotification({
variant: 'warning',
title: 'No blueprint was imported',
});
}
}, [blueprint, dispatch]);
return <CreateImageWizard />;

View file

@ -4,6 +4,7 @@ import {
Button,
HelperText,
HelperTextItem,
Icon,
Label,
LabelGroup,
TextInputGroup,
@ -47,30 +48,73 @@ const LabelInput = ({
const dispatch = useAppDispatch();
const [inputValue, setInputValue] = useState('');
const [errorText, setErrorText] = useState(stepValidation.errors[fieldName]);
const [onStepInputErrorText, setOnStepInputErrorText] = useState('');
let [invalidImports, duplicateImports] = ['', ''];
if (stepValidation.errors[fieldName]) {
[invalidImports, duplicateImports] =
stepValidation.errors[fieldName].split('|');
}
const onTextInputChange = (
_event: React.FormEvent<HTMLInputElement>,
value: string
value: string,
) => {
setInputValue(value);
setErrorText('');
setOnStepInputErrorText('');
};
const addItem = (value: string) => {
if (list?.includes(value) || requiredList?.includes(value)) {
setErrorText(`${item} already exists.`);
setOnStepInputErrorText(`${item} already exists.`);
return;
}
if (!validator(value)) {
setErrorText('Invalid format.');
switch (fieldName) {
case 'ports':
setOnStepInputErrorText(
'Expected format: <port/port-name>:<protocol>. Example: 8080:tcp, ssh:tcp',
);
break;
case 'kernelAppend':
setOnStepInputErrorText(
'Expected format: <kernel-argument>. Example: console=tty0',
);
break;
case 'kernelName':
setOnStepInputErrorText(
'Expected format: <kernel-name>. Example: kernel-5.14.0-284.11.1.el9_2.x86_64',
);
break;
case 'groups':
setOnStepInputErrorText(
'Expected format: <group-name>. Example: admin',
);
break;
case 'ntpServers':
setOnStepInputErrorText(
'Expected format: <ntp-server>. Example: time.redhat.com',
);
break;
case 'enabledSystemdServices':
case 'disabledSystemdServices':
case 'maskedSystemdServices':
case 'disabledServices':
case 'enabledServices':
setOnStepInputErrorText(
'Expected format: <service-name>. Example: sshd',
);
break;
default:
setOnStepInputErrorText('Invalid format.');
}
return;
}
dispatch(addAction(value));
setInputValue('');
setErrorText('');
setOnStepInputErrorText('');
};
const handleKeyDown = (e: React.KeyboardEvent, value: string) => {
@ -86,14 +130,18 @@ const LabelInput = ({
const handleRemoveItem = (e: React.MouseEvent, value: string) => {
dispatch(removeAction(value));
setErrorText('');
};
const handleClear = () => {
setInputValue('');
setErrorText('');
setOnStepInputErrorText('');
};
const errors = [];
if (onStepInputErrorText) errors.push(onStepInputErrorText);
if (invalidImports) errors.push(invalidImports);
if (duplicateImports) errors.push(duplicateImports);
return (
<>
<TextInputGroup>
@ -105,33 +153,39 @@ const LabelInput = ({
/>
<TextInputGroupUtilities>
<Button
variant="plain"
icon={
<Icon status='info'>
<PlusCircleIcon />
</Icon>
}
variant='plain'
onClick={(e) => handleAddItem(e, inputValue)}
isDisabled={!inputValue}
aria-label={ariaLabel}
>
<PlusCircleIcon className="pf-v5-u-primary-color-100" />
</Button>
/>
<Button
variant="plain"
icon={<TimesIcon />}
variant='plain'
onClick={handleClear}
isDisabled={!inputValue}
aria-label="Clear input"
>
<TimesIcon />
</Button>
aria-label='Clear input'
/>
</TextInputGroupUtilities>
</TextInputGroup>
{errorText && (
{errors.length > 0 && (
<HelperText>
<HelperTextItem variant={'error'}>{errorText}</HelperTextItem>
{errors.map((error, index) => (
<HelperTextItem key={index} variant={'error'}>
{error}
</HelperTextItem>
))}
</HelperText>
)}
{requiredList && requiredList.length > 0 && (
<LabelGroup
categoryName={requiredCategoryName}
numLabels={20}
className="pf-v5-u-mt-sm pf-v5-u-w-100"
className='pf-v6-u-mt-sm pf-v6-u-w-100'
>
{requiredList.map((item) => (
<Label key={item} isCompact>
@ -140,7 +194,7 @@ const LabelInput = ({
))}
</LabelGroup>
)}
<LabelGroup numLabels={20} className="pf-v5-u-mt-sm pf-v5-u-w-100">
<LabelGroup numLabels={20} className='pf-v6-u-mt-sm pf-v6-u-w-100'>
{list?.map((item) => (
<Label
key={item}

View file

@ -5,8 +5,8 @@ import { Alert } from '@patternfly/react-core';
const UsrSubDirectoriesDisabled = () => {
return (
<Alert
variant="warning"
title="Sub-directories for the /usr mount point are no longer supported"
variant='warning'
title='Sub-directories for the /usr mount point are no longer supported'
isInline
>
Please note that including sub-directories in the /usr path is no longer

View file

@ -23,7 +23,7 @@ type ValidatedTextInputPropTypes = TextInputProps & {
type ValidationInputProp = TextInputProps &
TextAreaProps & {
value: string;
placeholder: string;
placeholder?: string;
stepValidation: StepValidation;
dataTestId?: string;
fieldName: string;
@ -31,7 +31,7 @@ type ValidationInputProp = TextInputProps &
ariaLabel: string;
onChange: (
event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>,
value: string
value: string,
) => void;
isRequired?: boolean;
warning?: string;
@ -91,16 +91,14 @@ export const ValidatedInputAndTextArea = ({
onChange={onChange}
validated={validated}
onBlur={handleBlur}
placeholder={placeholder}
placeholder={placeholder || ''}
aria-label={ariaLabel}
data-testid={dataTestId}
/>
)}
{warning !== undefined && warning !== '' && (
<HelperText>
<HelperTextItem variant="warning" hasIcon>
{warning}
</HelperTextItem>
<HelperTextItem variant='warning'>{warning}</HelperTextItem>
</HelperText>
)}
{validated === 'error' && hasError && (
@ -113,13 +111,13 @@ export const ValidatedInputAndTextArea = ({
const getValidationState = (
isPristine: boolean,
errorMessage: string,
isRequired: boolean | undefined
isRequired: boolean | undefined,
): ValidationResult => {
const validated = isPristine
? 'default'
: (isRequired && errorMessage) || errorMessage
? 'error'
: 'success';
? 'error'
: 'success';
return validated;
};
@ -127,9 +125,7 @@ const getValidationState = (
export const ErrorMessage = ({ errorMessage }: ErrorMessageProps) => {
return (
<HelperText>
<HelperTextItem variant="error" hasIcon>
{errorMessage}
</HelperTextItem>
<HelperTextItem variant='error'>{errorMessage}</HelperTextItem>
</HelperText>
);
};
@ -142,6 +138,7 @@ export const ValidatedInput = ({
value,
placeholder,
onChange,
...props
}: ValidatedTextInputPropTypes) => {
const [isPristine, setIsPristine] = useState(!value ? true : false);
@ -162,18 +159,17 @@ export const ValidatedInput = ({
<TextInput
value={value}
data-testid={dataTestId}
type="text"
type='text'
onChange={onChange!}
validated={handleValidation()}
aria-label={ariaLabel || ''}
onBlur={handleBlur}
placeholder={placeholder || ''}
{...props}
/>
{!isPristine && !validator(value) && (
<HelperText>
<HelperTextItem variant="error" hasIcon>
{helperText}
</HelperTextItem>
<HelperTextItem variant='error'>{helperText}</HelperTextItem>
</HelperText>
)}
</>

View file

@ -0,0 +1,178 @@
import React from 'react';
import {
Checkbox,
DropEvent,
FileUpload,
FormGroup,
FormHelperText,
HelperText,
HelperTextItem,
} from '@patternfly/react-core';
import { useAppDispatch, useAppSelector } from '../../../../../store/hooks';
import {
changeAapCallbackUrl,
changeAapHostConfigKey,
changeAapTlsCertificateAuthority,
changeAapTlsConfirmation,
selectAapCallbackUrl,
selectAapHostConfigKey,
selectAapTlsCertificateAuthority,
selectAapTlsConfirmation,
} from '../../../../../store/wizardSlice';
import { useAAPValidation } from '../../../utilities/useValidation';
import { ValidatedInputAndTextArea } from '../../../ValidatedInput';
import { validateMultipleCertificates } from '../../../validators';
const AAPRegistration = () => {
const dispatch = useAppDispatch();
const callbackUrl = useAppSelector(selectAapCallbackUrl);
const hostConfigKey = useAppSelector(selectAapHostConfigKey);
const tlsCertificateAuthority = useAppSelector(
selectAapTlsCertificateAuthority,
);
const tlsConfirmation = useAppSelector(selectAapTlsConfirmation);
const [isRejected, setIsRejected] = React.useState(false);
const stepValidation = useAAPValidation();
const isHttpsUrl = callbackUrl?.toLowerCase().startsWith('https://') || false;
const shouldShowCaInput = !isHttpsUrl || (isHttpsUrl && !tlsConfirmation);
const validated = stepValidation.errors['certificate']
? 'error'
: stepValidation.errors['certificate'] === undefined &&
tlsCertificateAuthority &&
validateMultipleCertificates(tlsCertificateAuthority).validCertificates
.length > 0
? 'success'
: 'default';
const handleCallbackUrlChange = (value: string) => {
dispatch(changeAapCallbackUrl(value));
};
const handleHostConfigKeyChange = (value: string) => {
dispatch(changeAapHostConfigKey(value));
};
const handleClear = () => {
dispatch(changeAapTlsCertificateAuthority(''));
};
const handleTextChange = (
_event: React.ChangeEvent<HTMLTextAreaElement>,
value: string,
) => {
dispatch(changeAapTlsCertificateAuthority(value));
setIsRejected(false);
};
const handleDataChange = (_: DropEvent, value: string) => {
dispatch(changeAapTlsCertificateAuthority(value));
setIsRejected(false);
};
const handleFileRejected = () => {
dispatch(changeAapTlsCertificateAuthority(''));
setIsRejected(true);
};
const handleTlsConfirmationChange = (checked: boolean) => {
dispatch(changeAapTlsConfirmation(checked));
};
return (
<>
<FormGroup label='Ansible Callback URL' isRequired>
<ValidatedInputAndTextArea
value={callbackUrl || ''}
onChange={(_event, value) => handleCallbackUrlChange(value.trim())}
ariaLabel='ansible callback url'
isRequired
stepValidation={stepValidation}
fieldName='callbackUrl'
/>
</FormGroup>
<FormGroup label='Host Config Key' isRequired>
<ValidatedInputAndTextArea
value={hostConfigKey || ''}
onChange={(_event, value) => handleHostConfigKeyChange(value.trim())}
ariaLabel='host config key'
isRequired
stepValidation={stepValidation}
fieldName='hostConfigKey'
/>
</FormGroup>
{shouldShowCaInput && (
<FormGroup label='Certificate authority (CA) for Ansible Controller'>
<FileUpload
id='aap-certificate-upload'
type='text'
value={tlsCertificateAuthority || ''}
filename={tlsCertificateAuthority ? 'CA detected' : ''}
onDataChange={handleDataChange}
onTextChange={handleTextChange}
onClearClick={handleClear}
dropzoneProps={{
accept: {
'application/x-pem-file': ['.pem'],
'application/x-x509-ca-cert': ['.cer', '.crt'],
'application/pkix-cert': ['.der'],
},
maxSize: 512000,
onDropRejected: handleFileRejected,
}}
validated={isRejected ? 'error' : validated}
browseButtonText='Upload'
allowEditingUploadedText={true}
/>
<FormHelperText>
<HelperText>
<HelperTextItem
variant={
isRejected || validated === 'error'
? 'error'
: validated === 'success'
? 'success'
: 'default'
}
>
{isRejected
? 'Must be a .PEM/.CER/.CRT file'
: validated === 'error'
? stepValidation.errors['certificate']
: validated === 'success'
? 'Certificate was uploaded'
: 'Drag and drop a valid certificate file or upload one'}
</HelperTextItem>
</HelperText>
</FormHelperText>
</FormGroup>
)}
{isHttpsUrl && (
<FormGroup>
<Checkbox
id='tls-confirmation-checkbox'
label='Insecure'
isChecked={tlsConfirmation || false}
onChange={(_event, checked) => handleTlsConfirmationChange(checked)}
/>
{stepValidation.errors['tlsConfirmation'] && (
<FormHelperText>
<HelperText>
<HelperTextItem variant='error'>
{stepValidation.errors['tlsConfirmation']}
</HelperTextItem>
</HelperText>
</FormHelperText>
)}
</FormGroup>
)}
</>
);
};
export default AAPRegistration;

View file

@ -0,0 +1,18 @@
import React from 'react';
import { Form, Title } from '@patternfly/react-core';
import AAPRegistration from './components/AAPRegistration';
const AAPStep = () => {
return (
<Form>
<Title headingLevel='h1' size='xl'>
Ansible Automation Platform
</Title>
<AAPRegistration />
</Form>
);
};
export default AAPStep;

View file

@ -1,12 +1,12 @@
import React from 'react';
import {
Content,
Form,
FormGroup,
FormHelperText,
HelperText,
HelperTextItem,
Text,
Title,
} from '@patternfly/react-core';
@ -28,7 +28,7 @@ const DetailsStep = () => {
const handleNameChange = (
_event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>,
name: string
name: string,
) => {
dispatch(changeBlueprintName(name));
dispatch(setIsCustomName());
@ -36,7 +36,7 @@ const DetailsStep = () => {
const handleDescriptionChange = (
_event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>,
description: string
description: string,
) => {
dispatch(changeBlueprintDescription(description));
};
@ -45,23 +45,23 @@ const DetailsStep = () => {
return (
<Form>
<Title headingLevel="h1" size="xl">
<Title headingLevel='h1' size='xl'>
Details
</Title>
<Text>
<Content>
Enter a name to identify your blueprint. If no name is entered, the
images created from this blueprint will use the name of the parent
blueprint.
</Text>
<FormGroup isRequired label="Blueprint name" fieldId="blueprint-name">
</Content>
<FormGroup isRequired label='Blueprint name' fieldId='blueprint-name'>
<ValidatedInputAndTextArea
ariaLabel="blueprint name"
dataTestId="blueprint"
ariaLabel='blueprint name'
dataTestId='blueprint'
value={blueprintName}
onChange={handleNameChange}
placeholder="Add blueprint name"
placeholder='Add blueprint name'
stepValidation={stepValidation}
fieldName="name"
fieldName='name'
isRequired={true}
/>
<FormHelperText>
@ -75,17 +75,17 @@ const DetailsStep = () => {
</FormGroup>
<FormGroup
label="Blueprint description"
fieldId="blueprint-description-name"
label='Blueprint description'
fieldId='blueprint-description-name'
>
<ValidatedInputAndTextArea
ariaLabel="blueprint description"
dataTestId="blueprint description"
ariaLabel='blueprint description'
dataTestId='blueprint description'
value={blueprintDescription}
onChange={handleDescriptionChange}
placeholder="Add description"
placeholder='Add description'
stepValidation={stepValidation}
fieldName="description"
fieldName='description'
/>
</FormGroup>
</Form>

Some files were not shown because too many files have changed in this diff Show more