Compare commits

...

123 commits
v72 ... 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
270 changed files with 7068 additions and 42590 deletions

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

@ -71,7 +71,7 @@ jobs:
run: npm ci
- name: Check for manual changes to API
run: |
npm run api:generate
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."

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>

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

@ -45,7 +45,7 @@ spec:
- name: name
value: show-sbom
- name: bundle
value: quay.io/konflux-ci/tekton-catalog/task-show-sbom:0.1@sha256:86c069cac0a669797e8049faa8aa4088e70ff7fcd579d5bdc37626a9e0488a05
value: quay.io/konflux-ci/tekton-catalog/task-show-sbom:0.1@sha256:beb0616db051952b4b861dd8c3e00fa1c0eccbd926feddf71194d3bb3ace9ce7
- name: kind
value: task
resolver: bundles
@ -152,7 +152,7 @@ spec:
- name: name
value: init
- name: bundle
value: quay.io/konflux-ci/tekton-catalog/task-init:0.2@sha256:1d8221c84f91b923d89de50bf16481ea729e3b68ea04a9a7cbe8485ddbb27ee6
value: quay.io/konflux-ci/tekton-catalog/task-init:0.2@sha256:08e18a4dc5f947c1d20e8353a19d013144bea87b72f67236b165dd4778523951
- name: kind
value: task
resolver: bundles
@ -194,7 +194,7 @@ spec:
- name: name
value: prefetch-dependencies
- name: bundle
value: quay.io/konflux-ci/tekton-catalog/task-prefetch-dependencies:0.2@sha256:848f4d5e592d6c145ba3575f52b88d65be95ad6fbba108b24ff79d766cf5d45d
value: quay.io/konflux-ci/tekton-catalog/task-prefetch-dependencies:0.2@sha256:ce5f2485d759221444357fe38276be876fc54531651e50dcfc0f84b34909d760
- name: kind
value: task
resolver: bundles
@ -238,7 +238,7 @@ spec:
- name: name
value: buildah
- name: bundle
value: quay.io/konflux-ci/tekton-catalog/task-buildah:0.4@sha256:9d7d4724cd2fac84ca1b246eedf51d34f4f0a6d34c4a5b2f9bb614a0f293f38d
value: quay.io/konflux-ci/tekton-catalog/task-buildah:0.4@sha256:7782cb7462130de8e8839a58dd15ed78e50938d718b51375267679c6044b4367
- name: kind
value: task
resolver: bundles
@ -270,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:3499772af90aad0d3935629be6d37dd9292195fb629e6f43ec839c7f545a0faa
value: quay.io/konflux-ci/tekton-catalog/task-build-image-index:0.1@sha256:72f77a8c62f9d6f69ab5c35170839e4b190026e6cc3d7d4ceafa7033fc30ad7b
- name: kind
value: task
resolver: bundles
@ -292,7 +292,7 @@ spec:
- name: name
value: source-build
- name: bundle
value: quay.io/konflux-ci/tekton-catalog/task-source-build:0.3@sha256:1fdda7563f21340d6243c8738934a58adffd8253706b423d1c4ec5e26ba5fae0
value: quay.io/konflux-ci/tekton-catalog/task-source-build:0.3@sha256:96ed9431854ecf9805407dca77b063abdf7aba1b3b9d1925a5c6145c6b7e95fd
- 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:3c8b81fa868e27c6266e7660a4bfb4c822846dcf4304606e71e20893b0d3e515
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:417f44117f8d87a4a62fea6589b5746612ac61640b454dbd88f74892380411f2
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:f99d2bdb02f13223d494077a2cde31418d09369f33c02134a8e7e5fad2f61eda
value: quay.io/konflux-ci/tekton-catalog/task-ecosystem-cert-preflight-checks:0.2@sha256:1f151e00f7fc427654b7b76045a426bb02fe650d192ffe147a304d2184787e38
- 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:7749146f7e4fe530846f1b15c9366178ec9f44776ef1922a60d3e7e2b8c6426b
value: quay.io/konflux-ci/tekton-catalog/task-clamav-scan:0.3@sha256:cce2dfcc5bd6e91ee54aacdadad523b013eeae5cdaa7f6a4624b8cbcc040f439
- name: kind
value: task
resolver: bundles
@ -482,7 +482,7 @@ spec:
- name: name
value: apply-tags
- name: bundle
value: quay.io/konflux-ci/tekton-catalog/task-apply-tags:0.2@sha256:517a51e260c0b59654a9d7b842e1ab07d76bce15ca7ce9c8fd2489a19be6463d
value: quay.io/konflux-ci/tekton-catalog/task-apply-tags:0.2@sha256:70881c97a4c51ee1f4d023fa1110e0bdfcfd2f51d9a261fa543c3862b9a4eee9
- name: kind
value: task
resolver: bundles
@ -503,7 +503,7 @@ spec:
- name: name
value: push-dockerfile
- name: bundle
value: quay.io/konflux-ci/tekton-catalog/task-push-dockerfile:0.1@sha256:794850ad7934523d511ebd4e3ec16f1a811a2fa8729580f00209be174b8a3818
value: quay.io/konflux-ci/tekton-catalog/task-push-dockerfile:0.1@sha256:d5cb22a833be51dd72a872cac8bfbe149e8ad34da7cb48a643a1e613447a1f9d
- name: kind
value: task
resolver: bundles

View file

@ -42,7 +42,7 @@ spec:
- name: name
value: show-sbom
- name: bundle
value: quay.io/konflux-ci/tekton-catalog/task-show-sbom:0.1@sha256:86c069cac0a669797e8049faa8aa4088e70ff7fcd579d5bdc37626a9e0488a05
value: quay.io/konflux-ci/tekton-catalog/task-show-sbom:0.1@sha256:beb0616db051952b4b861dd8c3e00fa1c0eccbd926feddf71194d3bb3ace9ce7
- name: kind
value: task
resolver: bundles
@ -149,7 +149,7 @@ spec:
- name: name
value: init
- name: bundle
value: quay.io/konflux-ci/tekton-catalog/task-init:0.2@sha256:1d8221c84f91b923d89de50bf16481ea729e3b68ea04a9a7cbe8485ddbb27ee6
value: quay.io/konflux-ci/tekton-catalog/task-init:0.2@sha256:08e18a4dc5f947c1d20e8353a19d013144bea87b72f67236b165dd4778523951
- name: kind
value: task
resolver: bundles
@ -191,7 +191,7 @@ spec:
- name: name
value: prefetch-dependencies
- name: bundle
value: quay.io/konflux-ci/tekton-catalog/task-prefetch-dependencies:0.2@sha256:848f4d5e592d6c145ba3575f52b88d65be95ad6fbba108b24ff79d766cf5d45d
value: quay.io/konflux-ci/tekton-catalog/task-prefetch-dependencies:0.2@sha256:ce5f2485d759221444357fe38276be876fc54531651e50dcfc0f84b34909d760
- name: kind
value: task
resolver: bundles
@ -235,7 +235,7 @@ spec:
- name: name
value: buildah
- name: bundle
value: quay.io/konflux-ci/tekton-catalog/task-buildah:0.4@sha256:9d7d4724cd2fac84ca1b246eedf51d34f4f0a6d34c4a5b2f9bb614a0f293f38d
value: quay.io/konflux-ci/tekton-catalog/task-buildah:0.4@sha256:7782cb7462130de8e8839a58dd15ed78e50938d718b51375267679c6044b4367
- name: kind
value: task
resolver: bundles
@ -267,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:3499772af90aad0d3935629be6d37dd9292195fb629e6f43ec839c7f545a0faa
value: quay.io/konflux-ci/tekton-catalog/task-build-image-index:0.1@sha256:72f77a8c62f9d6f69ab5c35170839e4b190026e6cc3d7d4ceafa7033fc30ad7b
- name: kind
value: task
resolver: bundles
@ -289,7 +289,7 @@ spec:
- name: name
value: source-build
- name: bundle
value: quay.io/konflux-ci/tekton-catalog/task-source-build:0.3@sha256:1fdda7563f21340d6243c8738934a58adffd8253706b423d1c4ec5e26ba5fae0
value: quay.io/konflux-ci/tekton-catalog/task-source-build:0.3@sha256:96ed9431854ecf9805407dca77b063abdf7aba1b3b9d1925a5c6145c6b7e95fd
- 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:3c8b81fa868e27c6266e7660a4bfb4c822846dcf4304606e71e20893b0d3e515
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:417f44117f8d87a4a62fea6589b5746612ac61640b454dbd88f74892380411f2
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:f99d2bdb02f13223d494077a2cde31418d09369f33c02134a8e7e5fad2f61eda
value: quay.io/konflux-ci/tekton-catalog/task-ecosystem-cert-preflight-checks:0.2@sha256:1f151e00f7fc427654b7b76045a426bb02fe650d192ffe147a304d2184787e38
- 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:7749146f7e4fe530846f1b15c9366178ec9f44776ef1922a60d3e7e2b8c6426b
value: quay.io/konflux-ci/tekton-catalog/task-clamav-scan:0.3@sha256:cce2dfcc5bd6e91ee54aacdadad523b013eeae5cdaa7f6a4624b8cbcc040f439
- name: kind
value: task
resolver: bundles
@ -479,7 +479,7 @@ spec:
- name: name
value: apply-tags
- name: bundle
value: quay.io/konflux-ci/tekton-catalog/task-apply-tags:0.2@sha256:517a51e260c0b59654a9d7b842e1ab07d76bce15ca7ce9c8fd2489a19be6463d
value: quay.io/konflux-ci/tekton-catalog/task-apply-tags:0.2@sha256:70881c97a4c51ee1f4d023fa1110e0bdfcfd2f51d9a261fa543c3862b9a4eee9
- name: kind
value: task
resolver: bundles
@ -500,7 +500,7 @@ spec:
- name: name
value: push-dockerfile
- name: bundle
value: quay.io/konflux-ci/tekton-catalog/task-push-dockerfile:0.1@sha256:794850ad7934523d511ebd4e3ec16f1a811a2fa8729580f00209be174b8a3818
value: quay.io/konflux-ci/tekton-catalog/task-push-dockerfile:0.1@sha256:d5cb22a833be51dd72a872cac8bfbe149e8ad34da7cb48a643a1e613447a1f9d
- name: kind
value: task
resolver: bundles

View file

@ -128,19 +128,15 @@ see the [osbuild-getting-started project](https://github.com/osbuild/osbuild-get
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 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
@ -156,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,
@ -178,14 +174,7 @@ 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 `eslint.config.js` file by adding the generated code path to the ignores array:
4. Update the `eslint.config.js` file by adding the generated code path to the ignores array:
```
ignores: [
@ -194,7 +183,7 @@ ignores: [
]
```
7. run api generation
5. run api generation
```bash
npm run api
@ -355,12 +344,12 @@ 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
```

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',

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 schema files and overwrite the existing ones
curl https://raw.githubusercontent.com/osbuild/image-builder/main/internal/v1/api.yaml -o ./api/schema/imageBuilder.yaml
curl https://console.redhat.com/api/rhsm/v2/openapi.json -o ./api/schema/rhsm.json
curl https://console.redhat.com/api/content-sources/v1/openapi.json -o ./api/schema/contentSources.json
curl https://console.redhat.com/api/provisioning/v1/openapi.json -o ./api/schema/provisioning.json
curl https://console.redhat.com/api/edge/v1/openapi.json -o ./api/schema/edge.json
curl https://console.redhat.com/api/compliance/v2/openapi.json -o ./api/schema/compliance.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 07aeb0dd02b76bd1c9c309b8ec0e96893e0ebe49
Subproject commit b496d0a8c1755608bd256a6960869b14a7689d38

View file

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

View file

@ -7,6 +7,7 @@ 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');
@ -21,7 +22,6 @@ module.exports = defineConfig([
'**/contentSourcesApi.ts',
'**/rhsmApi.ts',
'**/provisioningApi.ts',
'**/edgeApi.ts',
'**/complianceApi.ts',
'**/composerCloudApi.ts'
]
@ -62,6 +62,7 @@ module.exports = defineConfig([
import: pluginImport,
jsxA11y: pluginJsxA11y,
'disable-autofix': disableAutofix,
prettier: pluginPrettier,
},
rules: {
...js.configs.recommended.rules,
@ -111,8 +112,25 @@ module.exports = defineConfig([
'@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': 'warn',
'@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: {
@ -145,6 +163,12 @@ module.exports = defineConfig([
...pluginPlaywright.configs.recommended.rules,
'playwright/no-conditional-in-test': 'off',
'playwright/no-conditional-expect': 'off',
'playwright/no-skipped-test': [
'error',
{
'allowConditional': true
}
]
},
},
]);

1307
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": "6.3.0",
"@patternfly/react-code-editor": "6.1.0",
"@patternfly/react-core": "6.1.0",
"@patternfly/react-table": "6.3.0",
"@redhat-cloud-services/frontend-components": "6.1.0",
"@redhat-cloud-services/frontend-components-notifications": "6.1.0",
"@redhat-cloud-services/frontend-components-utilities": "6.1.0",
"@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.6.1",
"@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",
@ -34,25 +35,25 @@
"@babel/preset-env": "7.28.0",
"@babel/preset-react": "7.27.1",
"@babel/preset-typescript": "7.27.1",
"@currents/playwright": "1.15.2",
"@eslint/js": "9.31.0",
"@patternfly/react-icons": "6.1.0",
"@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": "3.0.0",
"@redhat-cloud-services/frontend-components-config": "6.3.8",
"@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": "24.0.13",
"@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.38.0",
"@typescript-eslint/parser": "8.38.0",
"@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",
@ -61,16 +62,17 @@
"chartjs-plugin-annotation": "3.1.0",
"copy-webpack-plugin": "13.0.0",
"css-loader": "7.1.2",
"eslint": "9.30.1",
"eslint": "9.33.0",
"eslint-plugin-disable-autofix": "5.0.1",
"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.6.0",
"eslint-plugin-testing-library": "7.6.6",
"git-revision-webpack-plugin": "5.0.0",
"globals": "16.3.0",
"history": "5.3.0",
@ -79,20 +81,20 @@
"madge": "8.0.0",
"mini-css-extract-plugin": "2.9.2",
"moment": "2.30.1",
"msw": "2.10.4",
"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.89.2",
"sass": "1.90.0",
"sass-loader": "16.0.5",
"stylelint": "16.22.0",
"stylelint-config-recommended-scss": "15.0.1",
"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.38.0",
"typescript-eslint": "8.40.0",
"uuid": "11.1.0",
"vitest": "3.2.4",
"vitest-canvas-mock": "0.3.3",
@ -115,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

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

@ -158,8 +158,8 @@ test('Create a blueprint with Filesystem customization', async ({
await frame.getByRole('option', { name: '/usr' }).click();
await expect(
frame.getByText(
'Sub-directories for the /usr mount point are no longer supported'
)
'Sub-directories for the /usr mount point are no longer supported',
),
).toBeVisible();
await frame.getByRole('button', { name: '/usr' }).click();

View file

@ -66,9 +66,9 @@ test('Create a blueprint with Firewall customization', async ({
await expect(
frame
.getByText(
'Expected format: <port/port-name>:<protocol>. Example: 8080:tcp, ssh:tcp'
'Expected format: <port/port-name>:<protocol>. Example: 8080:tcp, ssh:tcp',
)
.nth(0)
.nth(0),
).toBeVisible();
});
@ -76,7 +76,7 @@ test('Create a blueprint with Firewall customization', async ({
await frame.getByPlaceholder('Add disabled service').fill('1');
await frame.getByRole('button', { name: 'Add disabled service' }).click();
await expect(
frame.getByText('Expected format: <service-name>. Example: sshd').nth(0)
frame.getByText('Expected format: <service-name>. Example: sshd').nth(0),
).toBeVisible();
});
@ -84,7 +84,7 @@ test('Create a blueprint with Firewall customization', async ({
await frame.getByPlaceholder('Add enabled service').fill('ťčš');
await frame.getByRole('button', { name: 'Add enabled service' }).click();
await expect(
frame.getByText('Expected format: <service-name>. Example: sshd').nth(1)
frame.getByText('Expected format: <service-name>. Example: sshd').nth(1),
).toBeVisible();
});

View file

@ -83,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

@ -47,30 +47,30 @@ test('Create a blueprint with Kernel customization', async ({
await frame.getByRole('button', { name: 'Add kernel argument' }).click();
await frame
.getByPlaceholder('Add kernel argument')
.fill('invalid/argument');
.fill('invalid$argument');
await frame.getByRole('button', { name: 'Add kernel argument' }).click();
await expect(
frame.getByText(
'Expected format: <kernel-argument>. Example: console=tty0'
)
'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' })
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' })
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();
@ -121,7 +121,7 @@ test('Create a blueprint with Kernel customization', async ({
await fillInImageOutputGuest(frame);
await frame.getByRole('button', { name: 'Kernel' }).click();
await expect(frame.getByPlaceholder('Select kernel package')).toHaveValue(
'kernel'
'kernel',
);
await expect(frame.getByText('rootwait')).toBeVisible();
await expect(frame.getByText('console=tty0')).toBeVisible();

View file

@ -48,7 +48,7 @@ test('Create a blueprint with Locale customization', async ({
await expect(
frame.getByRole('button', {
name: 'Close Western Frisian - Germany (fy_DE.UTF-8)',
})
}),
).toBeEnabled();
await frame
.getByRole('button', {
@ -58,7 +58,7 @@ test('Create a blueprint with Locale customization', async ({
await expect(
frame.getByRole('button', {
name: 'Close Western Frisian - Germany (fy_DE.UTF-8)',
})
}),
).toBeHidden();
await frame.getByPlaceholder('Select a language').fill('fy');
await frame
@ -67,20 +67,20 @@ test('Create a blueprint with Locale customization', async ({
await expect(
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 - Djibouti (aa_DJ.UTF-8)' })
.click();
await expect(
frame.getByRole('button', { name: 'Close aa - Djibouti (aa_DJ.UTF-8)' })
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 - Djibouti (aa_DJ.UTF-8)Language already addedaa - Eritrea (aa_ER.UTF-8)aa - Ethiopia (aa_ET.UTF-8)'
)
'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();
@ -104,17 +104,17 @@ test('Create a blueprint with Locale customization', async ({
await expect(
frame.getByRole('button', {
name: 'Close Western Frisian - Germany (fy_DE.UTF-8)',
})
}),
).toBeEnabled();
await expect(
frame.getByRole('button', { name: 'Close aa - Djibouti (aa_DJ.UTF-8)' })
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 - Eritrea (aa_ER.UTF-8)' })
.click();
await expect(
frame.getByRole('button', { name: 'Close aa - Eritrea (aa_ER.UTF-8)' })
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();
@ -143,16 +143,16 @@ test('Create a blueprint with Locale customization', async ({
await expect(
frame.getByRole('button', {
name: 'Close Western Frisian - Germany (fy_DE.UTF-8)',
})
}),
).toBeEnabled();
await expect(
frame.getByRole('button', { name: 'Close aa - Djibouti (aa_DJ.UTF-8)' })
frame.getByRole('button', { name: 'Close aa - Djibouti (aa_DJ.UTF-8)' }),
).toBeEnabled();
await expect(
frame.getByRole('button', { name: 'Close aa - Eritrea (aa_ER.UTF-8)' })
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

@ -65,19 +65,19 @@ test('Create a blueprint with Systemd customization', async ({
await frame.getByPlaceholder('Add disabled service').fill('&&');
await frame.getByRole('button', { name: 'Add disabled service' }).click();
await expect(
frame.getByText('Expected format: <service-name>. Example: sshd').nth(0)
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('Expected format: <service-name>. Example: sshd').nth(1)
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('Expected format: <service-name>. Example: sshd').nth(2)
frame.getByText('Expected format: <service-name>. Example: sshd').nth(2),
).toBeVisible();
});

View file

@ -58,7 +58,7 @@ test('Create a blueprint with Timezone customization', async ({
await expect(
frame
.getByText('Expected format: <ntp-server>. Example: time.redhat.com')
.nth(0)
.nth(0),
).toBeVisible();
await frame.getByPlaceholder('Add NTP servers').fill('0.cz.pool.ntp.org');
await frame.getByRole('button', { name: 'Add NTP server' }).click();
@ -86,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();
@ -118,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

@ -38,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

@ -1,3 +1,6 @@
import { execSync } from 'child_process';
import { readFileSync } from 'node:fs';
import { expect, type Page } from '@playwright/test';
export const togglePreview = async (page: Page) => {
@ -42,3 +45,43 @@ export const closePopupsIfExist = async (page: Page) => {
});
}
};
// 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

@ -68,14 +68,14 @@ const loginCockpit = async (page: Page, user: string, password: string) => {
try {
// Check if the user already has administrative access
await expect(
page.getByRole('button', { name: 'Administrative access' })
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' })
frame.getByRole('heading', { name: 'Access is limited' }),
).toBeVisible();
await page.getByRole('button', { name: 'Limited access' }).click();
@ -99,10 +99,10 @@ const loginCockpit = async (page: Page, user: string, password: string) => {
// 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();
};

View file

@ -1,6 +1,6 @@
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
@ -8,6 +8,13 @@ import { isHosted } from './helpers';
*/
export const navigateToOptionalSteps = async (page: Page | FrameLocator) => {
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();
};

View file

@ -10,7 +10,7 @@ import { ibFrame, navigateToLandingPage } 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();
@ -31,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
@ -86,7 +86,7 @@ export const deleteBlueprint = async (page: Page, blueprintName: string) => {
// 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' })
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
@ -101,7 +101,7 @@ export const deleteBlueprint = async (page: Page, blueprintName: string) => {
await frame.getByRole('menuitem', { name: 'Delete blueprint' }).click();
await frame.getByRole('button', { name: 'Delete' }).click();
},
{ box: true }
{ box: true },
);
};
@ -129,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();
@ -138,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

@ -72,6 +72,11 @@ test.describe.serial('test', () => {
frame.getByRole('heading', { name: 'Systemd services' });
await frame.getByRole('button', { name: 'Next', exact: true }).click();
if (isHosted()) {
frame.getByRole('heading', { name: 'Ansible Automation Platform' });
await frame.getByRole('button', { name: 'Next', exact: true }).click();
}
if (isHosted()) {
frame.getByRole('heading', { name: 'First boot configuration' });
await frame.getByRole('button', { name: 'Next', exact: true }).click();
@ -87,7 +92,12 @@ test.describe.serial('test', () => {
await frame.getByRole('button', { name: 'Create blueprint' }).click();
await expect(
frame.locator('.pf-v6-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();
});
@ -121,6 +131,7 @@ test.describe.serial('test', () => {
await frame.getByRole('button', { name: 'Review and finish' }).click();
await frame.getByRole('button', { name: 'About packages' }).click();
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();
@ -128,6 +139,7 @@ test.describe.serial('test', () => {
await frame.getByRole('button', { name: 'Edit blueprint' }).click();
await frame.getByRole('button', { name: 'About packages' }).click();
frame.getByRole('gridcell', { name: 'osbuild-composer' });
await frame.getByRole('button', { name: 'Close', exact: true }).click();
await frame.getByRole('button', { name: 'Cancel', exact: true }).click();
frame.getByRole('heading', { name: 'All images' });
});
@ -196,14 +208,16 @@ test.describe.serial('test', () => {
const switchInput = frame.locator('#aws-config-switch');
await expect(switchInput).toBeVisible();
// the test is a little bit flaky, if it fails the first time
// while setting the configs, the second time the config should
// already be loaded and visible, if it is go back to the landing page
// https://github.com/osbuild/image-builder-frontend/issues/3429
// 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' })
frame.getByRole('heading', { name: 'All images' }),
).toBeVisible();
} else {
const switchToggle = frame.locator('.pf-v6-c-switch');
@ -217,27 +231,84 @@ test.describe.serial('test', () => {
await frame.getByPlaceholder('Path to AWS credentials').fill(credentials);
await frame.getByRole('button', { name: 'Submit' }).click();
await expect(
frame.getByRole('heading', { name: 'All images' })
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('button', { name: 'Configure Cloud Providers' })
.click();
await expect(header).toBeVisible();
await expect(frame.locator('#aws-config-switch')).toBeChecked();
.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();
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();
// make sure the image is present
await frame
.getByTestId('images-table')
.getByRole('button', { name: 'Details' })
.click();
frame.getByText('Build Information');
});
});

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,11 +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,6 +1,6 @@
import React, { useEffect } from 'react';
import { useChrome } from '@redhat-cloud-services/frontend-components/useChrome';
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';

View file

@ -41,7 +41,7 @@ const Application = () => {
};
const ImageBuilder = () => (
<Provider store={store}>
<Page className="no-masthead-sidebar" isContentFilled>
<Page className='no-masthead-sidebar' isContentFilled>
<PageSection>
<Application />
</PageSection>

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,10 +58,10 @@ export const BlueprintActionsMenu: React.FunctionComponent<
ref={toggleRef}
isExpanded={showBlueprintActionsMenu}
onClick={() => setShowBlueprintActionsMenu(!showBlueprintActionsMenu)}
variant="plain"
aria-label="blueprint menu toggle"
variant='plain'
aria-label='blueprint menu toggle'
>
<EllipsisVIcon aria-hidden="true" />
<EllipsisVIcon aria-hidden='true' />
</MenuToggle>
)}
>
@ -81,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

@ -50,11 +50,21 @@ const BlueprintCard = ({ blueprint }: blueprintProps) => {
onChange: () => dispatch(setBlueprintId(blueprint.id)),
}}
>
<CardTitle>
<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

@ -34,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) {
@ -53,20 +53,20 @@ const BlueprintDiffModal = ({
/>
<ModalBody>
<DiffEditor
height="90vh"
language="json"
height='90vh'
language='json'
original={JSON.stringify(baseBlueprint, undefined, 2)}
modified={JSON.stringify(blueprint, undefined, 2)}
/>
</ModalBody>
<ModalFooter>
<BuildImagesButton key="build-button">
<BuildImagesButton key='build-button'>
Synchronize images
</BuildImagesButton>
<Button
key="cancel-button"
variant="link"
type="button"
key='cancel-button'
variant='link'
type='button'
onClick={onClose}
>
Cancel

View file

@ -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,
@ -17,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';
@ -29,6 +28,7 @@ import {
PAGINATION_LIMIT,
PAGINATION_OFFSET,
} from '../../constants';
import { useGetUser } from '../../Hooks';
import { useGetBlueprintsQuery } from '../../store/backendApi';
import {
selectBlueprintSearchInput,
@ -60,8 +60,8 @@ type emptyBlueprintStateProps = {
};
const BlueprintsSidebar = () => {
const [userData, setUserData] = useState<ChromeUser | void>(undefined);
const { analytics, auth } = useChrome();
const { userData } = useGetUser(auth);
const selectedBlueprintId = useAppSelector(selectSelectedBlueprintId);
const blueprintSearchInput = useAppSelector(selectBlueprintSearchInput);
@ -73,13 +73,6 @@ const BlueprintsSidebar = () => {
offset: blueprintsOffset,
};
useEffect(() => {
(async () => {
const data = await auth?.getUser();
setUserData(data);
})();
}, [auth]);
if (blueprintSearchInput) {
searchParams.search = blueprintSearchInput;
}
@ -97,7 +90,7 @@ const BlueprintsSidebar = () => {
if (isLoading) {
return (
<Bullseye>
<Spinner size="xl" />
<Spinner size='xl' />
</Bullseye>
);
}
@ -111,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.'
/>
);
}
@ -123,7 +116,7 @@ const BlueprintsSidebar = () => {
};
if (!process.env.IS_ON_PREMISE) {
const orgId = userData?.identity?.internal?.org_id;
const orgId = userData?.identity.internal?.org_id;
analytics.group(orgId, {
imagebuilder_blueprint_count: blueprintsData?.meta.count,
@ -144,7 +137,7 @@ const BlueprintsSidebar = () => {
<Flex justifyContent={{ default: 'justifyContentCenter' }}>
<FlexItem>
<Button
variant="link"
variant='link'
isDisabled={!selectedBlueprintId}
onClick={handleClickViewAll}
>
@ -160,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 &&
@ -191,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 () => {
@ -209,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`}
@ -223,7 +216,7 @@ const EmptyBlueprintState = ({
icon,
action,
}: emptyBlueprintStateProps) => (
<EmptyState headingLevel="h4" icon={icon} titleText={titleText} variant="sm">
<EmptyState headingLevel='h4' icon={icon} titleText={titleText} variant='sm'>
<EmptyStateBody>{bodyText}</EmptyStateBody>
<EmptyStateFooter>
<EmptyStateActions>{action}</EmptyStateActions>

View file

@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react';
import React, { useState } from 'react';
import {
Button,
@ -16,11 +16,13 @@ import {
} 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 { ChromeUser } from '@redhat-cloud-services/types';
import { skipToken } from '@reduxjs/toolkit/query';
import { AMPLITUDE_MODULE_NAME, targetOptions } from '../../constants';
import { useComposeBPWithNotification as useComposeBlueprintMutation } from '../../Hooks';
import {
useComposeBPWithNotification as useComposeBlueprintMutation,
useGetUser,
} from '../../Hooks';
import { useGetBlueprintQuery } from '../../store/backendApi';
import { selectSelectedBlueprintId } from '../../store/BlueprintSlice';
import { useAppSelector } from '../../store/hooks';
@ -37,15 +39,7 @@ export const BuildImagesButton = ({ children }: BuildImagesButtonPropTypes) => {
const { trigger: buildBlueprint, isLoading: imageBuildLoading } =
useComposeBlueprintMutation();
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) {
@ -53,15 +47,17 @@ export const BuildImagesButton = ({ children }: BuildImagesButtonPropTypes) => {
id: selectedBlueprintId,
body: {
image_types: blueprintImageType?.filter(
(target) => !deselectedTargets.includes(target)
(target) => !deselectedTargets.includes(target),
),
},
});
analytics.track(`${AMPLITUDE_MODULE_NAME} - Image Requested`, {
module: AMPLITUDE_MODULE_NAME,
trigger: 'synchronize images',
account_id: userData?.identity.internal?.account_id || 'Not found',
});
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',
});
}
}
};
const [isOpen, setIsOpen] = useState(false);
@ -69,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]);
@ -96,17 +92,17 @@ 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}
splitButtonItems={[
<MenuToggleAction
data-testid="blueprint-build-image-menu-option"
key="split-action"
data-testid='blueprint-build-image-menu-option'
key='split-action'
onClick={onBuildHandler}
id="wizard-build-image-btn"
id='wizard-build-image-btn'
isDisabled={
!selectedBlueprintId ||
deselectedTargets.length === blueprintImageType?.length
@ -122,7 +118,7 @@ export const BuildImagesButton = ({ children }: BuildImagesButtonPropTypes) => {
} as React.CSSProperties
}
isInline
size="md"
size='md'
/>
</FlexItem>
)}

View file

@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react';
import React from 'react';
import {
Button,
@ -9,14 +9,16 @@ import {
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,
PAGINATION_LIMIT,
PAGINATION_OFFSET,
} from '../../constants';
import { useDeleteBPWithNotification as useDeleteBlueprintMutation } from '../../Hooks';
import {
useDeleteBPWithNotification as useDeleteBlueprintMutation,
useGetUser,
} from '../../Hooks';
import { backendApi, useGetBlueprintsQuery } from '../../store/backendApi';
import {
selectBlueprintSearchInput,
@ -42,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,
@ -64,7 +59,7 @@ export const DeleteBlueprintModal: React.FunctionComponent<
selectFromResult: ({ data }) => ({
blueprintName: data?.data.find(
(blueprint: { id: string | undefined }) =>
blueprint.id === selectedBlueprintId
blueprint.id === selectedBlueprintId,
)?.name,
}),
});
@ -90,16 +85,16 @@ export const DeleteBlueprintModal: React.FunctionComponent<
};
return (
<Modal variant={ModalVariant.small} isOpen={isOpen} onClose={onDeleteClose}>
<ModalHeader title={'Delete blueprint?'} titleIconVariant="warning" />
<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}>
<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>
</ModalFooter>

View file

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

View file

@ -71,19 +71,19 @@ export const ImportBlueprintModal: React.FunctionComponent<
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 {
@ -98,7 +98,7 @@ export const ImportBlueprintModal: React.FunctionComponent<
repository as ApiRepositoryImportResponseRead;
if (contentSourcesRepo.uuid) {
newCustomRepos.push(
...mapToCustomRepositories(contentSourcesRepo)
...mapToCustomRepositories(contentSourcesRepo),
);
}
if (repository.warnings?.length === 0 && repository.url) {
@ -139,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);
@ -155,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 ?? [];
}
@ -175,7 +174,7 @@ export const ImportBlueprintModal: React.FunctionComponent<
undefined;
const importBlueprintState = mapExportRequestToState(
blueprintExportedResponse,
blueprintFromFile.image_requests || []
blueprintFromFile.image_requests || [],
);
setIsOnPrem(false);
@ -185,7 +184,7 @@ export const ImportBlueprintModal: React.FunctionComponent<
mapOnPremToHosted(blueprintFromFile);
const importBlueprintState = mapExportRequestToState(
blueprintFromFileMapped,
[]
[],
);
setIsOnPrem(true);
setImportedBlueprint(importBlueprintState);
@ -259,9 +258,9 @@ export const ImportBlueprintModal: React.FunctionComponent<
>
<Button
icon={<HelpIcon />}
variant="plain"
aria-label="About import"
className="pf-v6-u-pl-sm"
variant='plain'
aria-label='About import'
className='pf-v6-u-pl-sm'
isInline
/>
</Popover>
@ -270,23 +269,23 @@ export const ImportBlueprintModal: React.FunctionComponent<
/>
<ModalBody>
<Form>
<FormGroup fieldId="checkbox-import-custom-repositories">
<FormGroup fieldId='checkbox-import-custom-repositories'>
<Checkbox
label="Import missing custom repositories after file upload."
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"
aria-label='Import Custom Repositories checkbox'
id='checkbox-import-custom-repositories'
name='Import Repositories'
/>
</FormGroup>
<FormGroup fieldId="import-blueprint-file-upload">
<FormGroup fieldId='import-blueprint-file-upload'>
<FileUpload
id="import-blueprint-file-upload"
type="text"
id='import-blueprint-file-upload'
type='text'
value={fileContent}
filename={filename}
filenamePlaceholder="Drag and drop a file or upload one"
filenamePlaceholder='Drag and drop a file or upload one'
onFileInputChange={handleFileInputChange}
onDataChange={handleDataChange}
onReadStarted={handleFileReadStarted}
@ -294,7 +293,7 @@ export const ImportBlueprintModal: React.FunctionComponent<
onClearClick={handleClear}
isLoading={isLoading}
isReadOnly={true}
browseButtonText="Upload"
browseButtonText='Upload'
dropzoneProps={{
accept: { 'text/json': ['.json'], 'text/plain': ['.toml'] },
maxSize: 512000,
@ -308,10 +307,10 @@ export const ImportBlueprintModal: React.FunctionComponent<
{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.'}
? '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>
@ -320,7 +319,7 @@ export const ImportBlueprintModal: React.FunctionComponent<
</ModalBody>
<ModalFooter>
<Button
type="button"
type='button'
isDisabled={isRejected || isInvalidFormat || !fileContent}
onClick={() =>
navigate(resolveRelPath(`imagewizard/import`), {
@ -330,7 +329,7 @@ export const ImportBlueprintModal: React.FunctionComponent<
>
Review and finish
</Button>
<Button variant="link" type="button" onClick={onImportClose}>
<Button variant='link' type='button' onClick={onImportClose}>
Cancel
</Button>
</ModalFooter>

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

@ -1,4 +1,4 @@
import React, { useState } from 'react';
import React from 'react';
import {
Button,
@ -38,19 +38,19 @@ type ToggleGroupProps = Omit<FormGroupProps<boolean>, 'isDisabled'>;
const AWSConfigToggle = ({ value, onChange }: ToggleGroupProps) => {
const handleChange = (
_event: React.FormEvent<HTMLInputElement>,
checked: boolean
checked: boolean,
) => {
onChange(checked);
};
return (
<FormGroup label="Configure AWS Uploads">
<FormGroup label='Configure AWS Uploads'>
<Switch
id="aws-config-switch"
ouiaId="aws-config-switch"
aria-label="aws-config-switch"
id='aws-config-switch'
ouiaId='aws-config-switch'
aria-label='aws-config-switch'
// empty label so there is no icon
label=""
label=''
isChecked={value}
onChange={handleChange}
/>
@ -79,19 +79,19 @@ const AWSBucket = ({ value, onChange, isDisabled }: FormGroupProps<string>) => {
if (isDisabled) {
return (
<DisabledInputGroup label={label} value={value} ariaLabel="aws-bucket" />
<DisabledInputGroup label={label} value={value} ariaLabel='aws-bucket' />
);
}
return (
<FormGroup label={label}>
<ValidatedInput
placeholder="AWS bucket"
ariaLabel="aws-bucket"
placeholder='AWS bucket'
ariaLabel='aws-bucket'
value={value || ''}
validator={isAwsBucketValid}
onChange={(_event, value) => onChange(value)}
helperText="Invalid AWS bucket name"
helperText='Invalid AWS bucket name'
/>
</FormGroup>
);
@ -100,7 +100,7 @@ const AWSBucket = ({ value, onChange, isDisabled }: FormGroupProps<string>) => {
const CredsPathPopover = () => {
return (
<Popover
minWidth="35rem"
minWidth='35rem'
headerContent={'What is the AWS Credentials Path?'}
bodyContent={
<Content>
@ -115,9 +115,9 @@ const CredsPathPopover = () => {
>
<Button
icon={<HelpIcon />}
variant="plain"
aria-label="Credentials Path Info"
className="pf-v6-u-pl-sm header-button"
variant='plain'
aria-label='Credentials Path Info'
className='pf-v6-u-pl-sm header-button'
/>
</Popover>
);
@ -139,7 +139,7 @@ const AWSCredsPath = ({
<DisabledInputGroup
value={value}
label={label}
ariaLabel="aws-creds-path"
ariaLabel='aws-creds-path'
/>
);
}
@ -147,30 +147,35 @@ const AWSCredsPath = ({
return (
<FormGroup label={label}>
<ValidatedInput
placeholder="Path to AWS credentials"
ariaLabel="aws-creds-path"
placeholder='Path to AWS credentials'
ariaLabel='aws-creds-path'
value={value || ''}
validator={isAwsCredsPathValid}
onChange={(_event, value) => onChange(value)}
helperText="Invalid filepath for AWS credentials"
helperText='Invalid filepath for AWS credentials'
/>
</FormGroup>
);
};
type AWSConfigProps = {
isEnabled: boolean;
enabled: boolean;
setEnabled: (enabled: boolean) => void;
reinit: (config: AWSWorkerConfig | undefined) => void;
refetch: () => Promise<{
data?: WorkerConfigResponse | undefined;
}>;
};
export const AWSConfig = ({ isEnabled, refetch, reinit }: AWSConfigProps) => {
export const AWSConfig = ({
enabled,
setEnabled,
refetch,
reinit,
}: AWSConfigProps) => {
const dispatch = useAppDispatch();
const bucket = useAppSelector(selectAWSBucketName);
const credentials = useAppSelector(selectAWSCredsPath);
const [enabled, setEnabled] = useState<boolean>(isEnabled);
const onToggle = async (v: boolean) => {
if (v) {

View file

@ -1,4 +1,9 @@
import React, { MouseEventHandler, useCallback, useEffect } from 'react';
import React, {
MouseEventHandler,
useCallback,
useEffect,
useState,
} from 'react';
import {
Button,
@ -43,9 +48,9 @@ const ConfigError = ({
<EmptyState
variant={EmptyStateVariant.xl}
icon={ExclamationIcon}
color="#C9190B"
color='#C9190B'
>
<Title headingLevel="h4" size="lg">
<Title headingLevel='h4' size='lg'>
Error
</Title>
<EmptyStateBody>
@ -54,7 +59,7 @@ const ConfigError = ({
</EmptyStateBody>
<EmptyStateFooter>
<EmptyStateActions>
<Button variant="primary" onClick={onClose}>
<Button variant='primary' onClick={onClose}>
Go back
</Button>
</EmptyStateActions>
@ -68,6 +73,7 @@ export const CloudProviderConfig = () => {
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({});
@ -76,9 +82,12 @@ export const CloudProviderConfig = () => {
(config: AWSWorkerConfig | undefined) => {
if (!config) {
dispatch(reinitializeAWSConfig());
setEnabled(false);
return;
}
setEnabled(true);
const { bucket, credentials } = config;
if (bucket && bucket !== '') {
dispatch(changeAWSBucketName(bucket));
@ -88,7 +97,7 @@ export const CloudProviderConfig = () => {
dispatch(changeAWSCredsPath(credentials));
}
},
[dispatch]
[dispatch, setEnabled],
);
useEffect(() => {
@ -109,8 +118,8 @@ export const CloudProviderConfig = () => {
<PageSection>
<Wizard onClose={handleClose}>
<WizardStep
name="AWS Config"
id="aws-config"
name='AWS Config'
id='aws-config'
footer={{
nextButtonText: 'Submit',
isNextDisabled: !isAwsStepValid(config),
@ -126,7 +135,8 @@ export const CloudProviderConfig = () => {
<AWSConfig
refetch={refetch}
reinit={initAWSConfig}
isEnabled={!!(data?.aws && Object.keys(data.aws).length > 0)}
enabled={enabled}
setEnabled={setEnabled}
/>
</WizardStep>
</Wizard>

View file

@ -21,7 +21,7 @@ export const isAwsCredsPathValid = (credsPath?: string): boolean => {
};
export const isAwsStepValid = (
config: AWSWorkerConfig | undefined
config: AWSWorkerConfig | undefined,
): boolean => {
if (!config) {
return true;

View file

@ -13,7 +13,7 @@ import cockpit from 'cockpit';
export const NotReady = ({ enabled }: { enabled: boolean }) => {
return (
<EmptyState
headingLevel="h4"
headingLevel='h4'
icon={CubesIcon}
titleText={`OSBuild Composer is not ${enabled ? 'started' : 'enabled'}`}
variant={EmptyStateVariant.xl}
@ -21,7 +21,7 @@ export const NotReady = ({ enabled }: { enabled: boolean }) => {
<EmptyStateFooter>
<EmptyStateActions>
<Button
variant="primary"
variant='primary'
onClick={(event) => {
event.preventDefault();
cockpit
@ -30,7 +30,7 @@ export const NotReady = ({ enabled }: { enabled: boolean }) => {
{
superuser: 'require',
err: 'message',
}
},
)
.then(() => window.location.reload());
}}
@ -40,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

@ -10,9 +10,9 @@ import { LockIcon } from '@patternfly/react-icons';
export const RequireAdmin = () => {
return (
<EmptyState
headingLevel="h4"
headingLevel='h4'
icon={LockIcon}
titleText="Access is limited."
titleText='Access is limited.'
variant={EmptyStateVariant.xl}
>
<EmptyStateBody>

View file

@ -15,6 +15,7 @@ import { WizardStepType } from '@patternfly/react-core/dist/esm/components/Wizar
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/components/FileSystemTable';
@ -40,6 +41,7 @@ import UsersStep from './steps/Users';
import { getHostArch, getHostDistro } from './utilities/getHostInfo';
import { useHasSpecificTargetOnly } from './utilities/hasSpecificTargetOnly';
import {
useAAPValidation,
useDetailsValidation,
useFilesystemValidation,
useFirewallValidation,
@ -65,7 +67,6 @@ import {
AARCH64,
AMPLITUDE_MODULE_NAME,
RHEL_10,
RHEL_10_BETA,
RHEL_8,
RHEL_9,
} from '../../constants';
@ -74,14 +75,13 @@ import './CreateImageWizard.scss';
import {
addImageType,
changeArchitecture,
changeAwsShareMethod,
changeDistribution,
initializeWizard,
selectAwsAccountId,
selectAwsShareMethod,
selectAwsSourceId,
selectAzureResourceGroup,
selectAzureShareMethod,
selectAzureSource,
selectAzureSubscriptionId,
selectAzureTenantId,
selectDistribution,
@ -118,7 +118,7 @@ export const CustomWizardFooter = ({
<WizardFooterWrapper>
<Flex columnGap={{ default: 'columnGapSm' }}>
<Button
variant="primary"
variant='primary'
onClick={() => {
if (!process.env.IS_ON_PREMISE) {
analytics.track(`${AMPLITUDE_MODULE_NAME} - Button Clicked`, {
@ -134,7 +134,7 @@ export const CustomWizardFooter = ({
Next
</Button>
<Button
variant="secondary"
variant='secondary'
onClick={() => {
if (!process.env.IS_ON_PREMISE) {
analytics.track(`${AMPLITUDE_MODULE_NAME} - Button Clicked`, {
@ -151,7 +151,7 @@ export const CustomWizardFooter = ({
</Button>
{optional && (
<Button
variant="tertiary"
variant='tertiary'
onClick={() => {
if (!process.env.IS_ON_PREMISE) {
analytics.track(`${AMPLITUDE_MODULE_NAME} - Button Clicked`, {
@ -168,7 +168,7 @@ export const CustomWizardFooter = ({
</Button>
)}
<Button
variant="link"
variant='link'
onClick={() => {
if (!process.env.IS_ON_PREMISE) {
analytics.track(`${AMPLITUDE_MODULE_NAME} - Button Clicked`, {
@ -199,6 +199,7 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
// Feature flags
const complianceEnabled = useFlag('image-builder.compliance.enabled');
const isAAPRegistrationEnabled = useFlag('image-builder.aap.enabled');
// IMPORTANT: Ensure the wizard starts with a fresh initial state
useEffect(() => {
@ -209,9 +210,6 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
if (searchParams.get('release') === 'rhel9') {
dispatch(changeDistribution(RHEL_9));
}
if (searchParams.get('release') === 'rhel10beta') {
dispatch(changeDistribution(RHEL_10_BETA));
}
if (searchParams.get('release') === 'rhel10') {
dispatch(changeDistribution(RHEL_10));
}
@ -235,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();
@ -262,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
@ -286,6 +286,8 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
const firewallValidation = useFirewallValidation();
// Services
const servicesValidation = useServicesValidation();
// AAP
const aapValidation = useAAPValidation();
// Firstboot
const firstBootValidation = useFirstBootValidation();
// Details
@ -296,8 +298,10 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
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);
@ -308,7 +312,7 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
step: WizardStepType,
activeStep: WizardStepType,
steps: WizardStepType[],
goToStepByIndex: (index: number) => void
goToStepByIndex: (index: number) => void,
) => {
const isVisitOptional =
'parentId' in step && step.parentId === 'step-optional-steps';
@ -324,7 +328,7 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
}, [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
@ -352,7 +356,7 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
{
module: AMPLITUDE_MODULE_NAME,
isPreview: isBeta(),
}
},
);
}
}}
@ -371,8 +375,8 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
isVisitRequired
>
<WizardStep
name="Image output"
id="step-image-output"
name='Image output'
id='step-image-output'
footer={
<CustomWizardFooter
disableNext={targetEnvironments.length === 0}
@ -383,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: string) =>
target === 'aws' || target === 'gcp' || target === 'azure'
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
}
/>
}
@ -410,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={
@ -426,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)
}
/>
}
@ -451,13 +453,13 @@ 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}
status={
@ -478,9 +480,8 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
</WizardStep>,
<WizardStep
name={complianceEnabled ? 'Compliance' : 'OpenSCAP'}
id="step-oscap"
key="step-oscap"
isHidden={distribution === RHEL_10_BETA}
id='step-oscap'
key='step-oscap'
navItem={CustomStatusNavItem}
footer={
<CustomWizardFooter disableNext={false} optional={true} />
@ -489,9 +490,9 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
<OscapStep />
</WizardStep>,
<WizardStep
name="File system configuration"
id="step-file-system"
key="step-file-system"
name='File system configuration'
id='step-file-system'
key='step-file-system'
navItem={CustomStatusNavItem}
isHidden={hasWslTargetOnly}
footer={
@ -515,14 +516,12 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
</FileSystemContext.Provider>
</WizardStep>,
<WizardStep
name="Repeatable build"
id="wizard-repository-snapshot"
key="wizard-repository-snapshot"
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
}
isHidden={!!process.env.IS_ON_PREMISE}
footer={
<CustomWizardFooter
disableNext={snapshotValidation.disabledNext}
@ -533,13 +532,11 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
<SnapshotStep />
</WizardStep>,
<WizardStep
name="Custom repositories"
id="wizard-custom-repositories"
key="wizard-custom-repositories"
name='Custom repositories'
id='wizard-custom-repositories'
key='wizard-custom-repositories'
navItem={CustomStatusNavItem}
isHidden={
distribution === RHEL_10_BETA || !!process.env.IS_ON_PREMISE
}
isHidden={!!process.env.IS_ON_PREMISE}
isDisabled={snapshotValidation.disabledNext}
footer={
<CustomWizardFooter disableNext={false} optional={true} />
@ -548,9 +545,9 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
<RepositoriesStep />
</WizardStep>,
<WizardStep
name="Additional packages"
id="wizard-additional-packages"
key="wizard-additional-packages"
name='Additional packages'
id='wizard-additional-packages'
key='wizard-additional-packages'
navItem={CustomStatusNavItem}
isDisabled={snapshotValidation.disabledNext}
footer={
@ -560,9 +557,9 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
<PackagesStep />
</WizardStep>,
<WizardStep
name="Users"
id="wizard-users"
key="wizard-users"
name='Users'
id='wizard-users'
key='wizard-users'
navItem={CustomStatusNavItem}
status={usersValidation.disabledNext ? 'error' : 'default'}
footer={
@ -575,9 +572,9 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
<UsersStep />
</WizardStep>,
<WizardStep
name="Timezone"
id="wizard-timezone"
key="wizard-timezone"
name='Timezone'
id='wizard-timezone'
key='wizard-timezone'
navItem={CustomStatusNavItem}
status={timezoneValidation.disabledNext ? 'error' : 'default'}
footer={
@ -590,9 +587,9 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
<TimezoneStep />
</WizardStep>,
<WizardStep
name="Locale"
id="wizard-locale"
key="wizard-locale"
name='Locale'
id='wizard-locale'
key='wizard-locale'
navItem={CustomStatusNavItem}
status={localeValidation.disabledNext ? 'error' : 'default'}
footer={
@ -605,9 +602,9 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
<LocaleStep />
</WizardStep>,
<WizardStep
name="Hostname"
id="wizard-hostname"
key="wizard-hostname"
name='Hostname'
id='wizard-hostname'
key='wizard-hostname'
navItem={CustomStatusNavItem}
status={hostnameValidation.disabledNext ? 'error' : 'default'}
footer={
@ -620,9 +617,9 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
<HostnameStep />
</WizardStep>,
<WizardStep
name="Kernel"
id="wizard-kernel"
key="wizard-kernel"
name='Kernel'
id='wizard-kernel'
key='wizard-kernel'
navItem={CustomStatusNavItem}
isHidden={hasWslTargetOnly}
status={kernelValidation.disabledNext ? 'error' : 'default'}
@ -636,9 +633,9 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
<KernelStep />
</WizardStep>,
<WizardStep
name="Firewall"
id="wizard-firewall"
key="wizard-firewall"
name='Firewall'
id='wizard-firewall'
key='wizard-firewall'
navItem={CustomStatusNavItem}
status={firewallValidation.disabledNext ? 'error' : 'default'}
footer={
@ -651,9 +648,9 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
<FirewallStep />
</WizardStep>,
<WizardStep
name="Systemd services"
id="wizard-services"
key="wizard-services"
name='Systemd services'
id='wizard-services'
key='wizard-services'
navItem={CustomStatusNavItem}
status={servicesValidation.disabledNext ? 'error' : 'default'}
footer={
@ -666,9 +663,25 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
<ServicesStep />
</WizardStep>,
<WizardStep
name="First boot script configuration"
id="wizard-first-boot"
key="wizard-first-boot"
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}
@ -684,8 +697,8 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
]}
/>
<WizardStep
name="Details"
id="step-details"
name='Details'
id='step-details'
navItem={CustomStatusNavItem}
status={detailsValidation.disabledNext ? 'error' : 'default'}
footer={
@ -697,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

@ -15,7 +15,7 @@ const ImportImageWizard = () => {
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));

View file

@ -58,7 +58,7 @@ const LabelInput = ({
const onTextInputChange = (
_event: React.FormEvent<HTMLInputElement>,
value: string
value: string,
) => {
setInputValue(value);
setOnStepInputErrorText('');
@ -74,27 +74,27 @@ const LabelInput = ({
switch (fieldName) {
case 'ports':
setOnStepInputErrorText(
'Expected format: <port/port-name>:<protocol>. Example: 8080:tcp, ssh:tcp'
'Expected format: <port/port-name>:<protocol>. Example: 8080:tcp, ssh:tcp',
);
break;
case 'kernelAppend':
setOnStepInputErrorText(
'Expected format: <kernel-argument>. Example: console=tty0'
'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'
'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'
'Expected format: <group-name>. Example: admin',
);
break;
case 'ntpServers':
setOnStepInputErrorText(
'Expected format: <ntp-server>. Example: time.redhat.com'
'Expected format: <ntp-server>. Example: time.redhat.com',
);
break;
case 'enabledSystemdServices':
@ -103,7 +103,7 @@ const LabelInput = ({
case 'disabledServices':
case 'enabledServices':
setOnStepInputErrorText(
'Expected format: <service-name>. Example: sshd'
'Expected format: <service-name>. Example: sshd',
);
break;
default:
@ -154,21 +154,21 @@ const LabelInput = ({
<TextInputGroupUtilities>
<Button
icon={
<Icon status="info">
<Icon status='info'>
<PlusCircleIcon />
</Icon>
}
variant="plain"
variant='plain'
onClick={(e) => handleAddItem(e, inputValue)}
isDisabled={!inputValue}
aria-label={ariaLabel}
/>
<Button
icon={<TimesIcon />}
variant="plain"
variant='plain'
onClick={handleClear}
isDisabled={!inputValue}
aria-label="Clear input"
aria-label='Clear input'
/>
</TextInputGroupUtilities>
</TextInputGroup>
@ -185,7 +185,7 @@ const LabelInput = ({
<LabelGroup
categoryName={requiredCategoryName}
numLabels={20}
className="pf-v6-u-mt-sm pf-v6-u-w-100"
className='pf-v6-u-mt-sm pf-v6-u-w-100'
>
{requiredList.map((item) => (
<Label key={item} isCompact>
@ -194,7 +194,7 @@ const LabelInput = ({
))}
</LabelGroup>
)}
<LabelGroup numLabels={20} className="pf-v6-u-mt-sm pf-v6-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,14 +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">{warning}</HelperTextItem>
<HelperTextItem variant='warning'>{warning}</HelperTextItem>
</HelperText>
)}
{validated === 'error' && hasError && (
@ -111,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;
};
@ -125,7 +125,7 @@ const getValidationState = (
export const ErrorMessage = ({ errorMessage }: ErrorMessageProps) => {
return (
<HelperText>
<HelperTextItem variant="error">{errorMessage}</HelperTextItem>
<HelperTextItem variant='error'>{errorMessage}</HelperTextItem>
</HelperText>
);
};
@ -138,6 +138,7 @@ export const ValidatedInput = ({
value,
placeholder,
onChange,
...props
}: ValidatedTextInputPropTypes) => {
const [isPristine, setIsPristine] = useState(!value ? true : false);
@ -158,16 +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">{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

@ -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,7 +45,7 @@ const DetailsStep = () => {
return (
<Form>
<Title headingLevel="h1" size="xl">
<Title headingLevel='h1' size='xl'>
Details
</Title>
<Content>
@ -53,15 +53,15 @@ const DetailsStep = () => {
images created from this blueprint will use the name of the parent
blueprint.
</Content>
<FormGroup isRequired label="Blueprint name" fieldId="blueprint-name">
<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>

View file

@ -16,13 +16,13 @@ const FileSystemAutomaticPartition = () => {
current supported configuration layout.
<br></br>
<Button
component="a"
target="_blank"
variant="link"
component='a'
target='_blank'
variant='link'
icon={<ExternalLinkAltIcon />}
iconPosition="right"
iconPosition='right'
href={FILE_SYSTEM_CUSTOMIZATION_URL}
className="pf-v6-u-pl-0"
className='pf-v6-u-pl-0'
>
Customizing file systems during the image creation
</Button>

View file

@ -38,7 +38,7 @@ const FileSystemConfiguration = () => {
mountpoint: '/home',
min_size: '1',
unit: 'GiB',
})
}),
);
};
@ -49,7 +49,7 @@ const FileSystemConfiguration = () => {
const filteredTargets = (
automaticPartitioningOnlyTargets.filter((env) =>
environments.includes(env)
environments.includes(env),
) as ImageTypes[]
).map((env) => targetOptions[env]);
@ -58,8 +58,8 @@ const FileSystemConfiguration = () => {
<Content>
<Content component={ContentVariants.h3}>Configure partitions</Content>
</Content>
{partitions?.find((partition) =>
partition?.mountpoint?.includes('/usr')
{partitions.find((partition) =>
partition.mountpoint.includes('/usr'),
) && <UsrSubDirectoriesDisabled />}
<Content>
<Content>
@ -72,13 +72,13 @@ const FileSystemConfiguration = () => {
order to conform to best practices and ensure functionality.
<br></br>
<Button
component="a"
target="_blank"
variant="link"
component='a'
target='_blank'
variant='link'
icon={<ExternalLinkAltIcon />}
iconPosition="right"
iconPosition='right'
href={FILE_SYSTEM_CUSTOMIZATION_URL}
className="pf-v6-u-pl-0"
className='pf-v6-u-pl-0'
>
Read more about manual configuration here
</Button>
@ -87,18 +87,18 @@ const FileSystemConfiguration = () => {
{(environments.includes('image-installer') ||
environments.includes('wsl')) && (
<Alert
variant="warning"
variant='warning'
isInline
title={`Filesystem customizations are not applied to ${filteredTargets.join(
' and '
' and ',
)} images`}
/>
)}
<FileSystemTable />
<Content>
<Button
className="pf-v6-u-text-align-left"
variant="link"
className='pf-v6-u-text-align-left'
variant='link'
icon={<PlusCircleIcon />}
onClick={handleAddPartition}
>

View file

@ -12,7 +12,7 @@ import {
const FileSystemPartition = () => {
const dispatch = useAppDispatch();
const fileSystemConfigurationType = useAppSelector(
selectFileSystemConfigurationType
selectFileSystemConfigurationType,
);
const hasOscapProfile = useAppSelector(selectComplianceProfileID);
@ -23,27 +23,27 @@ const FileSystemPartition = () => {
return (
<FormGroup>
<Radio
id="automatic file system config radio"
id='automatic file system config radio'
label={
<>
<Label isCompact color="blue">
<Label isCompact color='blue'>
Recommended
</Label>{' '}
Use automatic partitioning
</>
}
name="sc-radio-automatic"
description="Automatically partition your image to what is best, depending on the target environment(s)"
name='sc-radio-automatic'
description='Automatically partition your image to what is best, depending on the target environment(s)'
isChecked={fileSystemConfigurationType === 'automatic'}
onChange={() => {
dispatch(changeFileSystemConfigurationType('automatic'));
}}
/>
<Radio
id="manual file system config radio"
label="Manually configure partitions"
name="fsc-radio-manual"
description="Manually configure the file system of your image by adding, removing, and editing partitions"
id='manual file system config radio'
label='Manually configure partitions'
name='fsc-radio-manual'
description='Manually configure the file system of your image by adding, removing, and editing partitions'
isChecked={fileSystemConfigurationType === 'manual'}
onChange={() => {
dispatch(changeFileSystemConfigurationType('manual'));

View file

@ -43,7 +43,7 @@ export const FileSystemContext = React.createContext<boolean>(true);
export const MinimumSizePopover = () => {
return (
<Popover
maxWidth="30rem"
maxWidth='30rem'
bodyContent={
<Content>
<Content>
@ -55,10 +55,10 @@ export const MinimumSizePopover = () => {
>
<Button
icon={<HelpIcon />}
variant="plain"
aria-label="File system configuration info"
aria-describedby="file-system-configuration-info"
className="popover-button pf-v6-u-p-0"
variant='plain'
aria-label='File system configuration info'
aria-describedby='file-system-configuration-info'
className='popover-button pf-v6-u-p-0'
/>
</Popover>
);
@ -112,11 +112,11 @@ const Row = ({ partition, onDragEnd, onDragStart, onDrop }: RowPropTypes) => {
id: `draggable-row-${partition.id}`,
}}
/>
<Td className="pf-m-width-20">
<Td className='pf-m-width-20'>
<MountpointPrefix partition={partition} />
{!isPristine && stepValidation.errors[`mountpoint-${partition.id}`] && (
<Alert
variant="danger"
variant='danger'
isInline
isPlain
title={stepValidation.errors[`mountpoint-${partition.id}`]}
@ -142,7 +142,7 @@ const Row = ({ partition, onDragEnd, onDragStart, onDrop }: RowPropTypes) => {
</Td>
<Td width={10}>
<Button
variant="link"
variant='link'
icon={<MinusCircleIcon />}
onClick={() => handleRemovePartition(partition.id)}
isDisabled={partition.mountpoint === '/'}
@ -180,7 +180,7 @@ const MountpointPrefix = ({ partition }: MountpointPrefixPropTypes) => {
setIsOpen(false);
const mountpoint = selection + (suffix.length > 0 ? '/' + suffix : '');
dispatch(
changePartitionMountpoint({ id: partition.id, mountpoint: mountpoint })
changePartitionMountpoint({ id: partition.id, mountpoint: mountpoint }),
);
};
@ -234,17 +234,17 @@ const MountpointSuffix = ({ partition }: MountpointSuffixPropTypes) => {
return (
<TextInput
value={suffix}
type="text"
type='text'
onChange={(event: React.FormEvent, newValue) => {
const mountpoint = prefix + normalizeSuffix(newValue);
dispatch(
changePartitionMountpoint({
id: partition.id,
mountpoint: mountpoint,
})
}),
);
}}
aria-label="mountpoint suffix"
aria-label='mountpoint suffix'
/>
);
};
@ -274,7 +274,7 @@ const MinimumSize = ({ partition }: MinimumSizePropTypes) => {
return (
<ValidatedInputAndTextArea
ariaLabel="minimum partition size"
ariaLabel='minimum partition size'
value={partition.min_size}
isDisabled={partition.unit === 'B'}
warning={
@ -282,20 +282,20 @@ const MinimumSize = ({ partition }: MinimumSizePropTypes) => {
? 'The Wizard only supports KiB, MiB, or GiB. Adjust or keep the current value.'
: ''
}
type="text"
type='text'
stepValidation={stepValidation}
fieldName={`min-size-${partition.id}`}
placeholder="File system"
placeholder='File system'
onChange={(event, minSize) => {
if (minSize === '' || /^\d+$/.test(minSize)) {
dispatch(
changePartitionMinSize({
id: partition.id,
min_size: minSize,
})
}),
);
dispatch(
changePartitionUnit({ id: partition.id, unit: partition.unit })
changePartitionUnit({ id: partition.id, unit: partition.unit }),
);
}
}}
@ -319,7 +319,7 @@ const SizeUnit = ({ partition }: SizeUnitPropTypes) => {
changePartitionMinSize({
id: partition.id,
min_size: initialValue.min_size,
})
}),
);
}
dispatch(changePartitionUnit({ id: partition.id, unit: selection }));
@ -364,7 +364,7 @@ const SizeUnit = ({ partition }: SizeUnitPropTypes) => {
const FileSystemTable = () => {
const [draggedItemId, setDraggedItemId] = useState<string | null>(null);
const [draggingToItemIndex, setDraggingToItemIndex] = useState<number | null>(
null
null,
);
const [isDragging, setIsDragging] = useState(false);
const [tempItemOrder, setTempItemOrder] = useState<string[]>([]);
@ -374,7 +374,7 @@ const FileSystemTable = () => {
const itemOrder = partitions.map((partition) => partition.id);
const dispatch = useAppDispatch();
const isValidDrop = (
evt: React.DragEvent<HTMLTableSectionElement | HTMLTableRowElement>
evt: React.DragEvent<HTMLTableSectionElement | HTMLTableRowElement>,
) => {
const ulRect = bodyRef.current?.getBoundingClientRect();
if (!ulRect) return false;
@ -439,13 +439,13 @@ const FileSystemTable = () => {
}
const dragId = curListItem.id;
const newDraggingToItemIndex = Array.from(
bodyRef.current.children
bodyRef.current.children,
).findIndex((item) => item.id === dragId);
if (newDraggingToItemIndex !== draggingToItemIndex && draggedItemId) {
const tempItemOrder = moveItem(
[...itemOrder],
draggedItemId,
newDraggingToItemIndex
newDraggingToItemIndex,
);
move(tempItemOrder);
setDraggingToItemIndex(newDraggingToItemIndex);
@ -497,20 +497,20 @@ const FileSystemTable = () => {
return (
<Table
className={isDragging ? styles.modifiers.dragOver : ''}
aria-label="File system table"
variant="compact"
aria-label='File system table'
variant='compact'
>
<Thead>
<Tr>
<Th aria-label="Drag mount point" />
<Th aria-label='Drag mount point' />
<Th>Mount point</Th>
<Th aria-label="Suffix">Suffix</Th>
<Th aria-label='Suffix'>Suffix</Th>
<Th>Type</Th>
<Th>
Minimum size <MinimumSizePopover />
</Th>
<Th aria-label="Unit">Unit</Th>
<Th aria-label="Remove mount point" />
<Th aria-label='Unit'>Unit</Th>
<Th aria-label='Remove mount point' />
</Tr>
</Thead>
@ -520,7 +520,7 @@ const FileSystemTable = () => {
onDragLeave={onDragLeave}
ref={bodyRef}
>
{partitions &&
{partitions.length > 0 &&
partitions.map((partition) => (
<Row
onDrop={onDrop}

View file

@ -9,17 +9,18 @@ import FileSystemPartition from './components/FileSystemPartition';
import { useAppSelector } from '../../../../store/hooks';
import { selectFileSystemConfigurationType } from '../../../../store/wizardSlice';
import { useHasSpecificTargetOnly } from '../../utilities/hasSpecificTargetOnly';
export type FileSystemConfigurationType = 'automatic' | 'manual';
const FileSystemStep = () => {
const fileSystemConfigurationType = useAppSelector(
selectFileSystemConfigurationType
selectFileSystemConfigurationType,
);
const hasIsoTargetOnly = useHasSpecificTargetOnly('image-installer');
return (
<Form>
<Title headingLevel="h1" size="xl">
<Title headingLevel='h1' size='xl'>
File system configuration
</Title>
<Content>Define the partitioning of the image.</Content>
@ -30,13 +31,11 @@ const FileSystemStep = () => {
<FileSystemPartition />
<FileSystemAutomaticPartition />
</>
) : fileSystemConfigurationType === 'manual' ? (
) : (
<>
<FileSystemPartition />
<FileSystemConfiguration />
</>
) : (
fileSystemConfigurationType === 'oscap' && <FileSystemConfiguration />
)}
</Form>
);

View file

@ -18,17 +18,17 @@ const PortsInput = () => {
const stepValidation = useFirewallValidation();
return (
<FormGroup label="Ports">
<FormGroup label='Ports'>
<LabelInput
ariaLabel="Add ports"
placeholder="Add ports"
ariaLabel='Add ports'
placeholder='Add ports'
validator={isPortValid}
list={ports}
item="Port"
item='Port'
addAction={addPort}
removeAction={removePort}
stepValidation={stepValidation}
fieldName="ports"
fieldName='ports'
/>
</FormGroup>
);

View file

@ -22,30 +22,30 @@ const Services = () => {
return (
<>
<FormGroup label="Enabled services">
<FormGroup label='Enabled services'>
<LabelInput
ariaLabel="Add enabled service"
placeholder="Add enabled service"
ariaLabel='Add enabled service'
placeholder='Add enabled service'
validator={isServiceValid}
list={enabledServices}
item="Enabled service"
item='Enabled service'
addAction={addEnabledFirewallService}
removeAction={removeEnabledFirewallService}
stepValidation={stepValidation}
fieldName="enabledServices"
fieldName='enabledServices'
/>
</FormGroup>
<FormGroup label="Disabled services">
<FormGroup label='Disabled services'>
<LabelInput
ariaLabel="Add disabled service"
placeholder="Add disabled service"
ariaLabel='Add disabled service'
placeholder='Add disabled service'
validator={isServiceValid}
list={disabledServices}
item="Disabled service"
item='Disabled service'
addAction={addDisabledFirewallService}
removeAction={removeDisabledFirewallService}
stepValidation={stepValidation}
fieldName="disabledServices"
fieldName='disabledServices'
/>
</FormGroup>
</>

View file

@ -8,7 +8,7 @@ import Services from './components/Services';
const FirewallStep = () => {
return (
<Form>
<Title headingLevel="h1" size="xl">
<Title headingLevel='h1' size='xl'>
Firewall
</Title>
<Content>Customize firewall settings for your image.</Content>

View file

@ -53,7 +53,7 @@ const FirstBootStep = () => {
return (
<Form>
<Title headingLevel="h1" size="xl">
<Title headingLevel='h1' size='xl'>
First boot configuration
</Title>
<Content>
@ -61,10 +61,10 @@ const FirstBootStep = () => {
boot.
</Content>
<Alert
variant="warning"
variant='warning'
isExpandable
isInline
title="Important: please do not include sensitive information"
title='Important: please do not include sensitive information'
>
<Content>
Please ensure that your script does not contain any secrets,
@ -90,14 +90,14 @@ const FirstBootStep = () => {
dispatch(setFirstBootScript(code.replace('\r\n', '\n')));
}}
code={selectedScript}
height="35vh"
emptyStateButton="Browse"
emptyStateLink="Start from scratch"
height='35vh'
emptyStateButton='Browse'
emptyStateLink='Start from scratch'
/>
{errors.script && (
<FormHelperText>
<HelperText>
<HelperTextItem variant="error">{errors.script}</HelperTextItem>
<HelperTextItem variant='error'>{errors.script}</HelperTextItem>
</HelperText>
</FormHelperText>
)}

View file

@ -21,14 +21,14 @@ const HostnameInput = () => {
};
return (
<FormGroup label="Hostname">
<FormGroup label='Hostname'>
<ValidatedInputAndTextArea
ariaLabel="hostname input"
ariaLabel='hostname input'
value={hostname}
onChange={handleChange}
placeholder="Add a hostname"
placeholder='Add a hostname'
stepValidation={stepValidation}
fieldName="hostname"
fieldName='hostname'
/>
</FormGroup>
);

View file

@ -7,7 +7,7 @@ import HostnameInput from './components/HostnameInput';
const HostnameStep = () => {
return (
<Form>
<Title headingLevel="h1" size="xl">
<Title headingLevel='h1' size='xl'>
Hostname
</Title>
<Content>Select a hostname for your image.</Content>

View file

@ -24,7 +24,7 @@ const ArchSelect = () => {
const setArch = (
_event: React.MouseEvent,
selection: ImageRequest['architecture']
selection: ImageRequest['architecture'],
) => {
dispatch(changeArchitecture(selection));
setIsOpen(false);
@ -44,7 +44,7 @@ const ArchSelect = () => {
options.push(
<SelectOption key={arch} value={arch}>
{arch}
</SelectOption>
</SelectOption>,
);
});
@ -60,14 +60,14 @@ const ArchSelect = () => {
ref={toggleRef}
onClick={onToggleClick}
isExpanded={isOpen}
data-testid="arch_select"
data-testid='arch_select'
>
{arch}
</MenuToggle>
);
return (
<FormGroup isRequired={true} label="Architecture">
<FormGroup isRequired={true} label='Architecture'>
<Select
isOpen={isOpen}
selected={arch}

View file

@ -8,11 +8,11 @@ import { DEVELOPERS_URL } from '../../../../../constants';
const DeveloperProgramButton = () => {
return (
<Button
component="a"
target="_blank"
variant="link"
component='a'
target='_blank'
variant='link'
icon={<ExternalLinkAltIcon />}
iconPosition="right"
iconPosition='right'
isInline
href={DEVELOPERS_URL}
>
@ -24,7 +24,7 @@ const DeveloperProgramButton = () => {
const CentOSAcknowledgement = () => {
return (
<Alert
variant="info"
variant='info'
isPlain
isInline
title={

View file

@ -117,7 +117,7 @@ export const chartMajorVersionCfg = {
export const MajorReleasesLifecyclesChart = () => {
return (
<Panel>
<PanelMain maxHeight="10rem">
<PanelMain maxHeight='10rem'>
<Bar
options={chartMajorVersionCfg.options}
data={chartMajorVersionCfg.data}
@ -147,16 +147,16 @@ const ReleaseLifecycle = () => {
isExpanded={isExpanded}
isIndented
>
<FormGroup label="Release lifecycle">
<FormGroup label='Release lifecycle'>
<MajorReleasesLifecyclesChart />
</FormGroup>
<br />
<Button
component="a"
target="_blank"
variant="link"
component='a'
target='_blank'
variant='link'
icon={<ExternalLinkAltIcon />}
iconPosition="right"
iconPosition='right'
isInline
href={RELEASE_LIFECYCLE_URL}
>

View file

@ -13,14 +13,12 @@ import {
ON_PREM_RELEASES,
RELEASES,
RHEL_10,
RHEL_10_BETA,
RHEL_10_FULL_SUPPORT,
RHEL_10_MAINTENANCE_SUPPORT,
RHEL_8,
RHEL_8_FULL_SUPPORT,
RHEL_8_MAINTENANCE_SUPPORT,
RHEL_9,
RHEL_9_BETA,
RHEL_9_FULL_SUPPORT,
RHEL_9_MAINTENANCE_SUPPORT,
} from '../../../../../constants';
@ -33,7 +31,6 @@ import {
} from '../../../../../store/wizardSlice';
import isRhel from '../../../../../Utilities/isRhel';
import { toMonthAndYear } from '../../../../../Utilities/time';
import { useFlag } from '../../../../../Utilities/useGetEnvironment';
const ReleaseSelect = () => {
// What the UI refers to as the "release" is referred to as the "distribution" in the API.
@ -44,9 +41,6 @@ const ReleaseSelect = () => {
const [isOpen, setIsOpen] = useState(false);
const [showDevelopmentOptions, setShowDevelopmentOptions] = useState(false);
const isRHEL9BetaEnabled = useFlag('image-builder.rhel9.beta.enabled');
const isRHEL10BetaEnabled = useFlag('image-builder.rhel10.beta.enabled');
const releases = process.env.IS_ON_PREMISE ? ON_PREM_RELEASES : RELEASES;
const handleSelect = (_event: React.MouseEvent, selection: Distributions) => {
@ -70,10 +64,6 @@ const ReleaseSelect = () => {
return '';
}
if (key === RHEL_9_BETA || key === RHEL_10_BETA) {
return '';
}
let fullSupportEnd = '';
let maintenanceSupportEnd = '';
@ -105,20 +95,12 @@ const ReleaseSelect = () => {
return key === distribution;
}
if (key === RHEL_9_BETA) {
return isRHEL9BetaEnabled;
}
if (key === RHEL_10_BETA) {
return isRHEL10BetaEnabled;
}
// Only show non-RHEL distros if expanded
if (showDevelopmentOptions) {
return true;
}
return isRhel(key);
})
}),
);
filteredRhel.forEach((value, key) => {
@ -129,7 +111,7 @@ const ReleaseSelect = () => {
description={setDescription(key as Distributions)}
>
{releases.get(key)}
</SelectOption>
</SelectOption>,
);
});
@ -145,7 +127,7 @@ const ReleaseSelect = () => {
ref={toggleRef}
onClick={onToggleClick}
isExpanded={isOpen}
data-testid="release_select"
data-testid='release_select'
style={
{
maxWidth: '100%',
@ -157,7 +139,7 @@ const ReleaseSelect = () => {
);
return (
<FormGroup isRequired={true} label="Release">
<FormGroup isRequired={true} label='Release'>
<Select
isOpen={isOpen}
onOpenChange={(isOpen) => setIsOpen(isOpen)}
@ -178,7 +160,7 @@ const ReleaseSelect = () => {
ev.stopPropagation();
handleExpand();
}}
value="loader"
value='loader'
isLoadButton
>
Show options for further development of RHEL

View file

@ -1,16 +1,19 @@
import React, { MouseEventHandler, useEffect } from 'react';
import {
Alert,
Button,
Card,
CardHeader,
Checkbox,
Content,
EmptyState,
Flex,
FlexItem,
FormGroup,
Gallery,
Popover,
Spinner,
Title,
} from '@patternfly/react-core';
import { ExternalLinkAltIcon, HelpIcon } from '@patternfly/react-icons';
@ -69,11 +72,15 @@ const TargetEnvironmentCard = ({
}}
>
<Flex direction={{ default: 'column' }}>
{!process.env.IS_ON_PREMISE && (
// the logos don't display in cockpit,
// so we can just hide them
<FlexItem>
<img className='provider-icon' src={imageSrc} alt={imageAlt} />
</FlexItem>
)}
<FlexItem>
<img className="provider-icon" src={imageSrc} alt={imageAlt} />
</FlexItem>
<FlexItem>
<Title headingLevel="h5" size="md">
<Title headingLevel='h5' size='md'>
{title}
</Title>
</FlexItem>
@ -88,11 +95,9 @@ const TargetEnvironment = () => {
const environments = useAppSelector(selectImageTypes);
const distribution = useAppSelector(selectDistribution);
const { data } = useGetArchitecturesQuery({
const { data, isFetching, isError } = useGetArchitecturesQuery({
distribution: distribution,
});
// TODO: Handle isFetching state (add skeletons)
// TODO: Handle isError state (very unlikely...)
const dispatch = useAppDispatch();
const prefetchSources = provisioningApi.usePrefetch('getSourceList');
@ -100,10 +105,13 @@ const TargetEnvironment = () => {
useEffect(() => {
prefetchActivationKeys();
// This useEffect hook should run *only* on mount and therefore has an empty
// dependency array. eslint's exhaustive-deps rule does not support this use.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const supportedEnvironments = data?.find(
(elem) => elem.arch === arch
(elem) => elem.arch === arch,
)?.image_types;
const handleToggleEnvironment = (environment: ImageTypes) => {
@ -133,20 +141,45 @@ const TargetEnvironment = () => {
);
};
if (isFetching) {
return (
<EmptyState
titleText='Loading target environments'
headingLevel='h6'
icon={Spinner}
/>
);
}
if (isError) {
return (
<Alert
title="Couldn't fetch target environments"
variant='danger'
isInline
>
Target environments couldn&apos;t be loaded, please refresh the page or
try again later.
</Alert>
);
}
return (
<FormGroup
isRequired={true}
label="Select target environments"
data-testid="target-select"
label='Select target environments'
data-testid='target-select'
>
{publicCloudsSupported() && (
<FormGroup label={<small>Public cloud</small>}>
<Gallery hasGutter>
{supportedEnvironments?.includes('aws') && (
<TargetEnvironmentCard
title="Amazon Web Services"
imageSrc={'/apps/frontend-assets/partners-icons/aws.svg'}
imageAlt="Amazon Web Services logo"
title='Amazon Web Services'
imageSrc={
'/apps/frontend-assets/partners-icons/aws-logomark.svg'
}
imageAlt='Amazon Web Services logo'
handleOnClick={() => handleToggleEnvironment('aws')}
onMouseEnter={() => prefetchSources({ provider: 'aws' })}
isClicked={environments.includes('aws')}
@ -154,11 +187,11 @@ const TargetEnvironment = () => {
)}
{supportedEnvironments?.includes('gcp') && (
<TargetEnvironmentCard
title="Google Cloud Platform"
title='Google Cloud Platform'
imageSrc={
'/apps/frontend-assets/partners-icons/google-cloud-short.svg'
'/apps/frontend-assets/partners-icons/google-cloud-logomark.svg'
}
imageAlt="Google Cloud Platform logo"
imageAlt='Google Cloud Platform logo'
handleOnClick={() => handleToggleEnvironment('gcp')}
onMouseEnter={() => prefetchSources({ provider: 'gcp' })}
isClicked={environments.includes('gcp')}
@ -166,11 +199,11 @@ const TargetEnvironment = () => {
)}
{supportedEnvironments?.includes('azure') && (
<TargetEnvironmentCard
title="Microsoft Azure"
title='Microsoft Azure'
imageSrc={
'/apps/frontend-assets/partners-icons/microsoft-azure-short.svg'
'/apps/frontend-assets/partners-icons/microsoft-azure-logomark.svg'
}
imageAlt="Microsoft Azure logo"
imageAlt='Microsoft Azure logo'
handleOnClick={() => handleToggleEnvironment('azure')}
onMouseEnter={() => prefetchSources({ provider: 'azure' })}
isClicked={environments.includes('azure')}
@ -178,11 +211,11 @@ const TargetEnvironment = () => {
)}
{supportedEnvironments?.includes('oci') && (
<TargetEnvironmentCard
title="Oracle Cloud Infrastructure"
title='Oracle Cloud Infrastructure'
imageSrc={
'/apps/frontend-assets/partners-icons/oracle-short.svg'
}
imageAlt="Oracle Cloud Infrastructure logo"
imageAlt='Oracle Cloud Infrastructure logo'
handleOnClick={() => handleToggleEnvironment('oci')}
isClicked={environments.includes('oci')}
/>
@ -194,19 +227,19 @@ const TargetEnvironment = () => {
supportedEnvironments?.includes('vsphere-ova')) && (
<FormGroup
label={<small>Private cloud</small>}
className="pf-v6-u-mt-sm"
className='pf-v6-u-mt-sm'
>
{supportedEnvironments?.includes('vsphere-ova') && (
{supportedEnvironments.includes('vsphere-ova') && (
<Checkbox
name="vsphere-checkbox-ova"
aria-label="VMware vSphere checkbox OVA"
id="vsphere-checkbox-ova"
name='vsphere-checkbox-ova'
aria-label='VMware vSphere checkbox OVA'
id='vsphere-checkbox-ova'
label={
<>
VMware vSphere - Open virtualization format (.ova)
<Popover
maxWidth="30rem"
position="right"
maxWidth='30rem'
position='right'
bodyContent={
<Content>
<Content>
@ -221,9 +254,9 @@ const TargetEnvironment = () => {
>
<Button
icon={<HelpIcon />}
className="pf-v6-u-pl-sm pf-v6-u-pt-0 pf-v6-u-pb-0"
variant="plain"
aria-label="About OVA file"
className='pf-v6-u-pl-sm pf-v6-u-pt-0 pf-v6-u-pb-0'
variant='plain'
aria-label='About OVA file'
isInline
/>
</Popover>
@ -235,18 +268,18 @@ const TargetEnvironment = () => {
isChecked={environments.includes('vsphere-ova')}
/>
)}
{supportedEnvironments?.includes('vsphere') && (
{supportedEnvironments.includes('vsphere') && (
<Checkbox
className="pf-v6-u-mt-sm"
name="vsphere-checkbox-vmdk"
aria-label="VMware vSphere checkbox VMDK"
id="vsphere-checkbox-vmdk"
className='pf-v6-u-mt-sm'
name='vsphere-checkbox-vmdk'
aria-label='VMware vSphere checkbox VMDK'
id='vsphere-checkbox-vmdk'
label={
<>
VMware vSphere - Virtual disk (.vmdk)
<Popover
maxWidth="30rem"
position="right"
maxWidth='30rem'
position='right'
bodyContent={
<Content>
<Content>
@ -260,9 +293,9 @@ const TargetEnvironment = () => {
>
<Button
icon={<HelpIcon />}
className="pf-v6-u-pl-sm pf-v6-u-pt-0 pf-v6-u-pb-0"
variant="plain"
aria-label="About VMDK file"
className='pf-v6-u-pl-sm pf-v6-u-pt-0 pf-v6-u-pb-0'
variant='plain'
aria-label='About VMDK file'
isInline
/>
</Popover>
@ -279,26 +312,26 @@ const TargetEnvironment = () => {
<FormGroup label={<small>Other</small>}>
{supportedEnvironments?.includes('guest-image') && (
<Checkbox
label="Virtualization - Guest image (.qcow2)"
label='Virtualization - Guest image (.qcow2)'
isChecked={environments.includes('guest-image')}
onChange={() => {
handleToggleEnvironment('guest-image');
}}
aria-label="Virtualization guest image checkbox"
id="checkbox-guest-image"
name="Virtualization guest image"
aria-label='Virtualization guest image checkbox'
id='checkbox-guest-image'
name='Virtualization guest image'
/>
)}
{supportedEnvironments?.includes('image-installer') && (
<Checkbox
label="Bare metal - Installer (.iso)"
label='Bare metal - Installer (.iso)'
isChecked={environments.includes('image-installer')}
onChange={() => {
handleToggleEnvironment('image-installer');
}}
aria-label="Bare metal installer checkbox"
id="checkbox-image-installer"
name="Bare metal installer"
aria-label='Bare metal installer checkbox'
id='checkbox-image-installer'
name='Bare metal installer'
/>
)}
{supportedEnvironments?.includes('wsl') && (
@ -307,8 +340,8 @@ const TargetEnvironment = () => {
<>
WSL - Windows Subsystem for Linux (.wsl)
<Popover
maxWidth="30rem"
position="right"
maxWidth='30rem'
position='right'
headerContent={
<Content>
<Content>
@ -329,13 +362,13 @@ const TargetEnvironment = () => {
}
footerContent={
<Button
component="a"
target="_blank"
variant="link"
component='a'
target='_blank'
variant='link'
icon={<ExternalLinkAltIcon />}
iconPosition="right"
iconPosition='right'
isInline
href="https://access.redhat.com/articles/7115538"
href='https://access.redhat.com/articles/7115538'
>
Learn more about Red Hat&apos;s WSL support
</Button>
@ -343,9 +376,9 @@ const TargetEnvironment = () => {
>
<Button
icon={<HelpIcon />}
className="pf-v6-u-pl-sm pf-v6-u-pt-0 pf-v6-u-pb-0"
variant="plain"
aria-label="About WSL file"
className='pf-v6-u-pl-sm pf-v6-u-pt-0 pf-v6-u-pb-0'
variant='plain'
aria-label='About WSL file'
isInline
/>
</Popover>
@ -355,9 +388,9 @@ const TargetEnvironment = () => {
onChange={() => {
handleToggleEnvironment('wsl');
}}
aria-label="windows subsystem for linux checkbox"
id="checkbox-wsl"
name="WSL"
aria-label='windows subsystem for linux checkbox'
id='checkbox-wsl'
name='WSL'
/>
)}
</FormGroup>

View file

@ -35,7 +35,7 @@ const ImageOutputStep = () => {
return (
<Form>
<Title headingLevel="h1" size="xl">
<Title headingLevel='h1' size='xl'>
Image output
</Title>
<Content>

View file

@ -31,27 +31,27 @@ const KernelArguments = () => {
},
{
skip: !complianceProfileID,
}
},
);
const requiredByOpenSCAP = kernelAppend.filter((arg) =>
oscapProfileInfo?.kernel?.append?.split(' ').includes(arg)
oscapProfileInfo?.kernel?.append?.split(' ').includes(arg),
);
return (
<FormGroup isRequired={false} label="Append">
<FormGroup isRequired={false} label='Append'>
<LabelInput
ariaLabel="Add kernel argument"
placeholder="Add kernel argument"
ariaLabel='Add kernel argument'
placeholder='Add kernel argument'
validator={isKernelArgumentValid}
list={kernelAppend.filter((arg) => !requiredByOpenSCAP.includes(arg))}
requiredList={requiredByOpenSCAP}
requiredCategoryName="Required by OpenSCAP"
item="Kernel argument"
requiredCategoryName='Required by OpenSCAP'
item='Kernel argument'
addAction={addKernelArg}
removeAction={removeKernelArg}
stepValidation={stepValidation}
fieldName="kernelAppend"
fieldName='kernelAppend'
/>
</FormGroup>
);

View file

@ -44,7 +44,7 @@ const KernelName = () => {
if (filterValue) {
filteredKernelPkgs = kernelOptions.filter((kernel: string) =>
String(kernel).toLowerCase().includes(filterValue.toLowerCase())
String(kernel).toLowerCase().includes(filterValue.toLowerCase()),
);
if (!filteredKernelPkgs.some((kernel) => kernel === filterValue)) {
filteredKernelPkgs = [
@ -113,7 +113,7 @@ const KernelName = () => {
const toggle = (toggleRef: React.Ref<MenuToggleElement>) => (
<MenuToggle
ref={toggleRef}
variant="typeahead"
variant='typeahead'
onClick={onToggleClick}
isExpanded={isOpen}
>
@ -122,8 +122,8 @@ const KernelName = () => {
value={kernel ? kernel : inputValue}
onClick={onInputClick}
onChange={onTextInputChange}
autoComplete="off"
placeholder="Select kernel package"
autoComplete='off'
placeholder='Select kernel package'
isExpanded={isOpen}
/>
@ -131,9 +131,9 @@ const KernelName = () => {
<TextInputGroupUtilities>
<Button
icon={<TimesIcon />}
variant="plain"
variant='plain'
onClick={onClearButtonClick}
aria-label="Clear input"
aria-label='Clear input'
/>
</TextInputGroupUtilities>
)}
@ -145,12 +145,12 @@ const KernelName = () => {
<>
{kernel && !initialOptions.includes(kernel) && (
<Alert
title="Custom kernel packages cannot be validated and can cause build issues."
title='Custom kernel packages cannot be validated and can cause build issues.'
isInline
variant="warning"
variant='warning'
/>
)}
<FormGroup isRequired={false} label="Name">
<FormGroup isRequired={false} label='Name'>
<Select
isScrollable
isOpen={isOpen}

View file

@ -13,15 +13,15 @@ const KernelStep = () => {
return (
<Form>
<Title headingLevel="h1" size="xl">
<Title headingLevel='h1' size='xl'>
Kernel
</Title>
<Content>Customize kernel name and kernel arguments.</Content>
{environments.includes('wsl') && (
<Alert
variant="warning"
variant='warning'
isInline
title="Kernel customizations are not applied to Windows Subsystem for Linux images"
title='Kernel customizations are not applied to Windows Subsystem for Linux images'
/>
)}
<KernelName />

View file

@ -42,14 +42,14 @@ const KeyboardDropDown = () => {
if (filterValue) {
filteredKeyboards = keyboardsList.filter((keyboard: string) =>
String(keyboard).toLowerCase().includes(filterValue.toLowerCase())
String(keyboard).toLowerCase().includes(filterValue.toLowerCase()),
);
if (!isOpen) {
setIsOpen(true);
}
}
setSelectOptions(
filteredKeyboards.sort((a, b) => sortfn(a, b, filterValue))
filteredKeyboards.sort((a, b) => sortfn(a, b, filterValue)),
);
// This useEffect hook should run *only* on when the filter value changes.
@ -98,7 +98,7 @@ const KeyboardDropDown = () => {
const toggle = (toggleRef: React.Ref<MenuToggleElement>) => (
<MenuToggle
ref={toggleRef}
variant="typeahead"
variant='typeahead'
onClick={onToggleClick}
isExpanded={isOpen}
>
@ -107,8 +107,8 @@ const KeyboardDropDown = () => {
value={keyboard ? keyboard : inputValue}
onClick={onInputClick}
onChange={onTextInputChange}
autoComplete="off"
placeholder="Select a keyboard"
autoComplete='off'
placeholder='Select a keyboard'
isExpanded={isOpen}
/>
@ -116,9 +116,9 @@ const KeyboardDropDown = () => {
<TextInputGroupUtilities>
<Button
icon={<TimesIcon />}
variant="plain"
variant='plain'
onClick={onClearButtonClick}
aria-label="Clear input"
aria-label='Clear input'
/>
</TextInputGroupUtilities>
)}
@ -127,7 +127,7 @@ const KeyboardDropDown = () => {
);
return (
<FormGroup isRequired={false} label="Keyboard">
<FormGroup isRequired={false} label='Keyboard'>
<Select
isScrollable
isOpen={isOpen}

View file

@ -37,7 +37,7 @@ const parseLanguageOption = (language: string) => {
type: 'language',
}).of(languageCode);
const countryName = new Intl.DisplayNames(['en'], { type: 'region' }).of(
countryCode
countryCode,
);
return `${languageName} - ${countryName} (${language})`;
@ -72,7 +72,7 @@ const LanguagesDropDown = () => {
if (filterValue) {
filteredLanguages = filteredLanguages.filter(([, parsed]) =>
String(parsed).toLowerCase().includes(filterValue.toLowerCase())
String(parsed).toLowerCase().includes(filterValue.toLowerCase()),
);
if (!isOpen) {
setIsOpen(true);
@ -81,7 +81,7 @@ const LanguagesDropDown = () => {
setSelectOptions(
filteredLanguages
.sort((a, b) => sortfn(a[1], b[1], filterValue))
.map(([raw]) => raw)
.map(([raw]) => raw),
);
// This useEffect hook should run *only* on when the filter value changes.
@ -130,7 +130,7 @@ const LanguagesDropDown = () => {
const toggle = (toggleRef: React.Ref<MenuToggleElement>) => (
<MenuToggle
ref={toggleRef}
variant="typeahead"
variant='typeahead'
onClick={onToggleClick}
isExpanded={isOpen}
>
@ -139,17 +139,17 @@ const LanguagesDropDown = () => {
value={inputValue}
onClick={onInputClick}
onChange={onTextInputChange}
autoComplete="off"
placeholder="Select a language"
autoComplete='off'
placeholder='Select a language'
isExpanded={isOpen}
/>
{inputValue && (
<TextInputGroupUtilities>
<Button
icon={<TimesIcon />}
variant="plain"
variant='plain'
onClick={onClearButtonClick}
aria-label="Clear input"
aria-label='Clear input'
/>
</TextInputGroupUtilities>
)}
@ -158,7 +158,7 @@ const LanguagesDropDown = () => {
);
return (
<FormGroup isRequired={false} label="Languages">
<FormGroup isRequired={false} label='Languages'>
<Select
isScrollable
isOpen={isOpen}
@ -194,11 +194,11 @@ const LanguagesDropDown = () => {
<HelperTextItem
variant={'error'}
>{`Unknown languages: ${unknownLanguages.join(
', '
', ',
)}`}</HelperTextItem>
</HelperText>
)}
<LabelGroup numLabels={5} className="pf-v6-u-mt-sm pf-v6-u-w-100">
<LabelGroup numLabels={5} className='pf-v6-u-mt-sm pf-v6-u-w-100'>
{languages?.map((lang) => (
<Label
key={lang}

View file

@ -8,7 +8,7 @@ import LanguagesDropDown from './components/LanguagesDropDown';
const LocaleStep = () => {
return (
<Form>
<Title headingLevel="h1" size="xl">
<Title headingLevel='h1' size='xl'>
Locale
</Title>
<Content>Select the locale for your image.</Content>

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