Compare commits
No commits in common. "main" and "v72" have entirely different histories.
270 changed files with 42590 additions and 7068 deletions
|
|
@ -1 +0,0 @@
|
|||
1
|
||||
|
|
@ -1,257 +0,0 @@
|
|||
---
|
||||
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
|
||||
|
|
@ -1,257 +0,0 @@
|
|||
---
|
||||
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
|
||||
2
.github/workflows/dev-checks.yml
vendored
2
.github/workflows/dev-checks.yml
vendored
|
|
@ -71,7 +71,7 @@ jobs:
|
|||
run: npm ci
|
||||
- name: Check for manual changes to API
|
||||
run: |
|
||||
npm run api
|
||||
npm run api:generate
|
||||
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
51
.github/workflows/update-apis.yml
vendored
|
|
@ -1,51 +0,0 @@
|
|||
# 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>
|
||||
|
|
@ -32,7 +32,8 @@ test:
|
|||
- RUNNER:
|
||||
- aws/fedora-41-x86_64
|
||||
- aws/fedora-42-x86_64
|
||||
- aws/rhel-10.1-nightly-x86_64
|
||||
- aws/rhel-9.6-nightly-x86_64
|
||||
- aws/rhel-10.0-nightly-x86_64
|
||||
INTERNAL_NETWORK: ["true"]
|
||||
|
||||
finish:
|
||||
|
|
|
|||
9
.prettierrc
Normal file
9
.prettierrc
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"semi": true,
|
||||
"tabWidth": 2,
|
||||
"singleQuote": false,
|
||||
"jsxSingleQuote": false,
|
||||
"bracketSpacing": true,
|
||||
"tsxSingleQuote": true,
|
||||
"tsSingleQuote": true
|
||||
}
|
||||
|
|
@ -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:beb0616db051952b4b861dd8c3e00fa1c0eccbd926feddf71194d3bb3ace9ce7
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-show-sbom:0.1@sha256:86c069cac0a669797e8049faa8aa4088e70ff7fcd579d5bdc37626a9e0488a05
|
||||
- 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:08e18a4dc5f947c1d20e8353a19d013144bea87b72f67236b165dd4778523951
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-init:0.2@sha256:1d8221c84f91b923d89de50bf16481ea729e3b68ea04a9a7cbe8485ddbb27ee6
|
||||
- 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:ce5f2485d759221444357fe38276be876fc54531651e50dcfc0f84b34909d760
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-prefetch-dependencies:0.2@sha256:848f4d5e592d6c145ba3575f52b88d65be95ad6fbba108b24ff79d766cf5d45d
|
||||
- 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:7782cb7462130de8e8839a58dd15ed78e50938d718b51375267679c6044b4367
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-buildah:0.4@sha256:9d7d4724cd2fac84ca1b246eedf51d34f4f0a6d34c4a5b2f9bb614a0f293f38d
|
||||
- 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:72f77a8c62f9d6f69ab5c35170839e4b190026e6cc3d7d4ceafa7033fc30ad7b
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-build-image-index:0.1@sha256:3499772af90aad0d3935629be6d37dd9292195fb629e6f43ec839c7f545a0faa
|
||||
- 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:96ed9431854ecf9805407dca77b063abdf7aba1b3b9d1925a5c6145c6b7e95fd
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-source-build:0.3@sha256:1fdda7563f21340d6243c8738934a58adffd8253706b423d1c4ec5e26ba5fae0
|
||||
- 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:1d07d16810c26713f3d875083924d93697900147364360587ccb5a63f2c31012
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-deprecated-image-check:0.5@sha256:3c8b81fa868e27c6266e7660a4bfb4c822846dcf4304606e71e20893b0d3e515
|
||||
- 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:893ffa3ce26b061e21bb4d8db9ef7ed4ddd4044fe7aa5451ef391034da3ff759
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-clair-scan:0.2@sha256:417f44117f8d87a4a62fea6589b5746612ac61640b454dbd88f74892380411f2
|
||||
- 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:1f151e00f7fc427654b7b76045a426bb02fe650d192ffe147a304d2184787e38
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-ecosystem-cert-preflight-checks:0.2@sha256:f99d2bdb02f13223d494077a2cde31418d09369f33c02134a8e7e5fad2f61eda
|
||||
- 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.3@sha256:cce2dfcc5bd6e91ee54aacdadad523b013eeae5cdaa7f6a4624b8cbcc040f439
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-clamav-scan:0.2@sha256:7749146f7e4fe530846f1b15c9366178ec9f44776ef1922a60d3e7e2b8c6426b
|
||||
- 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:70881c97a4c51ee1f4d023fa1110e0bdfcfd2f51d9a261fa543c3862b9a4eee9
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-apply-tags:0.2@sha256:517a51e260c0b59654a9d7b842e1ab07d76bce15ca7ce9c8fd2489a19be6463d
|
||||
- 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:d5cb22a833be51dd72a872cac8bfbe149e8ad34da7cb48a643a1e613447a1f9d
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-push-dockerfile:0.1@sha256:794850ad7934523d511ebd4e3ec16f1a811a2fa8729580f00209be174b8a3818
|
||||
- name: kind
|
||||
value: task
|
||||
resolver: bundles
|
||||
|
|
|
|||
|
|
@ -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:beb0616db051952b4b861dd8c3e00fa1c0eccbd926feddf71194d3bb3ace9ce7
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-show-sbom:0.1@sha256:86c069cac0a669797e8049faa8aa4088e70ff7fcd579d5bdc37626a9e0488a05
|
||||
- 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:08e18a4dc5f947c1d20e8353a19d013144bea87b72f67236b165dd4778523951
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-init:0.2@sha256:1d8221c84f91b923d89de50bf16481ea729e3b68ea04a9a7cbe8485ddbb27ee6
|
||||
- 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:ce5f2485d759221444357fe38276be876fc54531651e50dcfc0f84b34909d760
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-prefetch-dependencies:0.2@sha256:848f4d5e592d6c145ba3575f52b88d65be95ad6fbba108b24ff79d766cf5d45d
|
||||
- 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:7782cb7462130de8e8839a58dd15ed78e50938d718b51375267679c6044b4367
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-buildah:0.4@sha256:9d7d4724cd2fac84ca1b246eedf51d34f4f0a6d34c4a5b2f9bb614a0f293f38d
|
||||
- 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:72f77a8c62f9d6f69ab5c35170839e4b190026e6cc3d7d4ceafa7033fc30ad7b
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-build-image-index:0.1@sha256:3499772af90aad0d3935629be6d37dd9292195fb629e6f43ec839c7f545a0faa
|
||||
- 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:96ed9431854ecf9805407dca77b063abdf7aba1b3b9d1925a5c6145c6b7e95fd
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-source-build:0.3@sha256:1fdda7563f21340d6243c8738934a58adffd8253706b423d1c4ec5e26ba5fae0
|
||||
- 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:1d07d16810c26713f3d875083924d93697900147364360587ccb5a63f2c31012
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-deprecated-image-check:0.5@sha256:3c8b81fa868e27c6266e7660a4bfb4c822846dcf4304606e71e20893b0d3e515
|
||||
- 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:893ffa3ce26b061e21bb4d8db9ef7ed4ddd4044fe7aa5451ef391034da3ff759
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-clair-scan:0.2@sha256:417f44117f8d87a4a62fea6589b5746612ac61640b454dbd88f74892380411f2
|
||||
- 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:1f151e00f7fc427654b7b76045a426bb02fe650d192ffe147a304d2184787e38
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-ecosystem-cert-preflight-checks:0.2@sha256:f99d2bdb02f13223d494077a2cde31418d09369f33c02134a8e7e5fad2f61eda
|
||||
- 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.3@sha256:cce2dfcc5bd6e91ee54aacdadad523b013eeae5cdaa7f6a4624b8cbcc040f439
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-clamav-scan:0.2@sha256:7749146f7e4fe530846f1b15c9366178ec9f44776ef1922a60d3e7e2b8c6426b
|
||||
- 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:70881c97a4c51ee1f4d023fa1110e0bdfcfd2f51d9a261fa543c3862b9a4eee9
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-apply-tags:0.2@sha256:517a51e260c0b59654a9d7b842e1ab07d76bce15ca7ce9c8fd2489a19be6463d
|
||||
- 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:d5cb22a833be51dd72a872cac8bfbe149e8ad34da7cb48a643a1e613447a1f9d
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-push-dockerfile:0.1@sha256:794850ad7934523d511ebd4e3ec16f1a811a2fa8729580f00209be174b8a3818
|
||||
- name: kind
|
||||
value: task
|
||||
resolver: bundles
|
||||
|
|
|
|||
35
README.md
35
README.md
|
|
@ -128,15 +128,19 @@ 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.
|
||||
|
||||
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`.
|
||||
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`.
|
||||
|
||||
### Add a new API schema
|
||||
|
||||
For a hypothetical API called foobar
|
||||
|
||||
1. Create a new "empty" API file under `src/store/emptyFoobarApi.ts` that has following
|
||||
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
|
||||
content:
|
||||
|
||||
```typescript
|
||||
|
|
@ -152,21 +156,21 @@ export const emptyFoobarApi = createApi({
|
|||
});
|
||||
```
|
||||
|
||||
2. Declare new constant `FOOBAR_API` with the API url in `src/constants.ts`
|
||||
3. Declare new constant `FOOBAR_API` with the API url in `src/constants.ts`
|
||||
|
||||
```typescript
|
||||
export const FOOBAR_API = 'api/foobar/v1'
|
||||
```
|
||||
|
||||
3. Create the config file for code generation in `api/config/foobar.ts` containing:
|
||||
4. 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: 'URL_TO_THE_OPENAPI_SCHEMA',
|
||||
schemaFile: '../schema/foobar.json',
|
||||
apiFile: '../../src/store/emptyFoobarApi.ts',
|
||||
apiImport: 'emptyContentSourcesApi',
|
||||
apiImport: 'emptyEdgeApi',
|
||||
outputFile: '../../src/store/foobarApi.ts',
|
||||
exportName: 'foobarApi',
|
||||
hooks: true,
|
||||
|
|
@ -174,7 +178,14 @@ const config: ConfigFile = {
|
|||
};
|
||||
```
|
||||
|
||||
4. Update the `eslint.config.js` file by adding the generated code path to the ignores array:
|
||||
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:
|
||||
|
||||
```
|
||||
ignores: [
|
||||
|
|
@ -183,7 +194,7 @@ ignores: [
|
|||
]
|
||||
```
|
||||
|
||||
5. run api generation
|
||||
7. run api generation
|
||||
|
||||
```bash
|
||||
npm run api
|
||||
|
|
@ -344,12 +355,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
|
||||
```
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ 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 &
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import type { ConfigFile } from '@rtk-query/codegen-openapi';
|
||||
|
||||
const config: ConfigFile = {
|
||||
schemaFile: 'https://console.redhat.com/api/compliance/v2/openapi.json',
|
||||
schemaFile: '../schema/compliance.json',
|
||||
apiFile: '../../src/store/service/emptyComplianceApi.ts',
|
||||
apiImport: 'emptyComplianceApi',
|
||||
outputFile: '../../src/store/service/complianceApi.ts',
|
||||
|
|
|
|||
|
|
@ -1,15 +1,17 @@
|
|||
import type { ConfigFile } from '@rtk-query/codegen-openapi';
|
||||
|
||||
const config: ConfigFile = {
|
||||
schemaFile:
|
||||
'https://raw.githubusercontent.com/osbuild/osbuild-composer/main/internal/cloudapi/v2/openapi.v2.yml',
|
||||
schemaFile: '../schema/composerCloudApi.v2.yaml',
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import type { ConfigFile } from '@rtk-query/codegen-openapi';
|
||||
|
||||
const config: ConfigFile = {
|
||||
schemaFile: 'https://console.redhat.com/api/content-sources/v1/openapi.json',
|
||||
schemaFile: '../schema/contentSources.json',
|
||||
apiFile: '../../src/store/service/emptyContentSourcesApi.ts',
|
||||
apiImport: 'emptyContentSourcesApi',
|
||||
outputFile: '../../src/store/service/contentSourcesApi.ts',
|
||||
|
|
|
|||
36
api/config/edge.ts
Normal file
36
api/config/edge.ts
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
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;
|
||||
|
|
@ -1,8 +1,7 @@
|
|||
import type { ConfigFile } from '@rtk-query/codegen-openapi';
|
||||
|
||||
const config: ConfigFile = {
|
||||
schemaFile:
|
||||
'https://raw.githubusercontent.com/osbuild/image-builder/main/internal/v1/api.yaml',
|
||||
schemaFile: '../schema/imageBuilder.yaml',
|
||||
apiFile: '../../src/store/service/emptyImageBuilderApi.ts',
|
||||
apiImport: 'emptyImageBuilderApi',
|
||||
outputFile: '../../src/store/service/imageBuilderApi.ts',
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import type { ConfigFile } from '@rtk-query/codegen-openapi';
|
||||
|
||||
const config: ConfigFile = {
|
||||
schemaFile: 'https://console.redhat.com/api/provisioning/v1/openapi.json',
|
||||
schemaFile: '../schema/provisioning.json',
|
||||
apiFile: '../../src/store/service/emptyProvisioningApi.ts',
|
||||
apiImport: 'emptyProvisioningApi',
|
||||
outputFile: '../../src/store/service/provisioningApi.ts',
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import type { ConfigFile } from '@rtk-query/codegen-openapi';
|
||||
|
||||
const config: ConfigFile = {
|
||||
schemaFile: 'https://console.redhat.com/api/rhsm/v2/openapi.json',
|
||||
schemaFile: '../schema/rhsm.json',
|
||||
apiFile: '../../src/store/service/emptyRhsmApi.ts',
|
||||
apiImport: 'emptyRhsmApi',
|
||||
outputFile: '../../src/store/service/rhsmApi.ts',
|
||||
|
|
|
|||
10
api/pull.sh
Normal file
10
api/pull.sh
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
#!/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
|
||||
17629
api/schema/compliance.json
Normal file
17629
api/schema/compliance.json
Normal file
File diff suppressed because it is too large
Load diff
2826
api/schema/composerCloudApi.v2.yaml
Normal file
2826
api/schema/composerCloudApi.v2.yaml
Normal file
File diff suppressed because it is too large
Load diff
6657
api/schema/contentSources.json
Normal file
6657
api/schema/contentSources.json
Normal file
File diff suppressed because it is too large
Load diff
5919
api/schema/edge.json
Normal file
5919
api/schema/edge.json
Normal file
File diff suppressed because it is too large
Load diff
2277
api/schema/imageBuilder.yaml
Normal file
2277
api/schema/imageBuilder.yaml
Normal file
File diff suppressed because it is too large
Load diff
2193
api/schema/provisioning.json
Normal file
2193
api/schema/provisioning.json
Normal file
File diff suppressed because it is too large
Load diff
1
api/schema/rhsm.json
Normal file
1
api/schema/rhsm.json
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -1 +1 @@
|
|||
Subproject commit b496d0a8c1755608bd256a6960869b14a7689d38
|
||||
Subproject commit 07aeb0dd02b76bd1c9c309b8ec0e96893e0ebe49
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
Name: cockpit-image-builder
|
||||
Version: 76
|
||||
Version: 72
|
||||
Release: 1%{?dist}
|
||||
Summary: Image builder plugin for Cockpit
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ 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');
|
||||
|
|
@ -22,6 +21,7 @@ module.exports = defineConfig([
|
|||
'**/contentSourcesApi.ts',
|
||||
'**/rhsmApi.ts',
|
||||
'**/provisioningApi.ts',
|
||||
'**/edgeApi.ts',
|
||||
'**/complianceApi.ts',
|
||||
'**/composerCloudApi.ts'
|
||||
]
|
||||
|
|
@ -62,7 +62,6 @@ module.exports = defineConfig([
|
|||
import: pluginImport,
|
||||
jsxA11y: pluginJsxA11y,
|
||||
'disable-autofix': disableAutofix,
|
||||
prettier: pluginPrettier,
|
||||
},
|
||||
rules: {
|
||||
...js.configs.recommended.rules,
|
||||
|
|
@ -112,25 +111,8 @@ 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': [
|
||||
'error',
|
||||
{
|
||||
argsIgnorePattern: '^_',
|
||||
varsIgnorePattern: '^_',
|
||||
},
|
||||
],
|
||||
'@typescript-eslint/no-unused-vars': 'warn',
|
||||
'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: {
|
||||
|
|
@ -163,12 +145,6 @@ 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
1307
package-lock.json
generated
File diff suppressed because it is too large
Load diff
56
package.json
56
package.json
|
|
@ -8,18 +8,17 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@ltd/j-toml": "1.38.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",
|
||||
"@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",
|
||||
"@reduxjs/toolkit": "2.8.2",
|
||||
"@scalprum/react-core": "0.9.5",
|
||||
"@sentry/webpack-plugin": "4.1.1",
|
||||
"@unleash/proxy-client-react": "5.0.1",
|
||||
"@sentry/webpack-plugin": "3.6.1",
|
||||
"@unleash/proxy-client-react": "5.0.0",
|
||||
"classnames": "2.5.1",
|
||||
"jwt-decode": "4.0.0",
|
||||
"lodash": "4.17.21",
|
||||
|
|
@ -35,25 +34,25 @@
|
|||
"@babel/preset-env": "7.28.0",
|
||||
"@babel/preset-react": "7.27.1",
|
||||
"@babel/preset-typescript": "7.27.1",
|
||||
"@currents/playwright": "1.15.3",
|
||||
"@eslint/js": "9.32.0",
|
||||
"@patternfly/react-icons": "6.3.1",
|
||||
"@currents/playwright": "1.15.2",
|
||||
"@eslint/js": "9.31.0",
|
||||
"@patternfly/react-icons": "6.1.0",
|
||||
"@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.1",
|
||||
"@testing-library/jest-dom": "6.6.4",
|
||||
"@testing-library/dom": "10.4.0",
|
||||
"@testing-library/jest-dom": "6.6.3",
|
||||
"@testing-library/react": "16.3.0",
|
||||
"@testing-library/user-event": "14.6.1",
|
||||
"@types/node": "24.3.0",
|
||||
"@types/node": "24.0.13",
|
||||
"@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.40.0",
|
||||
"@typescript-eslint/parser": "8.40.0",
|
||||
"@typescript-eslint/eslint-plugin": "8.38.0",
|
||||
"@typescript-eslint/parser": "8.38.0",
|
||||
"@vitejs/plugin-react": "4.7.0",
|
||||
"@vitest/coverage-v8": "3.2.4",
|
||||
"babel-loader": "10.0.0",
|
||||
|
|
@ -62,17 +61,16 @@
|
|||
"chartjs-plugin-annotation": "3.1.0",
|
||||
"copy-webpack-plugin": "13.0.0",
|
||||
"css-loader": "7.1.2",
|
||||
"eslint": "9.33.0",
|
||||
"eslint": "9.30.1",
|
||||
"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.2",
|
||||
"eslint-plugin-prettier": "5.5.4",
|
||||
"eslint-plugin-playwright": "2.2.0",
|
||||
"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.6",
|
||||
"eslint-plugin-testing-library": "7.6.0",
|
||||
"git-revision-webpack-plugin": "5.0.0",
|
||||
"globals": "16.3.0",
|
||||
"history": "5.3.0",
|
||||
|
|
@ -81,20 +79,20 @@
|
|||
"madge": "8.0.0",
|
||||
"mini-css-extract-plugin": "2.9.2",
|
||||
"moment": "2.30.1",
|
||||
"msw": "2.10.5",
|
||||
"msw": "2.10.4",
|
||||
"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.90.0",
|
||||
"sass": "1.89.2",
|
||||
"sass-loader": "16.0.5",
|
||||
"stylelint": "16.23.1",
|
||||
"stylelint-config-recommended-scss": "16.0.0",
|
||||
"stylelint": "16.22.0",
|
||||
"stylelint-config-recommended-scss": "15.0.1",
|
||||
"ts-node": "10.9.2",
|
||||
"ts-patch": "3.3.0",
|
||||
"typescript": "5.8.3",
|
||||
"typescript-eslint": "8.40.0",
|
||||
"typescript-eslint": "8.38.0",
|
||||
"uuid": "11.1.0",
|
||||
"vitest": "3.2.4",
|
||||
"vitest-canvas-mock": "0.3.3",
|
||||
|
|
@ -117,7 +115,9 @@
|
|||
"test:cockpit": "src/test/cockpit-tests.sh",
|
||||
"build": "fec build",
|
||||
"build:cockpit": "webpack --config cockpit/webpack.config.ts",
|
||||
"api": "bash api/codegen.sh",
|
||||
"api": "npm-run-all api:pull api:generate",
|
||||
"api:generate": "bash api/codegen.sh",
|
||||
"api:pull": "bash api/pull.sh",
|
||||
"verify": "npm-run-all build lint test",
|
||||
"postinstall": "ts-patch install",
|
||||
"circular": "madge --circular ./src --extensions js,ts,tsx",
|
||||
|
|
|
|||
10
packit.yaml
10
packit.yaml
|
|
@ -16,15 +16,6 @@ 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
|
||||
|
|
@ -33,6 +24,7 @@ jobs:
|
|||
- centos-stream-10
|
||||
- centos-stream-10-aarch64
|
||||
- fedora-all
|
||||
- fedora-all-aarch64
|
||||
|
||||
- job: copr_build
|
||||
trigger: commit
|
||||
|
|
|
|||
|
|
@ -1,14 +0,0 @@
|
|||
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
|
||||
|
|
@ -1,214 +0,0 @@
|
|||
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();
|
||||
});
|
||||
});
|
||||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,189 +0,0 @@
|
|||
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();
|
||||
});
|
||||
});
|
||||
|
|
@ -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();
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ export const test = oldTest.extend<WithCleanup>({
|
|||
async () => {
|
||||
await Promise.all(Array.from(cleanupFns).map(([, fn]) => fn()));
|
||||
},
|
||||
{ box: true },
|
||||
{ box: true }
|
||||
);
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,6 +1,3 @@
|
|||
import { execSync } from 'child_process';
|
||||
import { readFileSync } from 'node:fs';
|
||||
|
||||
import { expect, type Page } from '@playwright/test';
|
||||
|
||||
export const togglePreview = async (page: Page) => {
|
||||
|
|
@ -45,43 +42,3 @@ 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, '');
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { expect, FrameLocator, Page } from '@playwright/test';
|
||||
import type { FrameLocator, Page } from '@playwright/test';
|
||||
|
||||
import { getHostArch, getHostDistroName, isHosted } from './helpers';
|
||||
import { isHosted } from './helpers';
|
||||
|
||||
/**
|
||||
* Opens the wizard, fills out the "Image Output" step, and navigates to the optional steps
|
||||
|
|
@ -8,13 +8,6 @@ import { getHostArch, getHostDistroName, 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();
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -72,11 +72,6 @@ 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();
|
||||
|
|
@ -92,12 +87,7 @@ test.describe.serial('test', () => {
|
|||
await frame.getByRole('button', { name: 'Create blueprint' }).click();
|
||||
|
||||
await expect(
|
||||
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,
|
||||
),
|
||||
frame.locator('.pf-v6-c-card__title-text').getByText(blueprintName)
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
|
|
@ -131,7 +121,6 @@ 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();
|
||||
|
|
@ -139,7 +128,6 @@ 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' });
|
||||
});
|
||||
|
|
@ -208,16 +196,14 @@ test.describe.serial('test', () => {
|
|||
const switchInput = frame.locator('#aws-config-switch');
|
||||
await expect(switchInput).toBeVisible();
|
||||
|
||||
// introduce a wait time, since it takes some time to load the
|
||||
// worker config file.
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
// If this test fails for any reason, the config should already be loaded
|
||||
// and visible on the retury. If it is go back to the landing page
|
||||
// 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
|
||||
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');
|
||||
|
|
@ -231,84 +217,27 @@ 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('textbox', { name: 'Search input' })
|
||||
.fill(cockpitBlueprintname);
|
||||
// the clickable blueprint cards are a bit awkward, so use the
|
||||
// button's id instead
|
||||
await frame.locator(`button[id="${cockpitBlueprintname}"]`).click();
|
||||
await frame.getByTestId('blueprint-build-image-menu-option').click();
|
||||
|
||||
// make sure the image is present
|
||||
await frame
|
||||
.getByTestId('images-table')
|
||||
.getByRole('button', { name: 'Details' })
|
||||
.getByRole('button', { name: 'Configure Cloud Providers' })
|
||||
.click();
|
||||
frame.getByText('Build Information');
|
||||
await expect(header).toBeVisible();
|
||||
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();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,8 +0,0 @@
|
|||
summary: run playwright tests
|
||||
test: ./playwright_tests.sh
|
||||
require:
|
||||
- cockpit-image-builder
|
||||
- podman
|
||||
- nodejs
|
||||
- nodejs-npm
|
||||
duration: 30m
|
||||
|
|
@ -1,16 +1,16 @@
|
|||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
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
|
||||
# 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
|
||||
|
||||
sudo systemctl enable --now cockpit.socket
|
||||
|
||||
|
|
@ -19,13 +19,10 @@ sudo usermod -aG wheel admin
|
|||
echo "admin ALL=(ALL:ALL) NOPASSWD: ALL" | sudo tee "/etc/sudoers.d/admin-nopasswd"
|
||||
|
||||
function upload_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
|
||||
mkdir -p /tmp/artifacts/extra-screenshots
|
||||
USER="$(whoami)"
|
||||
sudo chown -R "$USER:$USER" playwright-report
|
||||
mv playwright-report /tmp/artifacts/
|
||||
}
|
||||
trap upload_artifacts EXIT
|
||||
|
||||
|
|
@ -76,12 +73,11 @@ 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 \
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
cf0a810fd3b75fa27139746c4dfe72222e13dcba
|
||||
7b4735d287dd0950e0a6f47dde65b62b0f239da1
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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' });
|
||||
|
|
|
|||
|
|
@ -50,21 +50,11 @@ const BlueprintCard = ({ blueprint }: blueprintProps) => {
|
|||
onChange: () => dispatch(setBlueprintId(blueprint.id)),
|
||||
}}
|
||||
>
|
||||
<CardTitle aria-label={blueprint.name}>
|
||||
<CardTitle>
|
||||
{isLoading && blueprint.id === selectedBlueprintId && (
|
||||
<Spinner size='md' />
|
||||
<Spinner size="md" />
|
||||
)}
|
||||
{
|
||||
// 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
|
||||
}
|
||||
{blueprint.name}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardBody>{blueprint.description}</CardBody>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
/>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useCallback } from 'react';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
import {
|
||||
Bullseye,
|
||||
|
|
@ -17,6 +17,7 @@ import {
|
|||
import { PlusCircleIcon, SearchIcon } from '@patternfly/react-icons';
|
||||
import { SVGIconProps } from '@patternfly/react-icons/dist/esm/createIcon';
|
||||
import useChrome from '@redhat-cloud-services/frontend-components/useChrome';
|
||||
import { ChromeUser } from '@redhat-cloud-services/types';
|
||||
import debounce from 'lodash/debounce';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
|
|
@ -28,7 +29,6 @@ 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,6 +73,13 @@ const BlueprintsSidebar = () => {
|
|||
offset: blueprintsOffset,
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
const data = await auth?.getUser();
|
||||
setUserData(data);
|
||||
})();
|
||||
}, [auth]);
|
||||
|
||||
if (blueprintSearchInput) {
|
||||
searchParams.search = blueprintSearchInput;
|
||||
}
|
||||
|
|
@ -90,7 +97,7 @@ const BlueprintsSidebar = () => {
|
|||
if (isLoading) {
|
||||
return (
|
||||
<Bullseye>
|
||||
<Spinner size='xl' />
|
||||
<Spinner size="xl" />
|
||||
</Bullseye>
|
||||
);
|
||||
}
|
||||
|
|
@ -104,8 +111,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."
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
@ -116,7 +123,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,
|
||||
|
|
@ -137,7 +144,7 @@ const BlueprintsSidebar = () => {
|
|||
<Flex justifyContent={{ default: 'justifyContentCenter' }}>
|
||||
<FlexItem>
|
||||
<Button
|
||||
variant='link'
|
||||
variant="link"
|
||||
isDisabled={!selectedBlueprintId}
|
||||
onClick={handleClickViewAll}
|
||||
>
|
||||
|
|
@ -153,14 +160,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 &&
|
||||
|
|
@ -184,7 +191,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 () => {
|
||||
|
|
@ -202,7 +209,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`}
|
||||
|
|
@ -216,7 +223,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>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useState } from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
import {
|
||||
Button,
|
||||
|
|
@ -16,13 +16,11 @@ 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,
|
||||
useGetUser,
|
||||
} from '../../Hooks';
|
||||
import { useComposeBPWithNotification as useComposeBlueprintMutation } from '../../Hooks';
|
||||
import { useGetBlueprintQuery } from '../../store/backendApi';
|
||||
import { selectSelectedBlueprintId } from '../../store/BlueprintSlice';
|
||||
import { useAppSelector } from '../../store/hooks';
|
||||
|
|
@ -39,7 +37,15 @@ export const BuildImagesButton = ({ children }: BuildImagesButtonPropTypes) => {
|
|||
const { trigger: buildBlueprint, isLoading: imageBuildLoading } =
|
||||
useComposeBlueprintMutation();
|
||||
const { analytics, auth } = useChrome();
|
||||
const { userData } = useGetUser(auth);
|
||||
|
||||
const [userData, setUserData] = useState<ChromeUser | void>(undefined);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
const data = await auth?.getUser();
|
||||
setUserData(data);
|
||||
})();
|
||||
}, [auth]);
|
||||
|
||||
const onBuildHandler = async () => {
|
||||
if (selectedBlueprintId) {
|
||||
|
|
@ -47,17 +53,15 @@ export const BuildImagesButton = ({ children }: BuildImagesButtonPropTypes) => {
|
|||
id: selectedBlueprintId,
|
||||
body: {
|
||||
image_types: blueprintImageType?.filter(
|
||||
(target) => !deselectedTargets.includes(target),
|
||||
(target) => !deselectedTargets.includes(target)
|
||||
),
|
||||
},
|
||||
});
|
||||
if (!process.env.IS_ON_PREMISE) {
|
||||
analytics.track(`${AMPLITUDE_MODULE_NAME} - Image Requested`, {
|
||||
module: AMPLITUDE_MODULE_NAME,
|
||||
trigger: 'synchronize images',
|
||||
account_id: userData?.identity.internal?.account_id || 'Not found',
|
||||
});
|
||||
}
|
||||
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);
|
||||
|
|
@ -65,21 +69,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]);
|
||||
|
|
@ -92,17 +96,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
|
||||
|
|
@ -118,7 +122,7 @@ export const BuildImagesButton = ({ children }: BuildImagesButtonPropTypes) => {
|
|||
} as React.CSSProperties
|
||||
}
|
||||
isInline
|
||||
size='md'
|
||||
size="md"
|
||||
/>
|
||||
</FlexItem>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import React from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
import {
|
||||
Button,
|
||||
|
|
@ -9,16 +9,14 @@ 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,
|
||||
useGetUser,
|
||||
} from '../../Hooks';
|
||||
import { useDeleteBPWithNotification as useDeleteBlueprintMutation } from '../../Hooks';
|
||||
import { backendApi, useGetBlueprintsQuery } from '../../store/backendApi';
|
||||
import {
|
||||
selectBlueprintSearchInput,
|
||||
|
|
@ -44,7 +42,14 @@ export const DeleteBlueprintModal: React.FunctionComponent<
|
|||
const blueprintsLimit = useAppSelector(selectLimit) || PAGINATION_LIMIT;
|
||||
const dispatch = useAppDispatch();
|
||||
const { analytics, auth } = useChrome();
|
||||
const { userData } = useGetUser(auth);
|
||||
const [userData, setUserData] = useState<ChromeUser | void>(undefined);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
const data = await auth?.getUser();
|
||||
setUserData(data);
|
||||
})();
|
||||
}, [auth]);
|
||||
|
||||
const searchParams: GetBlueprintsApiArg = {
|
||||
limit: blueprintsLimit,
|
||||
|
|
@ -59,7 +64,7 @@ export const DeleteBlueprintModal: React.FunctionComponent<
|
|||
selectFromResult: ({ data }) => ({
|
||||
blueprintName: data?.data.find(
|
||||
(blueprint: { id: string | undefined }) =>
|
||||
blueprint.id === selectedBlueprintId,
|
||||
blueprint.id === selectedBlueprintId
|
||||
)?.name,
|
||||
}),
|
||||
});
|
||||
|
|
@ -85,16 +90,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>
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ export const EditBlueprintButton = () => {
|
|||
onClick={() =>
|
||||
navigate(resolveRelPath(`imagewizard/${selectedBlueprintId}`))
|
||||
}
|
||||
variant='secondary'
|
||||
variant="secondary"
|
||||
>
|
||||
Edit blueprint
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -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,8 +155,9 @@ 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 ?? [];
|
||||
}
|
||||
|
||||
|
|
@ -174,7 +175,7 @@ export const ImportBlueprintModal: React.FunctionComponent<
|
|||
undefined;
|
||||
const importBlueprintState = mapExportRequestToState(
|
||||
blueprintExportedResponse,
|
||||
blueprintFromFile.image_requests || [],
|
||||
blueprintFromFile.image_requests || []
|
||||
);
|
||||
|
||||
setIsOnPrem(false);
|
||||
|
|
@ -184,7 +185,7 @@ export const ImportBlueprintModal: React.FunctionComponent<
|
|||
mapOnPremToHosted(blueprintFromFile);
|
||||
const importBlueprintState = mapExportRequestToState(
|
||||
blueprintFromFileMapped,
|
||||
[],
|
||||
[]
|
||||
);
|
||||
setIsOnPrem(true);
|
||||
setImportedBlueprint(importBlueprintState);
|
||||
|
|
@ -258,9 +259,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>
|
||||
|
|
@ -269,23 +270,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}
|
||||
|
|
@ -293,7 +294,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,
|
||||
|
|
@ -307,10 +308,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>
|
||||
|
|
@ -319,7 +320,7 @@ export const ImportBlueprintModal: React.FunctionComponent<
|
|||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button
|
||||
type='button'
|
||||
type="button"
|
||||
isDisabled={isRejected || isInvalidFormat || !fileContent}
|
||||
onClick={() =>
|
||||
navigate(resolveRelPath(`imagewizard/import`), {
|
||||
|
|
@ -329,7 +330,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>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import React from 'react';
|
||||
import React, { useState } 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,35 +147,30 @@ 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 = {
|
||||
enabled: boolean;
|
||||
setEnabled: (enabled: boolean) => void;
|
||||
isEnabled: boolean;
|
||||
reinit: (config: AWSWorkerConfig | undefined) => void;
|
||||
refetch: () => Promise<{
|
||||
data?: WorkerConfigResponse | undefined;
|
||||
}>;
|
||||
};
|
||||
|
||||
export const AWSConfig = ({
|
||||
enabled,
|
||||
setEnabled,
|
||||
refetch,
|
||||
reinit,
|
||||
}: AWSConfigProps) => {
|
||||
export const AWSConfig = ({ isEnabled, 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) {
|
||||
|
|
|
|||
|
|
@ -1,9 +1,4 @@
|
|||
import React, {
|
||||
MouseEventHandler,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useState,
|
||||
} from 'react';
|
||||
import React, { MouseEventHandler, useCallback, useEffect } from 'react';
|
||||
|
||||
import {
|
||||
Button,
|
||||
|
|
@ -48,9 +43,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>
|
||||
|
|
@ -59,7 +54,7 @@ const ConfigError = ({
|
|||
</EmptyStateBody>
|
||||
<EmptyStateFooter>
|
||||
<EmptyStateActions>
|
||||
<Button variant='primary' onClick={onClose}>
|
||||
<Button variant="primary" onClick={onClose}>
|
||||
Go back
|
||||
</Button>
|
||||
</EmptyStateActions>
|
||||
|
|
@ -73,7 +68,6 @@ 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({});
|
||||
|
|
@ -82,12 +76,9 @@ 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));
|
||||
|
|
@ -97,7 +88,7 @@ export const CloudProviderConfig = () => {
|
|||
dispatch(changeAWSCredsPath(credentials));
|
||||
}
|
||||
},
|
||||
[dispatch, setEnabled],
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -118,8 +109,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),
|
||||
|
|
@ -135,8 +126,7 @@ export const CloudProviderConfig = () => {
|
|||
<AWSConfig
|
||||
refetch={refetch}
|
||||
reinit={initAWSConfig}
|
||||
enabled={enabled}
|
||||
setEnabled={setEnabled}
|
||||
isEnabled={!!(data?.aws && Object.keys(data.aws).length > 0)}
|
||||
/>
|
||||
</WizardStep>
|
||||
</Wizard>
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ export const isAwsCredsPathValid = (credsPath?: string): boolean => {
|
|||
};
|
||||
|
||||
export const isAwsStepValid = (
|
||||
config: AWSWorkerConfig | undefined,
|
||||
config: AWSWorkerConfig | undefined
|
||||
): boolean => {
|
||||
if (!config) {
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
);
|
||||
}}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ 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';
|
||||
|
|
@ -41,7 +40,6 @@ import UsersStep from './steps/Users';
|
|||
import { getHostArch, getHostDistro } from './utilities/getHostInfo';
|
||||
import { useHasSpecificTargetOnly } from './utilities/hasSpecificTargetOnly';
|
||||
import {
|
||||
useAAPValidation,
|
||||
useDetailsValidation,
|
||||
useFilesystemValidation,
|
||||
useFirewallValidation,
|
||||
|
|
@ -67,6 +65,7 @@ import {
|
|||
AARCH64,
|
||||
AMPLITUDE_MODULE_NAME,
|
||||
RHEL_10,
|
||||
RHEL_10_BETA,
|
||||
RHEL_8,
|
||||
RHEL_9,
|
||||
} from '../../constants';
|
||||
|
|
@ -75,13 +74,14 @@ 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,7 +199,6 @@ 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(() => {
|
||||
|
|
@ -210,6 +209,9 @@ 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));
|
||||
}
|
||||
|
|
@ -233,10 +235,6 @@ 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();
|
||||
|
|
@ -264,9 +262,11 @@ 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,8 +286,6 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
|
|||
const firewallValidation = useFirewallValidation();
|
||||
// Services
|
||||
const servicesValidation = useServicesValidation();
|
||||
// AAP
|
||||
const aapValidation = useAAPValidation();
|
||||
// Firstboot
|
||||
const firstBootValidation = useFirstBootValidation();
|
||||
// Details
|
||||
|
|
@ -298,10 +296,8 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
|
|||
const hasWslTargetOnly = useHasSpecificTargetOnly('wsl');
|
||||
|
||||
let startIndex = 1; // default index
|
||||
const JUMP_TO_REVIEW_STEP = 23;
|
||||
|
||||
if (isEdit) {
|
||||
startIndex = JUMP_TO_REVIEW_STEP;
|
||||
startIndex = 22;
|
||||
}
|
||||
|
||||
const [wasRegisterVisited, setWasRegisterVisited] = useState(false);
|
||||
|
|
@ -312,7 +308,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';
|
||||
|
|
@ -328,7 +324,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
|
||||
|
|
@ -356,7 +352,7 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
|
|||
{
|
||||
module: AMPLITUDE_MODULE_NAME,
|
||||
isPreview: isBeta(),
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
}}
|
||||
|
|
@ -375,8 +371,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}
|
||||
|
|
@ -387,29 +383,25 @@ 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={
|
||||
// we don't need the account id for
|
||||
// on-prem aws.
|
||||
process.env.IS_ON_PREMISE
|
||||
? false
|
||||
: awsShareMethod === 'manual'
|
||||
? !isAwsAccountIdValid(awsAccountId)
|
||||
: awsSourceId === undefined
|
||||
awsShareMethod === 'manual'
|
||||
? !isAwsAccountIdValid(awsAccountId)
|
||||
: awsSourceId === undefined
|
||||
}
|
||||
/>
|
||||
}
|
||||
|
|
@ -418,9 +410,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={
|
||||
|
|
@ -434,15 +426,21 @@ 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={
|
||||
!isAzureTenantGUIDValid(azureTenantId) ||
|
||||
!isAzureSubscriptionIdValid(azureSubscriptionId) ||
|
||||
!isAzureResourceGroupValid(azureResourceGroup)
|
||||
azureShareMethod === 'manual'
|
||||
? !isAzureTenantGUIDValid(azureTenantId) ||
|
||||
!isAzureSubscriptionIdValid(azureSubscriptionId) ||
|
||||
!isAzureResourceGroupValid(azureResourceGroup)
|
||||
: azureShareMethod === 'sources'
|
||||
? !isAzureTenantGUIDValid(azureTenantId) ||
|
||||
!isAzureSubscriptionIdValid(azureSubscriptionId) ||
|
||||
!isAzureResourceGroupValid(azureResourceGroup)
|
||||
: azureSource === undefined
|
||||
}
|
||||
/>
|
||||
}
|
||||
|
|
@ -453,13 +451,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={
|
||||
|
|
@ -480,8 +478,9 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
|
|||
</WizardStep>,
|
||||
<WizardStep
|
||||
name={complianceEnabled ? 'Compliance' : 'OpenSCAP'}
|
||||
id='step-oscap'
|
||||
key='step-oscap'
|
||||
id="step-oscap"
|
||||
key="step-oscap"
|
||||
isHidden={distribution === RHEL_10_BETA}
|
||||
navItem={CustomStatusNavItem}
|
||||
footer={
|
||||
<CustomWizardFooter disableNext={false} optional={true} />
|
||||
|
|
@ -490,9 +489,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={
|
||||
|
|
@ -516,12 +515,14 @@ 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={!!process.env.IS_ON_PREMISE}
|
||||
isHidden={
|
||||
distribution === RHEL_10_BETA || !!process.env.IS_ON_PREMISE
|
||||
}
|
||||
footer={
|
||||
<CustomWizardFooter
|
||||
disableNext={snapshotValidation.disabledNext}
|
||||
|
|
@ -532,11 +533,13 @@ 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={!!process.env.IS_ON_PREMISE}
|
||||
isHidden={
|
||||
distribution === RHEL_10_BETA || !!process.env.IS_ON_PREMISE
|
||||
}
|
||||
isDisabled={snapshotValidation.disabledNext}
|
||||
footer={
|
||||
<CustomWizardFooter disableNext={false} optional={true} />
|
||||
|
|
@ -545,9 +548,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={
|
||||
|
|
@ -557,9 +560,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={
|
||||
|
|
@ -572,9 +575,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={
|
||||
|
|
@ -587,9 +590,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={
|
||||
|
|
@ -602,9 +605,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={
|
||||
|
|
@ -617,9 +620,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'}
|
||||
|
|
@ -633,9 +636,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={
|
||||
|
|
@ -648,9 +651,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={
|
||||
|
|
@ -663,25 +666,9 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
|
|||
<ServicesStep />
|
||||
</WizardStep>,
|
||||
<WizardStep
|
||||
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'
|
||||
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}
|
||||
|
|
@ -697,8 +684,8 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
|
|||
]}
|
||||
/>
|
||||
<WizardStep
|
||||
name='Details'
|
||||
id='step-details'
|
||||
name="Details"
|
||||
id="step-details"
|
||||
navItem={CustomStatusNavItem}
|
||||
status={detailsValidation.disabledNext ? 'error' : 'default'}
|
||||
footer={
|
||||
|
|
@ -710,8 +697,8 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
|
|||
<DetailsStep />
|
||||
</WizardStep>
|
||||
<WizardStep
|
||||
name='Review'
|
||||
id='step-review'
|
||||
name="Review"
|
||||
id="step-review"
|
||||
footer={<ReviewWizardFooter />}
|
||||
>
|
||||
<ReviewStep />
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,7 +138,6 @@ export const ValidatedInput = ({
|
|||
value,
|
||||
placeholder,
|
||||
onChange,
|
||||
...props
|
||||
}: ValidatedTextInputPropTypes) => {
|
||||
const [isPristine, setIsPristine] = useState(!value ? true : false);
|
||||
|
||||
|
|
@ -159,17 +158,16 @@ 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>
|
||||
)}
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -1,178 +0,0 @@
|
|||
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;
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
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;
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -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'));
|
||||
|
|
|
|||
|
|
@ -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.length > 0 &&
|
||||
{partitions &&
|
||||
partitions.map((partition) => (
|
||||
<Row
|
||||
onDrop={onDrop}
|
||||
|
|
|
|||
|
|
@ -9,18 +9,17 @@ 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>
|
||||
|
|
@ -31,11 +30,13 @@ const FileSystemStep = () => {
|
|||
<FileSystemPartition />
|
||||
<FileSystemAutomaticPartition />
|
||||
</>
|
||||
) : (
|
||||
) : fileSystemConfigurationType === 'manual' ? (
|
||||
<>
|
||||
<FileSystemPartition />
|
||||
<FileSystemConfiguration />
|
||||
</>
|
||||
) : (
|
||||
fileSystemConfigurationType === 'oscap' && <FileSystemConfiguration />
|
||||
)}
|
||||
</Form>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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={
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -13,12 +13,14 @@ 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';
|
||||
|
|
@ -31,6 +33,7 @@ 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.
|
||||
|
|
@ -41,6 +44,9 @@ 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) => {
|
||||
|
|
@ -64,6 +70,10 @@ const ReleaseSelect = () => {
|
|||
return '';
|
||||
}
|
||||
|
||||
if (key === RHEL_9_BETA || key === RHEL_10_BETA) {
|
||||
return '';
|
||||
}
|
||||
|
||||
let fullSupportEnd = '';
|
||||
let maintenanceSupportEnd = '';
|
||||
|
||||
|
|
@ -95,12 +105,20 @@ 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) => {
|
||||
|
|
@ -111,7 +129,7 @@ const ReleaseSelect = () => {
|
|||
description={setDescription(key as Distributions)}
|
||||
>
|
||||
{releases.get(key)}
|
||||
</SelectOption>,
|
||||
</SelectOption>
|
||||
);
|
||||
});
|
||||
|
||||
|
|
@ -127,7 +145,7 @@ const ReleaseSelect = () => {
|
|||
ref={toggleRef}
|
||||
onClick={onToggleClick}
|
||||
isExpanded={isOpen}
|
||||
data-testid='release_select'
|
||||
data-testid="release_select"
|
||||
style={
|
||||
{
|
||||
maxWidth: '100%',
|
||||
|
|
@ -139,7 +157,7 @@ const ReleaseSelect = () => {
|
|||
);
|
||||
|
||||
return (
|
||||
<FormGroup isRequired={true} label='Release'>
|
||||
<FormGroup isRequired={true} label="Release">
|
||||
<Select
|
||||
isOpen={isOpen}
|
||||
onOpenChange={(isOpen) => setIsOpen(isOpen)}
|
||||
|
|
@ -160,7 +178,7 @@ const ReleaseSelect = () => {
|
|||
ev.stopPropagation();
|
||||
handleExpand();
|
||||
}}
|
||||
value='loader'
|
||||
value="loader"
|
||||
isLoadButton
|
||||
>
|
||||
Show options for further development of RHEL
|
||||
|
|
|
|||
|
|
@ -1,19 +1,16 @@
|
|||
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';
|
||||
|
|
@ -72,15 +69,11 @@ 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>
|
||||
<Title headingLevel='h5' size='md'>
|
||||
<img className="provider-icon" src={imageSrc} alt={imageAlt} />
|
||||
</FlexItem>
|
||||
<FlexItem>
|
||||
<Title headingLevel="h5" size="md">
|
||||
{title}
|
||||
</Title>
|
||||
</FlexItem>
|
||||
|
|
@ -95,9 +88,11 @@ const TargetEnvironment = () => {
|
|||
const environments = useAppSelector(selectImageTypes);
|
||||
const distribution = useAppSelector(selectDistribution);
|
||||
|
||||
const { data, isFetching, isError } = useGetArchitecturesQuery({
|
||||
const { data } = useGetArchitecturesQuery({
|
||||
distribution: distribution,
|
||||
});
|
||||
// TODO: Handle isFetching state (add skeletons)
|
||||
// TODO: Handle isError state (very unlikely...)
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
const prefetchSources = provisioningApi.usePrefetch('getSourceList');
|
||||
|
|
@ -105,13 +100,10 @@ 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) => {
|
||||
|
|
@ -141,45 +133,20 @@ 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'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-logomark.svg'
|
||||
}
|
||||
imageAlt='Amazon Web Services logo'
|
||||
title="Amazon Web Services"
|
||||
imageSrc={'/apps/frontend-assets/partners-icons/aws.svg'}
|
||||
imageAlt="Amazon Web Services logo"
|
||||
handleOnClick={() => handleToggleEnvironment('aws')}
|
||||
onMouseEnter={() => prefetchSources({ provider: 'aws' })}
|
||||
isClicked={environments.includes('aws')}
|
||||
|
|
@ -187,11 +154,11 @@ const TargetEnvironment = () => {
|
|||
)}
|
||||
{supportedEnvironments?.includes('gcp') && (
|
||||
<TargetEnvironmentCard
|
||||
title='Google Cloud Platform'
|
||||
title="Google Cloud Platform"
|
||||
imageSrc={
|
||||
'/apps/frontend-assets/partners-icons/google-cloud-logomark.svg'
|
||||
'/apps/frontend-assets/partners-icons/google-cloud-short.svg'
|
||||
}
|
||||
imageAlt='Google Cloud Platform logo'
|
||||
imageAlt="Google Cloud Platform logo"
|
||||
handleOnClick={() => handleToggleEnvironment('gcp')}
|
||||
onMouseEnter={() => prefetchSources({ provider: 'gcp' })}
|
||||
isClicked={environments.includes('gcp')}
|
||||
|
|
@ -199,11 +166,11 @@ const TargetEnvironment = () => {
|
|||
)}
|
||||
{supportedEnvironments?.includes('azure') && (
|
||||
<TargetEnvironmentCard
|
||||
title='Microsoft Azure'
|
||||
title="Microsoft Azure"
|
||||
imageSrc={
|
||||
'/apps/frontend-assets/partners-icons/microsoft-azure-logomark.svg'
|
||||
'/apps/frontend-assets/partners-icons/microsoft-azure-short.svg'
|
||||
}
|
||||
imageAlt='Microsoft Azure logo'
|
||||
imageAlt="Microsoft Azure logo"
|
||||
handleOnClick={() => handleToggleEnvironment('azure')}
|
||||
onMouseEnter={() => prefetchSources({ provider: 'azure' })}
|
||||
isClicked={environments.includes('azure')}
|
||||
|
|
@ -211,11 +178,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')}
|
||||
/>
|
||||
|
|
@ -227,19 +194,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>
|
||||
|
|
@ -254,9 +221,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>
|
||||
|
|
@ -268,18 +235,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>
|
||||
|
|
@ -293,9 +260,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>
|
||||
|
|
@ -312,26 +279,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') && (
|
||||
|
|
@ -340,8 +307,8 @@ const TargetEnvironment = () => {
|
|||
<>
|
||||
WSL - Windows Subsystem for Linux (.wsl)
|
||||
<Popover
|
||||
maxWidth='30rem'
|
||||
position='right'
|
||||
maxWidth="30rem"
|
||||
position="right"
|
||||
headerContent={
|
||||
<Content>
|
||||
<Content>
|
||||
|
|
@ -362,13 +329,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's WSL support
|
||||
</Button>
|
||||
|
|
@ -376,9 +343,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>
|
||||
|
|
@ -388,9 +355,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>
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ const ImageOutputStep = () => {
|
|||
|
||||
return (
|
||||
<Form>
|
||||
<Title headingLevel='h1' size='xl'>
|
||||
<Title headingLevel="h1" size="xl">
|
||||
Image output
|
||||
</Title>
|
||||
<Content>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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 />
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue