Compare commits
123 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
080513ad6d | ||
|
|
7391652e17 | ||
|
|
9a17373234 | ||
|
|
d7f844b8b6 | ||
|
|
859b7cace8 | ||
|
|
3a83a14720 | ||
|
|
e61cb99f1b | ||
|
|
a5aa15cbcb | ||
|
|
44c3674072 | ||
|
|
4d783537fb | ||
|
|
0b96c64c93 | ||
|
|
0d917c3cd8 | ||
|
|
957700adcc | ||
|
|
fa0560ac4d | ||
|
|
0e7f5d9e7b | ||
|
|
e0dd33fdc9 | ||
|
|
b0393a5f4f | ||
|
|
a9d2ba59a8 | ||
|
|
af19251f17 | ||
|
|
090544c333 | ||
|
|
4b188a0393 | ||
|
|
63f55c7408 | ||
|
|
bc3288a83e | ||
|
|
3e5c5dca76 | ||
|
|
04f0528701 | ||
|
|
3e2e9dcaa6 | ||
|
|
54e413f459 | ||
|
|
f6f6e58449 | ||
|
|
bf77501eea | ||
|
|
42b16bafd8 | ||
|
|
04adcc133c | ||
|
|
11e352440f | ||
|
|
122c481c09 | ||
|
|
c930621316 | ||
|
|
cedb4f07bd | ||
|
|
0c47c4b165 | ||
|
|
223d11b691 | ||
|
|
62d18b2a38 | ||
|
|
df405033d5 | ||
|
|
771abb8bc9 | ||
|
|
dea68f8b5c | ||
|
|
f668d295a6 | ||
|
|
0af6a0324f | ||
|
|
eee1f78d27 | ||
|
|
d66f54a847 | ||
|
|
3461c908fb | ||
|
|
b08fee11bc | ||
|
|
e5de087810 | ||
|
|
0c46f052a8 | ||
|
|
904e4cccea | ||
|
|
c868fe5d41 | ||
|
|
676ffc9b3a | ||
|
|
30f4cdd9c3 | ||
|
|
8209bfe62c | ||
|
|
2098ede032 | ||
|
|
bb345c0e4f | ||
|
|
eafcd200ae | ||
|
|
e9025e460c | ||
|
|
4c098db796 | ||
|
|
2bea0bd50b | ||
|
|
894d2a4d76 | ||
|
|
68b2f74a97 | ||
|
|
35c9f32cf8 | ||
|
|
7269b0c7db | ||
|
|
88dd0880c8 | ||
|
|
4f250ee637 | ||
|
|
acc79e149c | ||
|
|
3b8b2ad240 | ||
|
|
9f526faa65 | ||
|
|
0acedb913c | ||
|
|
327e1cd48f | ||
|
|
1bfc830147 | ||
|
|
fdaf5129c8 | ||
|
|
64e5744d8c | ||
|
|
90c2c65ebe | ||
|
|
9943f54cd9 | ||
|
|
d94834e25f | ||
|
|
9efdd82771 | ||
|
|
1096c6d4fb | ||
|
|
d5321bb078 | ||
|
|
8cf161d4e5 | ||
|
|
98b890206b | ||
|
|
8d656b766c | ||
|
|
ceec85209c | ||
|
|
cfa8cbcb28 | ||
|
|
96da1817df | ||
|
|
9da490ad52 | ||
|
|
128abcb98f | ||
|
|
c026102dd3 | ||
|
|
d5877b256c | ||
|
|
730554dc84 | ||
|
|
09febf8061 | ||
|
|
8524e8e374 | ||
|
|
a8f21a7a90 | ||
|
|
e52b43bdb7 | ||
|
|
2c3efe4c04 | ||
|
|
ad5ea22da8 | ||
|
|
9f3ad99037 | ||
|
|
f4e872548c | ||
|
|
5b7b8daa4d | ||
|
|
67a0f86dde | ||
|
|
4d051eecde | ||
|
|
661fd29a5e | ||
|
|
6bf800d4d9 | ||
|
|
825e3beac1 | ||
|
|
6f9a34c972 | ||
|
|
42d96edd00 | ||
|
|
b8dc0e60c9 | ||
|
|
5c1f9dbbdd | ||
|
|
3d39065ad0 | ||
|
|
f86f81d6d5 | ||
|
|
690b71636a | ||
|
|
a6eadbffac | ||
|
|
8af4181ae9 | ||
|
|
3a9e3aa200 | ||
|
|
4339420cb8 | ||
|
|
fe5abaeb45 | ||
|
|
c88171da19 | ||
|
|
2f765a1d4b | ||
|
|
2ce62d4ef0 | ||
|
|
253317497e | ||
|
|
8dd82d5801 | ||
|
|
33d3a02ee5 |
270 changed files with 7068 additions and 42590 deletions
1
.fmf/version
Normal file
1
.fmf/version
Normal file
|
|
@ -0,0 +1 @@
|
|||
1
|
||||
257
.forgejo/workflows/ci.yml
Normal file
257
.forgejo/workflows/ci.yml
Normal file
|
|
@ -0,0 +1,257 @@
|
|||
---
|
||||
name: Debian Image Builder Frontend CI/CD
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, develop]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
NODE_VERSION: "18"
|
||||
DEBIAN_FRONTEND: noninteractive
|
||||
|
||||
jobs:
|
||||
build-and-test:
|
||||
name: Build and Test Frontend
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: node:18-bullseye
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js environment
|
||||
run: |
|
||||
node --version
|
||||
npm --version
|
||||
|
||||
- name: Install build dependencies
|
||||
run: |
|
||||
apt-get update
|
||||
apt-get install -y \
|
||||
build-essential \
|
||||
git \
|
||||
ca-certificates \
|
||||
python3
|
||||
|
||||
- name: Install Node.js dependencies
|
||||
run: |
|
||||
npm ci
|
||||
npm run build || echo "Build script not found"
|
||||
|
||||
- name: Run tests
|
||||
run: |
|
||||
if [ -f package.json ] && npm run test; then
|
||||
npm test
|
||||
else
|
||||
echo "No test script found, skipping tests"
|
||||
fi
|
||||
|
||||
- name: Run linting
|
||||
run: |
|
||||
if [ -f package.json ] && npm run lint; then
|
||||
npm run lint
|
||||
else
|
||||
echo "No lint script found, skipping linting"
|
||||
fi
|
||||
|
||||
- name: Build production bundle
|
||||
run: |
|
||||
if [ -f package.json ] && npm run build; then
|
||||
npm run build
|
||||
else
|
||||
echo "No build script found"
|
||||
fi
|
||||
|
||||
- name: Upload build artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: frontend-build
|
||||
path: |
|
||||
dist/
|
||||
build/
|
||||
retention-days: 30
|
||||
|
||||
package:
|
||||
name: Package Frontend
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: node:18-bullseye
|
||||
needs: build-and-test
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js environment
|
||||
run: |
|
||||
node --version
|
||||
npm --version
|
||||
|
||||
- name: Install build dependencies
|
||||
run: |
|
||||
apt-get update
|
||||
apt-get install -y \
|
||||
build-essential \
|
||||
devscripts \
|
||||
debhelper \
|
||||
git \
|
||||
ca-certificates \
|
||||
python3
|
||||
|
||||
- name: Install Node.js dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Build production bundle
|
||||
run: |
|
||||
if [ -f package.json ] && npm run build; then
|
||||
npm run build
|
||||
else
|
||||
echo "No build script found"
|
||||
fi
|
||||
|
||||
- name: Create debian directory
|
||||
run: |
|
||||
mkdir -p debian
|
||||
cat > debian/control << EOF
|
||||
Source: debian-image-builder-frontend
|
||||
Section: web
|
||||
Priority: optional
|
||||
Maintainer: Debian Forge Team <team@debian-forge.org>
|
||||
Build-Depends: debhelper (>= 13), nodejs, npm, git, ca-certificates
|
||||
Standards-Version: 4.6.2
|
||||
|
||||
Package: debian-image-builder-frontend
|
||||
Architecture: all
|
||||
Depends: \${misc:Depends}, nodejs, nginx
|
||||
Description: Debian Image Builder Frontend
|
||||
Web-based frontend for Debian Image Builder with Cockpit integration.
|
||||
Provides a user interface for managing image builds, blueprints,
|
||||
and system configurations through a modern React application.
|
||||
EOF
|
||||
|
||||
cat > debian/rules << EOF
|
||||
#!/usr/bin/make -f
|
||||
%:
|
||||
dh \$@
|
||||
|
||||
override_dh_auto_install:
|
||||
dh_auto_install
|
||||
mkdir -p debian/debian-image-builder-frontend/usr/share/debian-image-builder-frontend
|
||||
mkdir -p debian/debian-image-builder-frontend/etc/nginx/sites-available
|
||||
mkdir -p debian/debian-image-builder-frontend/etc/cockpit
|
||||
|
||||
# Copy built frontend files
|
||||
if [ -d dist ]; then
|
||||
cp -r dist/* debian/debian-image-builder-frontend/usr/share/debian-image-builder-frontend/
|
||||
elif [ -d build ]; then
|
||||
cp -r build/* debian/debian-image-builder-frontend/usr/share/debian-image-builder-frontend/
|
||||
fi
|
||||
|
||||
# Copy source files for development
|
||||
cp -r src debian/debian-image-builder-frontend/usr/share/debian-image-builder-frontend/
|
||||
cp package.json debian/debian-image-builder-frontend/usr/share/debian-image-builder-frontend/
|
||||
|
||||
# Create nginx configuration
|
||||
cat > debian/debian-image-builder-frontend/etc/nginx/sites-available/debian-image-builder-frontend << 'NGINX_EOF'
|
||||
server {
|
||||
listen 80;
|
||||
server_name localhost;
|
||||
root /usr/share/debian-image-builder-frontend;
|
||||
index index.html;
|
||||
|
||||
location / {
|
||||
try_files \$uri \$uri/ /index.html;
|
||||
}
|
||||
|
||||
location /api/ {
|
||||
proxy_pass http://localhost:8080/;
|
||||
proxy_set_header Host \$host;
|
||||
proxy_set_header X-Real-IP \$remote_addr;
|
||||
}
|
||||
}
|
||||
NGINX_EOF
|
||||
|
||||
# Create cockpit manifest
|
||||
cat > debian/debian-image-builder-frontend/etc/cockpit/debian-image-builder.manifest << 'COCKPIT_EOF'
|
||||
{
|
||||
"version": 1,
|
||||
"manifest": {
|
||||
"name": "debian-image-builder",
|
||||
"version": "1.0.0",
|
||||
"title": "Debian Image Builder",
|
||||
"description": "Build and manage Debian atomic images",
|
||||
"url": "/usr/share/debian-image-builder-frontend",
|
||||
"icon": "debian-logo",
|
||||
"requires": {
|
||||
"cockpit": ">= 200"
|
||||
}
|
||||
}
|
||||
}
|
||||
COCKPIT_EOF
|
||||
EOF
|
||||
|
||||
cat > debian/changelog << EOF
|
||||
debian-image-builder-frontend (1.0.0-1) unstable; urgency=medium
|
||||
|
||||
* Initial release
|
||||
* Debian Image Builder Frontend with Cockpit integration
|
||||
* React-based web interface for image management
|
||||
|
||||
-- Debian Forge Team <team@debian-forge.org> $(date -R)
|
||||
EOF
|
||||
|
||||
cat > debian/compat << EOF
|
||||
13
|
||||
EOF
|
||||
|
||||
chmod +x debian/rules
|
||||
|
||||
- name: Build Debian package
|
||||
run: |
|
||||
dpkg-buildpackage -us -uc -b
|
||||
ls -la ../*.deb
|
||||
|
||||
- name: Upload Debian package
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: debian-image-builder-frontend-deb
|
||||
path: ../*.deb
|
||||
retention-days: 30
|
||||
|
||||
cockpit-integration:
|
||||
name: Test Cockpit Integration
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: node:18-bullseye
|
||||
needs: build-and-test
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js environment
|
||||
run: |
|
||||
node --version
|
||||
npm --version
|
||||
|
||||
- name: Install Node.js dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Test cockpit integration
|
||||
run: |
|
||||
echo "Testing Cockpit integration..."
|
||||
if [ -d cockpit ]; then
|
||||
echo "Cockpit directory found:"
|
||||
ls -la cockpit/
|
||||
else
|
||||
echo "No cockpit directory found"
|
||||
fi
|
||||
|
||||
if [ -f package.json ]; then
|
||||
echo "Package.json scripts:"
|
||||
npm run
|
||||
fi
|
||||
257
.forgejo/workflows/ci.yml.disabled
Normal file
257
.forgejo/workflows/ci.yml.disabled
Normal file
|
|
@ -0,0 +1,257 @@
|
|||
---
|
||||
name: Debian Image Builder Frontend CI/CD
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, develop]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
NODE_VERSION: "18"
|
||||
DEBIAN_FRONTEND: noninteractive
|
||||
|
||||
jobs:
|
||||
build-and-test:
|
||||
name: Build and Test Frontend
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: node:18-bullseye
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js environment
|
||||
run: |
|
||||
node --version
|
||||
npm --version
|
||||
|
||||
- name: Install build dependencies
|
||||
run: |
|
||||
apt-get update
|
||||
apt-get install -y \
|
||||
build-essential \
|
||||
git \
|
||||
ca-certificates \
|
||||
python3
|
||||
|
||||
- name: Install Node.js dependencies
|
||||
run: |
|
||||
npm ci
|
||||
npm run build || echo "Build script not found"
|
||||
|
||||
- name: Run tests
|
||||
run: |
|
||||
if [ -f package.json ] && npm run test; then
|
||||
npm test
|
||||
else
|
||||
echo "No test script found, skipping tests"
|
||||
fi
|
||||
|
||||
- name: Run linting
|
||||
run: |
|
||||
if [ -f package.json ] && npm run lint; then
|
||||
npm run lint
|
||||
else
|
||||
echo "No lint script found, skipping linting"
|
||||
fi
|
||||
|
||||
- name: Build production bundle
|
||||
run: |
|
||||
if [ -f package.json ] && npm run build; then
|
||||
npm run build
|
||||
else
|
||||
echo "No build script found"
|
||||
fi
|
||||
|
||||
- name: Upload build artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: frontend-build
|
||||
path: |
|
||||
dist/
|
||||
build/
|
||||
retention-days: 30
|
||||
|
||||
package:
|
||||
name: Package Frontend
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: node:18-bullseye
|
||||
needs: build-and-test
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js environment
|
||||
run: |
|
||||
node --version
|
||||
npm --version
|
||||
|
||||
- name: Install build dependencies
|
||||
run: |
|
||||
apt-get update
|
||||
apt-get install -y \
|
||||
build-essential \
|
||||
devscripts \
|
||||
debhelper \
|
||||
git \
|
||||
ca-certificates \
|
||||
python3
|
||||
|
||||
- name: Install Node.js dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Build production bundle
|
||||
run: |
|
||||
if [ -f package.json ] && npm run build; then
|
||||
npm run build
|
||||
else
|
||||
echo "No build script found"
|
||||
fi
|
||||
|
||||
- name: Create debian directory
|
||||
run: |
|
||||
mkdir -p debian
|
||||
cat > debian/control << EOF
|
||||
Source: debian-image-builder-frontend
|
||||
Section: web
|
||||
Priority: optional
|
||||
Maintainer: Debian Forge Team <team@debian-forge.org>
|
||||
Build-Depends: debhelper (>= 13), nodejs, npm, git, ca-certificates
|
||||
Standards-Version: 4.6.2
|
||||
|
||||
Package: debian-image-builder-frontend
|
||||
Architecture: all
|
||||
Depends: \${misc:Depends}, nodejs, nginx
|
||||
Description: Debian Image Builder Frontend
|
||||
Web-based frontend for Debian Image Builder with Cockpit integration.
|
||||
Provides a user interface for managing image builds, blueprints,
|
||||
and system configurations through a modern React application.
|
||||
EOF
|
||||
|
||||
cat > debian/rules << EOF
|
||||
#!/usr/bin/make -f
|
||||
%:
|
||||
dh \$@
|
||||
|
||||
override_dh_auto_install:
|
||||
dh_auto_install
|
||||
mkdir -p debian/debian-image-builder-frontend/usr/share/debian-image-builder-frontend
|
||||
mkdir -p debian/debian-image-builder-frontend/etc/nginx/sites-available
|
||||
mkdir -p debian/debian-image-builder-frontend/etc/cockpit
|
||||
|
||||
# Copy built frontend files
|
||||
if [ -d dist ]; then
|
||||
cp -r dist/* debian/debian-image-builder-frontend/usr/share/debian-image-builder-frontend/
|
||||
elif [ -d build ]; then
|
||||
cp -r build/* debian/debian-image-builder-frontend/usr/share/debian-image-builder-frontend/
|
||||
fi
|
||||
|
||||
# Copy source files for development
|
||||
cp -r src debian/debian-image-builder-frontend/usr/share/debian-image-builder-frontend/
|
||||
cp package.json debian/debian-image-builder-frontend/usr/share/debian-image-builder-frontend/
|
||||
|
||||
# Create nginx configuration
|
||||
cat > debian/debian-image-builder-frontend/etc/nginx/sites-available/debian-image-builder-frontend << 'NGINX_EOF'
|
||||
server {
|
||||
listen 80;
|
||||
server_name localhost;
|
||||
root /usr/share/debian-image-builder-frontend;
|
||||
index index.html;
|
||||
|
||||
location / {
|
||||
try_files \$uri \$uri/ /index.html;
|
||||
}
|
||||
|
||||
location /api/ {
|
||||
proxy_pass http://localhost:8080/;
|
||||
proxy_set_header Host \$host;
|
||||
proxy_set_header X-Real-IP \$remote_addr;
|
||||
}
|
||||
}
|
||||
NGINX_EOF
|
||||
|
||||
# Create cockpit manifest
|
||||
cat > debian/debian-image-builder-frontend/etc/cockpit/debian-image-builder.manifest << 'COCKPIT_EOF'
|
||||
{
|
||||
"version": 1,
|
||||
"manifest": {
|
||||
"name": "debian-image-builder",
|
||||
"version": "1.0.0",
|
||||
"title": "Debian Image Builder",
|
||||
"description": "Build and manage Debian atomic images",
|
||||
"url": "/usr/share/debian-image-builder-frontend",
|
||||
"icon": "debian-logo",
|
||||
"requires": {
|
||||
"cockpit": ">= 200"
|
||||
}
|
||||
}
|
||||
}
|
||||
COCKPIT_EOF
|
||||
EOF
|
||||
|
||||
cat > debian/changelog << EOF
|
||||
debian-image-builder-frontend (1.0.0-1) unstable; urgency=medium
|
||||
|
||||
* Initial release
|
||||
* Debian Image Builder Frontend with Cockpit integration
|
||||
* React-based web interface for image management
|
||||
|
||||
-- Debian Forge Team <team@debian-forge.org> $(date -R)
|
||||
EOF
|
||||
|
||||
cat > debian/compat << EOF
|
||||
13
|
||||
EOF
|
||||
|
||||
chmod +x debian/rules
|
||||
|
||||
- name: Build Debian package
|
||||
run: |
|
||||
dpkg-buildpackage -us -uc -b
|
||||
ls -la ../*.deb
|
||||
|
||||
- name: Upload Debian package
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: debian-image-builder-frontend-deb
|
||||
path: ../*.deb
|
||||
retention-days: 30
|
||||
|
||||
cockpit-integration:
|
||||
name: Test Cockpit Integration
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: node:18-bullseye
|
||||
needs: build-and-test
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js environment
|
||||
run: |
|
||||
node --version
|
||||
npm --version
|
||||
|
||||
- name: Install Node.js dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Test cockpit integration
|
||||
run: |
|
||||
echo "Testing Cockpit integration..."
|
||||
if [ -d cockpit ]; then
|
||||
echo "Cockpit directory found:"
|
||||
ls -la cockpit/
|
||||
else
|
||||
echo "No cockpit directory found"
|
||||
fi
|
||||
|
||||
if [ -f package.json ]; then
|
||||
echo "Package.json scripts:"
|
||||
npm run
|
||||
fi
|
||||
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:generate
|
||||
npm run api
|
||||
if [ -n "$(git status --porcelain)" ]; then
|
||||
echo
|
||||
echo "✗ API manually changed, please refer to the README for the procedure to follow for programmatically generated API endpoints."
|
||||
|
|
|
|||
51
.github/workflows/update-apis.yml
vendored
Normal file
51
.github/workflows/update-apis.yml
vendored
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
# This action checks API updates every day at 5:00 UTC.
|
||||
name: Update API code generation
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: "0 5 * * *"
|
||||
|
||||
jobs:
|
||||
update-api:
|
||||
name: "Update API definitions"
|
||||
if: github.repository == 'osbuild/image-builder-frontend'
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Use Node.js 22
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Mark the working directory as safe for git
|
||||
run: git config --global --add safe.directory "$(pwd)"
|
||||
|
||||
- name: Run API code generation
|
||||
run: npm run api
|
||||
|
||||
- name: Check if there are any changes
|
||||
run: |
|
||||
if [ "$(git status --porcelain)" ]; then
|
||||
echo
|
||||
echo "API codegen is up-to-date"
|
||||
exit "0"
|
||||
fi
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
with:
|
||||
branch: update-apis
|
||||
delete-branch: true
|
||||
title: "api: regenerate api code generation"
|
||||
commit-message: "api: regenerate api code generation"
|
||||
body: Update api code generation
|
||||
token: ${{ secrets.SCHUTZBOT_GITHUB_ACCESS_TOKEN }}
|
||||
author: schutzbot <schutzbot@gmail.com>
|
||||
|
|
@ -32,8 +32,7 @@ test:
|
|||
- RUNNER:
|
||||
- aws/fedora-41-x86_64
|
||||
- aws/fedora-42-x86_64
|
||||
- aws/rhel-9.6-nightly-x86_64
|
||||
- aws/rhel-10.0-nightly-x86_64
|
||||
- aws/rhel-10.1-nightly-x86_64
|
||||
INTERNAL_NETWORK: ["true"]
|
||||
|
||||
finish:
|
||||
|
|
|
|||
|
|
@ -1,9 +0,0 @@
|
|||
{
|
||||
"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:86c069cac0a669797e8049faa8aa4088e70ff7fcd579d5bdc37626a9e0488a05
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-show-sbom:0.1@sha256:beb0616db051952b4b861dd8c3e00fa1c0eccbd926feddf71194d3bb3ace9ce7
|
||||
- name: kind
|
||||
value: task
|
||||
resolver: bundles
|
||||
|
|
@ -152,7 +152,7 @@ spec:
|
|||
- name: name
|
||||
value: init
|
||||
- name: bundle
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-init:0.2@sha256:1d8221c84f91b923d89de50bf16481ea729e3b68ea04a9a7cbe8485ddbb27ee6
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-init:0.2@sha256:08e18a4dc5f947c1d20e8353a19d013144bea87b72f67236b165dd4778523951
|
||||
- name: kind
|
||||
value: task
|
||||
resolver: bundles
|
||||
|
|
@ -194,7 +194,7 @@ spec:
|
|||
- name: name
|
||||
value: prefetch-dependencies
|
||||
- name: bundle
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-prefetch-dependencies:0.2@sha256:848f4d5e592d6c145ba3575f52b88d65be95ad6fbba108b24ff79d766cf5d45d
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-prefetch-dependencies:0.2@sha256:ce5f2485d759221444357fe38276be876fc54531651e50dcfc0f84b34909d760
|
||||
- name: kind
|
||||
value: task
|
||||
resolver: bundles
|
||||
|
|
@ -238,7 +238,7 @@ spec:
|
|||
- name: name
|
||||
value: buildah
|
||||
- name: bundle
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-buildah:0.4@sha256:9d7d4724cd2fac84ca1b246eedf51d34f4f0a6d34c4a5b2f9bb614a0f293f38d
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-buildah:0.4@sha256:7782cb7462130de8e8839a58dd15ed78e50938d718b51375267679c6044b4367
|
||||
- name: kind
|
||||
value: task
|
||||
resolver: bundles
|
||||
|
|
@ -270,7 +270,7 @@ spec:
|
|||
- name: name
|
||||
value: build-image-index
|
||||
- name: bundle
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-build-image-index:0.1@sha256:3499772af90aad0d3935629be6d37dd9292195fb629e6f43ec839c7f545a0faa
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-build-image-index:0.1@sha256:72f77a8c62f9d6f69ab5c35170839e4b190026e6cc3d7d4ceafa7033fc30ad7b
|
||||
- name: kind
|
||||
value: task
|
||||
resolver: bundles
|
||||
|
|
@ -292,7 +292,7 @@ spec:
|
|||
- name: name
|
||||
value: source-build
|
||||
- name: bundle
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-source-build:0.3@sha256:1fdda7563f21340d6243c8738934a58adffd8253706b423d1c4ec5e26ba5fae0
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-source-build:0.3@sha256:96ed9431854ecf9805407dca77b063abdf7aba1b3b9d1925a5c6145c6b7e95fd
|
||||
- name: kind
|
||||
value: task
|
||||
resolver: bundles
|
||||
|
|
@ -371,7 +371,7 @@ spec:
|
|||
- name: name
|
||||
value: deprecated-image-check
|
||||
- name: bundle
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-deprecated-image-check:0.5@sha256:3c8b81fa868e27c6266e7660a4bfb4c822846dcf4304606e71e20893b0d3e515
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-deprecated-image-check:0.5@sha256:1d07d16810c26713f3d875083924d93697900147364360587ccb5a63f2c31012
|
||||
- name: kind
|
||||
value: task
|
||||
resolver: bundles
|
||||
|
|
@ -393,7 +393,7 @@ spec:
|
|||
- name: name
|
||||
value: clair-scan
|
||||
- name: bundle
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-clair-scan:0.2@sha256:417f44117f8d87a4a62fea6589b5746612ac61640b454dbd88f74892380411f2
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-clair-scan:0.2@sha256:893ffa3ce26b061e21bb4d8db9ef7ed4ddd4044fe7aa5451ef391034da3ff759
|
||||
- name: kind
|
||||
value: task
|
||||
resolver: bundles
|
||||
|
|
@ -413,7 +413,7 @@ spec:
|
|||
- name: name
|
||||
value: ecosystem-cert-preflight-checks
|
||||
- name: bundle
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-ecosystem-cert-preflight-checks:0.2@sha256:f99d2bdb02f13223d494077a2cde31418d09369f33c02134a8e7e5fad2f61eda
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-ecosystem-cert-preflight-checks:0.2@sha256:1f151e00f7fc427654b7b76045a426bb02fe650d192ffe147a304d2184787e38
|
||||
- name: kind
|
||||
value: task
|
||||
resolver: bundles
|
||||
|
|
@ -460,7 +460,7 @@ spec:
|
|||
- name: name
|
||||
value: clamav-scan
|
||||
- name: bundle
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-clamav-scan:0.2@sha256:7749146f7e4fe530846f1b15c9366178ec9f44776ef1922a60d3e7e2b8c6426b
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-clamav-scan:0.3@sha256:cce2dfcc5bd6e91ee54aacdadad523b013eeae5cdaa7f6a4624b8cbcc040f439
|
||||
- name: kind
|
||||
value: task
|
||||
resolver: bundles
|
||||
|
|
@ -482,7 +482,7 @@ spec:
|
|||
- name: name
|
||||
value: apply-tags
|
||||
- name: bundle
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-apply-tags:0.2@sha256:517a51e260c0b59654a9d7b842e1ab07d76bce15ca7ce9c8fd2489a19be6463d
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-apply-tags:0.2@sha256:70881c97a4c51ee1f4d023fa1110e0bdfcfd2f51d9a261fa543c3862b9a4eee9
|
||||
- name: kind
|
||||
value: task
|
||||
resolver: bundles
|
||||
|
|
@ -503,7 +503,7 @@ spec:
|
|||
- name: name
|
||||
value: push-dockerfile
|
||||
- name: bundle
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-push-dockerfile:0.1@sha256:794850ad7934523d511ebd4e3ec16f1a811a2fa8729580f00209be174b8a3818
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-push-dockerfile:0.1@sha256:d5cb22a833be51dd72a872cac8bfbe149e8ad34da7cb48a643a1e613447a1f9d
|
||||
- name: kind
|
||||
value: task
|
||||
resolver: bundles
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ spec:
|
|||
- name: name
|
||||
value: show-sbom
|
||||
- name: bundle
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-show-sbom:0.1@sha256:86c069cac0a669797e8049faa8aa4088e70ff7fcd579d5bdc37626a9e0488a05
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-show-sbom:0.1@sha256:beb0616db051952b4b861dd8c3e00fa1c0eccbd926feddf71194d3bb3ace9ce7
|
||||
- name: kind
|
||||
value: task
|
||||
resolver: bundles
|
||||
|
|
@ -149,7 +149,7 @@ spec:
|
|||
- name: name
|
||||
value: init
|
||||
- name: bundle
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-init:0.2@sha256:1d8221c84f91b923d89de50bf16481ea729e3b68ea04a9a7cbe8485ddbb27ee6
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-init:0.2@sha256:08e18a4dc5f947c1d20e8353a19d013144bea87b72f67236b165dd4778523951
|
||||
- name: kind
|
||||
value: task
|
||||
resolver: bundles
|
||||
|
|
@ -191,7 +191,7 @@ spec:
|
|||
- name: name
|
||||
value: prefetch-dependencies
|
||||
- name: bundle
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-prefetch-dependencies:0.2@sha256:848f4d5e592d6c145ba3575f52b88d65be95ad6fbba108b24ff79d766cf5d45d
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-prefetch-dependencies:0.2@sha256:ce5f2485d759221444357fe38276be876fc54531651e50dcfc0f84b34909d760
|
||||
- name: kind
|
||||
value: task
|
||||
resolver: bundles
|
||||
|
|
@ -235,7 +235,7 @@ spec:
|
|||
- name: name
|
||||
value: buildah
|
||||
- name: bundle
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-buildah:0.4@sha256:9d7d4724cd2fac84ca1b246eedf51d34f4f0a6d34c4a5b2f9bb614a0f293f38d
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-buildah:0.4@sha256:7782cb7462130de8e8839a58dd15ed78e50938d718b51375267679c6044b4367
|
||||
- name: kind
|
||||
value: task
|
||||
resolver: bundles
|
||||
|
|
@ -267,7 +267,7 @@ spec:
|
|||
- name: name
|
||||
value: build-image-index
|
||||
- name: bundle
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-build-image-index:0.1@sha256:3499772af90aad0d3935629be6d37dd9292195fb629e6f43ec839c7f545a0faa
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-build-image-index:0.1@sha256:72f77a8c62f9d6f69ab5c35170839e4b190026e6cc3d7d4ceafa7033fc30ad7b
|
||||
- name: kind
|
||||
value: task
|
||||
resolver: bundles
|
||||
|
|
@ -289,7 +289,7 @@ spec:
|
|||
- name: name
|
||||
value: source-build
|
||||
- name: bundle
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-source-build:0.3@sha256:1fdda7563f21340d6243c8738934a58adffd8253706b423d1c4ec5e26ba5fae0
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-source-build:0.3@sha256:96ed9431854ecf9805407dca77b063abdf7aba1b3b9d1925a5c6145c6b7e95fd
|
||||
- name: kind
|
||||
value: task
|
||||
resolver: bundles
|
||||
|
|
@ -368,7 +368,7 @@ spec:
|
|||
- name: name
|
||||
value: deprecated-image-check
|
||||
- name: bundle
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-deprecated-image-check:0.5@sha256:3c8b81fa868e27c6266e7660a4bfb4c822846dcf4304606e71e20893b0d3e515
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-deprecated-image-check:0.5@sha256:1d07d16810c26713f3d875083924d93697900147364360587ccb5a63f2c31012
|
||||
- name: kind
|
||||
value: task
|
||||
resolver: bundles
|
||||
|
|
@ -390,7 +390,7 @@ spec:
|
|||
- name: name
|
||||
value: clair-scan
|
||||
- name: bundle
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-clair-scan:0.2@sha256:417f44117f8d87a4a62fea6589b5746612ac61640b454dbd88f74892380411f2
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-clair-scan:0.2@sha256:893ffa3ce26b061e21bb4d8db9ef7ed4ddd4044fe7aa5451ef391034da3ff759
|
||||
- name: kind
|
||||
value: task
|
||||
resolver: bundles
|
||||
|
|
@ -410,7 +410,7 @@ spec:
|
|||
- name: name
|
||||
value: ecosystem-cert-preflight-checks
|
||||
- name: bundle
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-ecosystem-cert-preflight-checks:0.2@sha256:f99d2bdb02f13223d494077a2cde31418d09369f33c02134a8e7e5fad2f61eda
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-ecosystem-cert-preflight-checks:0.2@sha256:1f151e00f7fc427654b7b76045a426bb02fe650d192ffe147a304d2184787e38
|
||||
- name: kind
|
||||
value: task
|
||||
resolver: bundles
|
||||
|
|
@ -457,7 +457,7 @@ spec:
|
|||
- name: name
|
||||
value: clamav-scan
|
||||
- name: bundle
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-clamav-scan:0.2@sha256:7749146f7e4fe530846f1b15c9366178ec9f44776ef1922a60d3e7e2b8c6426b
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-clamav-scan:0.3@sha256:cce2dfcc5bd6e91ee54aacdadad523b013eeae5cdaa7f6a4624b8cbcc040f439
|
||||
- name: kind
|
||||
value: task
|
||||
resolver: bundles
|
||||
|
|
@ -479,7 +479,7 @@ spec:
|
|||
- name: name
|
||||
value: apply-tags
|
||||
- name: bundle
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-apply-tags:0.2@sha256:517a51e260c0b59654a9d7b842e1ab07d76bce15ca7ce9c8fd2489a19be6463d
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-apply-tags:0.2@sha256:70881c97a4c51ee1f4d023fa1110e0bdfcfd2f51d9a261fa543c3862b9a4eee9
|
||||
- name: kind
|
||||
value: task
|
||||
resolver: bundles
|
||||
|
|
@ -500,7 +500,7 @@ spec:
|
|||
- name: name
|
||||
value: push-dockerfile
|
||||
- name: bundle
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-push-dockerfile:0.1@sha256:794850ad7934523d511ebd4e3ec16f1a811a2fa8729580f00209be174b8a3818
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-push-dockerfile:0.1@sha256:d5cb22a833be51dd72a872cac8bfbe149e8ad34da7cb48a643a1e613447a1f9d
|
||||
- name: kind
|
||||
value: task
|
||||
resolver: bundles
|
||||
|
|
|
|||
35
README.md
35
README.md
|
|
@ -128,19 +128,15 @@ see the [osbuild-getting-started project](https://github.com/osbuild/osbuild-get
|
|||
|
||||
API slice definitions are programmatically generated using the [@rtk-query/codegen-openapi](https://redux-toolkit.js.org/rtk-query/usage/code-generation) package.
|
||||
|
||||
OpenAPI schema for the endpoints are stored in `/api/schema`. Their
|
||||
corresponding configuration files are stored in `/api/config`. Each endpoint
|
||||
has a corresponding empty API slice and generated API slice which are stored in
|
||||
`/src/store`.
|
||||
The OpenAPI schema are imported during code generation. OpenAPI configuration files are
|
||||
stored in `/api/config`. Each endpoint has a corresponding empty API slice and generated API
|
||||
slice which are stored in `/src/store`.
|
||||
|
||||
### Add a new API schema
|
||||
|
||||
For a hypothetical API called foobar
|
||||
|
||||
1. Download the foobar API OpenAPI json or yaml representation under
|
||||
`api/schema/foobar.json`
|
||||
|
||||
2. Create a new "empty" API file under `src/store/emptyFoobarApi.ts` that has following
|
||||
1. Create a new "empty" API file under `src/store/emptyFoobarApi.ts` that has following
|
||||
content:
|
||||
|
||||
```typescript
|
||||
|
|
@ -156,21 +152,21 @@ export const emptyFoobarApi = createApi({
|
|||
});
|
||||
```
|
||||
|
||||
3. Declare new constant `FOOBAR_API` with the API url in `src/constants.ts`
|
||||
2. Declare new constant `FOOBAR_API` with the API url in `src/constants.ts`
|
||||
|
||||
```typescript
|
||||
export const FOOBAR_API = 'api/foobar/v1'
|
||||
```
|
||||
|
||||
4. Create the config file for code generation in `api/config/foobar.ts` containing:
|
||||
3. Create the config file for code generation in `api/config/foobar.ts` containing:
|
||||
|
||||
```typescript
|
||||
import type { ConfigFile } from '@rtk-query/codegen-openapi';
|
||||
|
||||
const config: ConfigFile = {
|
||||
schemaFile: '../schema/foobar.json',
|
||||
schemaFile: 'URL_TO_THE_OPENAPI_SCHEMA',
|
||||
apiFile: '../../src/store/emptyFoobarApi.ts',
|
||||
apiImport: 'emptyEdgeApi',
|
||||
apiImport: 'emptyContentSourcesApi',
|
||||
outputFile: '../../src/store/foobarApi.ts',
|
||||
exportName: 'foobarApi',
|
||||
hooks: true,
|
||||
|
|
@ -178,14 +174,7 @@ const config: ConfigFile = {
|
|||
};
|
||||
```
|
||||
|
||||
5. Update the `api.sh` script by adding a new line for npx to generate the code:
|
||||
|
||||
```bash
|
||||
npx @rtk-query/codegen-openapi ./api/config/foobar.ts &
|
||||
```
|
||||
|
||||
|
||||
6. Update the `eslint.config.js` file by adding the generated code path to the ignores array:
|
||||
4. Update the `eslint.config.js` file by adding the generated code path to the ignores array:
|
||||
|
||||
```
|
||||
ignores: [
|
||||
|
|
@ -194,7 +183,7 @@ ignores: [
|
|||
]
|
||||
```
|
||||
|
||||
7. run api generation
|
||||
5. run api generation
|
||||
|
||||
```bash
|
||||
npm run api
|
||||
|
|
@ -355,12 +344,12 @@ Follow these steps to find and paste the certification file into the 'Keychain A
|
|||
npm ci
|
||||
```
|
||||
|
||||
3. Download the Playwright browsers with
|
||||
3. Download the Playwright browsers with
|
||||
```bash
|
||||
npx playwright install
|
||||
```
|
||||
|
||||
4. Start the local development stage server by running
|
||||
4. Start the local development stage server by running
|
||||
```bash
|
||||
npm run start:stage
|
||||
```
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ npx @rtk-query/codegen-openapi ./api/config/imageBuilder.ts &
|
|||
npx @rtk-query/codegen-openapi ./api/config/rhsm.ts &
|
||||
npx @rtk-query/codegen-openapi ./api/config/contentSources.ts &
|
||||
npx @rtk-query/codegen-openapi ./api/config/provisioning.ts &
|
||||
npx @rtk-query/codegen-openapi ./api/config/edge.ts &
|
||||
npx @rtk-query/codegen-openapi ./api/config/compliance.ts &
|
||||
npx @rtk-query/codegen-openapi ./api/config/composerCloudApi.ts &
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import type { ConfigFile } from '@rtk-query/codegen-openapi';
|
||||
|
||||
const config: ConfigFile = {
|
||||
schemaFile: '../schema/compliance.json',
|
||||
schemaFile: 'https://console.redhat.com/api/compliance/v2/openapi.json',
|
||||
apiFile: '../../src/store/service/emptyComplianceApi.ts',
|
||||
apiImport: 'emptyComplianceApi',
|
||||
outputFile: '../../src/store/service/complianceApi.ts',
|
||||
|
|
|
|||
|
|
@ -1,17 +1,15 @@
|
|||
import type { ConfigFile } from '@rtk-query/codegen-openapi';
|
||||
|
||||
const config: ConfigFile = {
|
||||
schemaFile: '../schema/composerCloudApi.v2.yaml',
|
||||
schemaFile:
|
||||
'https://raw.githubusercontent.com/osbuild/osbuild-composer/main/internal/cloudapi/v2/openapi.v2.yml',
|
||||
apiFile: '../../src/store/cockpit/emptyComposerCloudApi.ts',
|
||||
apiImport: 'emptyComposerCloudApi',
|
||||
outputFile: '../../src/store/cockpit/composerCloudApi.ts',
|
||||
exportName: 'composerCloudApi',
|
||||
hooks: false,
|
||||
unionUndefined: true,
|
||||
filterEndpoints: [
|
||||
'postCompose',
|
||||
'getComposeStatus',
|
||||
],
|
||||
filterEndpoints: ['postCompose', 'getComposeStatus'],
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import type { ConfigFile } from '@rtk-query/codegen-openapi';
|
||||
|
||||
const config: ConfigFile = {
|
||||
schemaFile: '../schema/contentSources.json',
|
||||
schemaFile: 'https://console.redhat.com/api/content-sources/v1/openapi.json',
|
||||
apiFile: '../../src/store/service/emptyContentSourcesApi.ts',
|
||||
apiImport: 'emptyContentSourcesApi',
|
||||
outputFile: '../../src/store/service/contentSourcesApi.ts',
|
||||
|
|
|
|||
|
|
@ -1,36 +0,0 @@
|
|||
import type { ConfigFile } from '@rtk-query/codegen-openapi';
|
||||
|
||||
const config: ConfigFile = {
|
||||
schemaFile: '../schema/edge.json',
|
||||
apiFile: '../../src/store/service/emptyEdgeApi.ts',
|
||||
apiImport: 'emptyEdgeApi',
|
||||
outputFile: '../../src/store/service/edgeApi.ts',
|
||||
exportName: 'edgeApi',
|
||||
hooks: true,
|
||||
unionUndefined: true,
|
||||
filterEndpoints: [
|
||||
'createImage',
|
||||
'createImageUpdate',
|
||||
'getAllImages',
|
||||
'getImageStatusByID',
|
||||
'getImageByID',
|
||||
'getImageDetailsByID',
|
||||
'getImageByOstree',
|
||||
'createInstallerForImage',
|
||||
'getRepoForImage',
|
||||
'getMetadataForImage',
|
||||
'createKickStartForImage',
|
||||
'checkImageName',
|
||||
'retryCreateImage',
|
||||
'listAllImageSets',
|
||||
'getImageSetsByID',
|
||||
'getImageSetsView',
|
||||
'getImageSetViewByID',
|
||||
'getAllImageSetImagesView',
|
||||
'getImageSetsDevicesByID',
|
||||
'deleteImageSet',
|
||||
'getImageSetImageView',
|
||||
],
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
|
@ -1,7 +1,8 @@
|
|||
import type { ConfigFile } from '@rtk-query/codegen-openapi';
|
||||
|
||||
const config: ConfigFile = {
|
||||
schemaFile: '../schema/imageBuilder.yaml',
|
||||
schemaFile:
|
||||
'https://raw.githubusercontent.com/osbuild/image-builder/main/internal/v1/api.yaml',
|
||||
apiFile: '../../src/store/service/emptyImageBuilderApi.ts',
|
||||
apiImport: 'emptyImageBuilderApi',
|
||||
outputFile: '../../src/store/service/imageBuilderApi.ts',
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import type { ConfigFile } from '@rtk-query/codegen-openapi';
|
||||
|
||||
const config: ConfigFile = {
|
||||
schemaFile: '../schema/provisioning.json',
|
||||
schemaFile: 'https://console.redhat.com/api/provisioning/v1/openapi.json',
|
||||
apiFile: '../../src/store/service/emptyProvisioningApi.ts',
|
||||
apiImport: 'emptyProvisioningApi',
|
||||
outputFile: '../../src/store/service/provisioningApi.ts',
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import type { ConfigFile } from '@rtk-query/codegen-openapi';
|
||||
|
||||
const config: ConfigFile = {
|
||||
schemaFile: '../schema/rhsm.json',
|
||||
schemaFile: 'https://console.redhat.com/api/rhsm/v2/openapi.json',
|
||||
apiFile: '../../src/store/service/emptyRhsmApi.ts',
|
||||
apiImport: 'emptyRhsmApi',
|
||||
outputFile: '../../src/store/service/rhsmApi.ts',
|
||||
|
|
|
|||
10
api/pull.sh
10
api/pull.sh
|
|
@ -1,10 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Download the most up-to-date schema files and overwrite the existing ones
|
||||
curl https://raw.githubusercontent.com/osbuild/image-builder/main/internal/v1/api.yaml -o ./api/schema/imageBuilder.yaml
|
||||
curl https://console.redhat.com/api/rhsm/v2/openapi.json -o ./api/schema/rhsm.json
|
||||
curl https://console.redhat.com/api/content-sources/v1/openapi.json -o ./api/schema/contentSources.json
|
||||
curl https://console.redhat.com/api/provisioning/v1/openapi.json -o ./api/schema/provisioning.json
|
||||
curl https://console.redhat.com/api/edge/v1/openapi.json -o ./api/schema/edge.json
|
||||
curl https://console.redhat.com/api/compliance/v2/openapi.json -o ./api/schema/compliance.json
|
||||
curl https://raw.githubusercontent.com/osbuild/osbuild-composer/main/internal/cloudapi/v2/openapi.v2.yml -o ./api/schema/composerCloudApi.v2.yaml
|
||||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
5919
api/schema/edge.json
5919
api/schema/edge.json
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because one or more lines are too long
|
|
@ -1 +1 @@
|
|||
Subproject commit 07aeb0dd02b76bd1c9c309b8ec0e96893e0ebe49
|
||||
Subproject commit b496d0a8c1755608bd256a6960869b14a7689d38
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
Name: cockpit-image-builder
|
||||
Version: 72
|
||||
Version: 76
|
||||
Release: 1%{?dist}
|
||||
Summary: Image builder plugin for Cockpit
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ const pluginImport = require('eslint-plugin-import');
|
|||
const fecConfig = require('@redhat-cloud-services/eslint-config-redhat-cloud-services');
|
||||
const pluginJsxA11y = require('eslint-plugin-jsx-a11y');
|
||||
const disableAutofix = require('eslint-plugin-disable-autofix');
|
||||
const pluginPrettier = require('eslint-plugin-prettier');
|
||||
const jestDom = require('eslint-plugin-jest-dom');
|
||||
const pluginTestingLibrary = require('eslint-plugin-testing-library');
|
||||
const pluginPlaywright = require('eslint-plugin-playwright');
|
||||
|
|
@ -21,7 +22,6 @@ module.exports = defineConfig([
|
|||
'**/contentSourcesApi.ts',
|
||||
'**/rhsmApi.ts',
|
||||
'**/provisioningApi.ts',
|
||||
'**/edgeApi.ts',
|
||||
'**/complianceApi.ts',
|
||||
'**/composerCloudApi.ts'
|
||||
]
|
||||
|
|
@ -62,6 +62,7 @@ module.exports = defineConfig([
|
|||
import: pluginImport,
|
||||
jsxA11y: pluginJsxA11y,
|
||||
'disable-autofix': disableAutofix,
|
||||
prettier: pluginPrettier,
|
||||
},
|
||||
rules: {
|
||||
...js.configs.recommended.rules,
|
||||
|
|
@ -111,8 +112,25 @@ module.exports = defineConfig([
|
|||
'@typescript-eslint/no-require-imports': 'error',
|
||||
'disable-autofix/@typescript-eslint/no-unnecessary-condition': 'warn',
|
||||
'no-unused-vars': 'off', // disable js rule in favor of @typescript-eslint's rule
|
||||
'@typescript-eslint/no-unused-vars': 'warn',
|
||||
'@typescript-eslint/no-unused-vars': [
|
||||
'error',
|
||||
{
|
||||
argsIgnorePattern: '^_',
|
||||
varsIgnorePattern: '^_',
|
||||
},
|
||||
],
|
||||
'jsx-a11y/no-autofocus': 'off',
|
||||
'prettier/prettier': ['error', {
|
||||
semi: true,
|
||||
tabWidth: 2,
|
||||
singleQuote: true,
|
||||
jsxSingleQuote: true,
|
||||
bracketSpacing: true,
|
||||
tsxSingleQuote: true,
|
||||
tsSingleQuote: true,
|
||||
printWidth: 80,
|
||||
trailingComma: 'all',
|
||||
}],
|
||||
},
|
||||
settings: {
|
||||
react: {
|
||||
|
|
@ -145,6 +163,12 @@ module.exports = defineConfig([
|
|||
...pluginPlaywright.configs.recommended.rules,
|
||||
'playwright/no-conditional-in-test': 'off',
|
||||
'playwright/no-conditional-expect': 'off',
|
||||
'playwright/no-skipped-test': [
|
||||
'error',
|
||||
{
|
||||
'allowConditional': true
|
||||
}
|
||||
]
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
|
|
|||
1307
package-lock.json
generated
1307
package-lock.json
generated
File diff suppressed because it is too large
Load diff
56
package.json
56
package.json
|
|
@ -8,17 +8,18 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@ltd/j-toml": "1.38.0",
|
||||
"@patternfly/patternfly": "6.3.0",
|
||||
"@patternfly/react-code-editor": "6.1.0",
|
||||
"@patternfly/react-core": "6.1.0",
|
||||
"@patternfly/react-table": "6.3.0",
|
||||
"@redhat-cloud-services/frontend-components": "6.1.0",
|
||||
"@redhat-cloud-services/frontend-components-notifications": "6.1.0",
|
||||
"@redhat-cloud-services/frontend-components-utilities": "6.1.0",
|
||||
"@patternfly/patternfly": "6.3.1",
|
||||
"@patternfly/react-code-editor": "6.3.1",
|
||||
"@patternfly/react-core": "6.3.1",
|
||||
"@patternfly/react-table": "6.3.1",
|
||||
"@redhat-cloud-services/frontend-components": "7.0.3",
|
||||
"@redhat-cloud-services/frontend-components-notifications": "6.1.5",
|
||||
"@redhat-cloud-services/frontend-components-utilities": "7.0.3",
|
||||
"@redhat-cloud-services/types": "3.0.1",
|
||||
"@reduxjs/toolkit": "2.8.2",
|
||||
"@scalprum/react-core": "0.9.5",
|
||||
"@sentry/webpack-plugin": "3.6.1",
|
||||
"@unleash/proxy-client-react": "5.0.0",
|
||||
"@sentry/webpack-plugin": "4.1.1",
|
||||
"@unleash/proxy-client-react": "5.0.1",
|
||||
"classnames": "2.5.1",
|
||||
"jwt-decode": "4.0.0",
|
||||
"lodash": "4.17.21",
|
||||
|
|
@ -34,25 +35,25 @@
|
|||
"@babel/preset-env": "7.28.0",
|
||||
"@babel/preset-react": "7.27.1",
|
||||
"@babel/preset-typescript": "7.27.1",
|
||||
"@currents/playwright": "1.15.2",
|
||||
"@eslint/js": "9.31.0",
|
||||
"@patternfly/react-icons": "6.1.0",
|
||||
"@currents/playwright": "1.15.3",
|
||||
"@eslint/js": "9.32.0",
|
||||
"@patternfly/react-icons": "6.3.1",
|
||||
"@playwright/test": "1.51.1",
|
||||
"@redhat-cloud-services/eslint-config-redhat-cloud-services": "3.0.0",
|
||||
"@redhat-cloud-services/frontend-components-config": "6.3.8",
|
||||
"@redhat-cloud-services/tsc-transform-imports": "1.0.25",
|
||||
"@rtk-query/codegen-openapi": "2.0.0",
|
||||
"@testing-library/dom": "10.4.0",
|
||||
"@testing-library/jest-dom": "6.6.3",
|
||||
"@testing-library/dom": "10.4.1",
|
||||
"@testing-library/jest-dom": "6.6.4",
|
||||
"@testing-library/react": "16.3.0",
|
||||
"@testing-library/user-event": "14.6.1",
|
||||
"@types/node": "24.0.13",
|
||||
"@types/node": "24.3.0",
|
||||
"@types/react": "18.3.12",
|
||||
"@types/react-dom": "18.3.1",
|
||||
"@types/react-redux": "7.1.34",
|
||||
"@types/uuid": "10.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "8.38.0",
|
||||
"@typescript-eslint/parser": "8.38.0",
|
||||
"@typescript-eslint/eslint-plugin": "8.40.0",
|
||||
"@typescript-eslint/parser": "8.40.0",
|
||||
"@vitejs/plugin-react": "4.7.0",
|
||||
"@vitest/coverage-v8": "3.2.4",
|
||||
"babel-loader": "10.0.0",
|
||||
|
|
@ -61,16 +62,17 @@
|
|||
"chartjs-plugin-annotation": "3.1.0",
|
||||
"copy-webpack-plugin": "13.0.0",
|
||||
"css-loader": "7.1.2",
|
||||
"eslint": "9.30.1",
|
||||
"eslint": "9.33.0",
|
||||
"eslint-plugin-disable-autofix": "5.0.1",
|
||||
"eslint-plugin-import": "2.32.0",
|
||||
"eslint-plugin-jest-dom": "5.5.0",
|
||||
"eslint-plugin-jsx-a11y": "6.10.2",
|
||||
"eslint-plugin-playwright": "2.2.0",
|
||||
"eslint-plugin-playwright": "2.2.2",
|
||||
"eslint-plugin-prettier": "5.5.4",
|
||||
"eslint-plugin-react": "7.37.5",
|
||||
"eslint-plugin-react-hooks": "5.2.0",
|
||||
"eslint-plugin-react-redux": "4.2.2",
|
||||
"eslint-plugin-testing-library": "7.6.0",
|
||||
"eslint-plugin-testing-library": "7.6.6",
|
||||
"git-revision-webpack-plugin": "5.0.0",
|
||||
"globals": "16.3.0",
|
||||
"history": "5.3.0",
|
||||
|
|
@ -79,20 +81,20 @@
|
|||
"madge": "8.0.0",
|
||||
"mini-css-extract-plugin": "2.9.2",
|
||||
"moment": "2.30.1",
|
||||
"msw": "2.10.4",
|
||||
"msw": "2.10.5",
|
||||
"npm-run-all": "4.1.5",
|
||||
"path-browserify": "1.0.1",
|
||||
"postcss-scss": "4.0.9",
|
||||
"react-chartjs-2": "5.3.0",
|
||||
"redux-mock-store": "1.5.5",
|
||||
"sass": "1.89.2",
|
||||
"sass": "1.90.0",
|
||||
"sass-loader": "16.0.5",
|
||||
"stylelint": "16.22.0",
|
||||
"stylelint-config-recommended-scss": "15.0.1",
|
||||
"stylelint": "16.23.1",
|
||||
"stylelint-config-recommended-scss": "16.0.0",
|
||||
"ts-node": "10.9.2",
|
||||
"ts-patch": "3.3.0",
|
||||
"typescript": "5.8.3",
|
||||
"typescript-eslint": "8.38.0",
|
||||
"typescript-eslint": "8.40.0",
|
||||
"uuid": "11.1.0",
|
||||
"vitest": "3.2.4",
|
||||
"vitest-canvas-mock": "0.3.3",
|
||||
|
|
@ -115,9 +117,7 @@
|
|||
"test:cockpit": "src/test/cockpit-tests.sh",
|
||||
"build": "fec build",
|
||||
"build:cockpit": "webpack --config cockpit/webpack.config.ts",
|
||||
"api": "npm-run-all api:pull api:generate",
|
||||
"api:generate": "bash api/codegen.sh",
|
||||
"api:pull": "bash api/pull.sh",
|
||||
"api": "bash api/codegen.sh",
|
||||
"verify": "npm-run-all build lint test",
|
||||
"postinstall": "ts-patch install",
|
||||
"circular": "madge --circular ./src --extensions js,ts,tsx",
|
||||
|
|
|
|||
10
packit.yaml
10
packit.yaml
|
|
@ -16,6 +16,15 @@ srpm_build_deps:
|
|||
- npm
|
||||
|
||||
jobs:
|
||||
- job: tests
|
||||
identifier: self
|
||||
trigger: pull_request
|
||||
tmt_plan: /plans/all/main
|
||||
targets:
|
||||
- centos-stream-10
|
||||
- fedora-41
|
||||
- fedora-42
|
||||
|
||||
- job: copr_build
|
||||
trigger: pull_request
|
||||
targets: &build_targets
|
||||
|
|
@ -24,7 +33,6 @@ jobs:
|
|||
- centos-stream-10
|
||||
- centos-stream-10-aarch64
|
||||
- fedora-all
|
||||
- fedora-all-aarch64
|
||||
|
||||
- job: copr_build
|
||||
trigger: commit
|
||||
|
|
|
|||
14
plans/all.fmf
Normal file
14
plans/all.fmf
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
summary: cockpit-image-builder playwright tests
|
||||
prepare:
|
||||
how: install
|
||||
package:
|
||||
- cockpit-image-builder
|
||||
discover:
|
||||
how: fmf
|
||||
execute:
|
||||
how: tmt
|
||||
|
||||
/main:
|
||||
summary: playwright tests
|
||||
discover+:
|
||||
test: /schutzbot/playwright
|
||||
214
playwright/Customizations/AAP.spec.ts
Normal file
214
playwright/Customizations/AAP.spec.ts
Normal file
|
|
@ -0,0 +1,214 @@
|
|||
import { expect } from '@playwright/test';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import { test } from '../fixtures/customizations';
|
||||
import { isHosted } from '../helpers/helpers';
|
||||
import { ensureAuthenticated } from '../helpers/login';
|
||||
import {
|
||||
ibFrame,
|
||||
navigateToLandingPage,
|
||||
navigateToOptionalSteps,
|
||||
} from '../helpers/navHelpers';
|
||||
import {
|
||||
createBlueprint,
|
||||
deleteBlueprint,
|
||||
exportBlueprint,
|
||||
fillInDetails,
|
||||
fillInImageOutputGuest,
|
||||
importBlueprint,
|
||||
registerLater,
|
||||
} from '../helpers/wizardHelpers';
|
||||
|
||||
const validCallbackUrl =
|
||||
'https://controller.url/api/controller/v2/job_templates/9/callback/';
|
||||
const validHttpCallbackUrl =
|
||||
'http://controller.url/api/controller/v2/job_templates/9/callback/';
|
||||
const validHostConfigKey = 'hostconfigkey';
|
||||
const validCertificate = `-----BEGIN CERTIFICATE-----
|
||||
MIIDXTCCAkWgAwIBAgIJAOEzx5ezZ9EIMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV
|
||||
BAYTAklOMQswCQYDVQQIDAJLUjEMMAoGA1UEBwwDS1JHMRAwDgYDVQQKDAdUZXN0
|
||||
IENBMB4XDTI1MDUxNTEyMDAwMFoXDTI2MDUxNTEyMDAwMFowRTELMAkGA1UEBhMC
|
||||
SU4xCzAJBgNVBAgMAktSMQwwCgYDVQQHDANSR0sxEDAOBgNVBAoMB1Rlc3QgQ0Ew
|
||||
ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC+R4gfN5pyJQo5qBTTtN+7
|
||||
eE9CSXZJ8SVVaE3U54IgqQoqsSoBY5QtExy7v5C6l6mW4E6dzK/JecmvTTO/BvlG
|
||||
A5k2hxB6bOQxtxYwfgElH+RFWN9P4xxhtEiQgHoG1rDfnXuDJk1U3YEkCQELUebz
|
||||
fF3EIDU1yR0Sz2bA+Sl2VXe8og1MEZfytq8VZUVltxtn2PfW7zI5gOllBR2sKeUc
|
||||
K6h8HXN7qMgfEvsLIXxTw7fU/zA3ibcxfRCl3m6QhF8hwRh6F9Wtz2s8hCzGegV5
|
||||
z0M39nY7X8C3GZQ4Ly8v8DdY+FbEix7K3SSBRbWtdPfAHRFlX9Er2Wf8DAr7O2hH
|
||||
AgMBAAGjUDBOMB0GA1UdDgQWBBTXXz2eIDgK+BhzDUAGzptn0OMcpDAfBgNVHSME
|
||||
GDAWgBTXXz2eIDgK+BhzDUAGzptn0OMcpDAMBgNVHRMEBTADAQH/MA0GCSqGSIb3
|
||||
DQEBCwUAA4IBAQAoUgY4jsuBMB3el9cc7JS2rcOhhJzn47Hj2UANfJq52g5lbjo7
|
||||
XDc7Wb3VDcV+1LzjdzayT1qO1WzHb6FDPW9L9f6h4s8lj6MvJ+xhOWgD11srdIt3
|
||||
vbQaQW4zDfeVRcKXzqbcUX8BLXAdzJPqVwZ+Z4EDjYrJ7lF9k+IqfZm0MsYX7el9
|
||||
kvdRHbLuF4Q0sZ05CXMFkhM0Ulhu4MZ+1FcsQa7nWfZzTmbjHOuWJPB4z5WwrB7z
|
||||
U8YYvWJ3qxToWGbATqJxkRKGGqLrNrmwcfzgPqkpuCRYi0Kky6gJ1RvL+DRopY9x
|
||||
uD+ckf3oH2wYAB6RpPRMkfVxe7lGMvq/yEZ6
|
||||
-----END CERTIFICATE-----`;
|
||||
const invalidCertificate = `-----BEGIN CERTIFICATE-----
|
||||
ThisIs*Not+Valid/Base64==
|
||||
-----END CERTIFICATE-----`;
|
||||
|
||||
test('Create a blueprint with AAP registration customization', async ({
|
||||
page,
|
||||
cleanup,
|
||||
}) => {
|
||||
const blueprintName = 'test-' + uuidv4();
|
||||
|
||||
// Skip entirely in Cockpit/on-premise where AAP customization is unavailable
|
||||
test.skip(!isHosted(), 'AAP customization is not available in the plugin');
|
||||
|
||||
// Delete the blueprint after the run fixture
|
||||
await cleanup.add(() => deleteBlueprint(page, blueprintName));
|
||||
await ensureAuthenticated(page);
|
||||
|
||||
// Navigate to IB landing page and get the frame
|
||||
await navigateToLandingPage(page);
|
||||
const frame = await ibFrame(page);
|
||||
|
||||
await test.step('Navigate to optional steps in Wizard', async () => {
|
||||
await navigateToOptionalSteps(frame);
|
||||
await registerLater(frame);
|
||||
});
|
||||
|
||||
await test.step('Select and fill the AAP step with valid configuration', async () => {
|
||||
await frame
|
||||
.getByRole('button', { name: 'Ansible Automation Platform' })
|
||||
.click();
|
||||
await frame
|
||||
.getByRole('textbox', { name: 'ansible callback url' })
|
||||
.fill(validCallbackUrl);
|
||||
await frame
|
||||
.getByRole('textbox', { name: 'host config key' })
|
||||
.fill(validHostConfigKey);
|
||||
await frame
|
||||
.getByRole('textbox', { name: 'File upload' })
|
||||
.fill(validCertificate);
|
||||
await expect(frame.getByRole('button', { name: 'Next' })).toBeEnabled();
|
||||
});
|
||||
|
||||
await test.step('Test TLS confirmation checkbox for HTTPS URLs', async () => {
|
||||
// TLS confirmation checkbox should appear for HTTPS URLs
|
||||
await expect(
|
||||
frame.getByRole('checkbox', {
|
||||
name: 'Insecure',
|
||||
}),
|
||||
).toBeVisible();
|
||||
|
||||
// Check TLS confirmation and verify CA input is hidden
|
||||
await frame
|
||||
.getByRole('checkbox', {
|
||||
name: 'Insecure',
|
||||
})
|
||||
.check();
|
||||
await expect(
|
||||
frame.getByRole('textbox', { name: 'File upload' }),
|
||||
).toBeHidden();
|
||||
|
||||
await frame
|
||||
.getByRole('checkbox', {
|
||||
name: 'Insecure',
|
||||
})
|
||||
.uncheck();
|
||||
|
||||
await expect(
|
||||
frame.getByRole('textbox', { name: 'File upload' }),
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('Test certificate validation', async () => {
|
||||
await frame.getByRole('textbox', { name: 'File upload' }).clear();
|
||||
await frame
|
||||
.getByRole('textbox', { name: 'File upload' })
|
||||
.fill(invalidCertificate);
|
||||
await expect(frame.getByText(/Certificate.*is not valid/)).toBeVisible();
|
||||
|
||||
await frame.getByRole('textbox', { name: 'File upload' }).clear();
|
||||
await frame
|
||||
.getByRole('textbox', { name: 'File upload' })
|
||||
.fill(validCertificate);
|
||||
|
||||
await expect(frame.getByText('Certificate was uploaded')).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('Test HTTP URL behavior', async () => {
|
||||
await frame.getByRole('textbox', { name: 'ansible callback url' }).clear();
|
||||
await frame
|
||||
.getByRole('textbox', { name: 'ansible callback url' })
|
||||
.fill(validHttpCallbackUrl);
|
||||
|
||||
// TLS confirmation checkbox should NOT appear for HTTP URLs
|
||||
await expect(
|
||||
frame.getByRole('checkbox', {
|
||||
name: 'Insecure',
|
||||
}),
|
||||
).toBeHidden();
|
||||
await expect(
|
||||
frame.getByRole('textbox', { name: 'File upload' }),
|
||||
).toBeVisible();
|
||||
|
||||
await frame.getByRole('textbox', { name: 'ansible callback url' }).clear();
|
||||
await frame
|
||||
.getByRole('textbox', { name: 'ansible callback url' })
|
||||
.fill(validCallbackUrl);
|
||||
});
|
||||
|
||||
await test.step('Complete AAP configuration and proceed to review', async () => {
|
||||
await frame.getByRole('button', { name: 'Review and finish' }).click();
|
||||
});
|
||||
|
||||
await test.step('Fill the BP details', async () => {
|
||||
await fillInDetails(frame, blueprintName);
|
||||
});
|
||||
|
||||
await test.step('Create BP', async () => {
|
||||
await createBlueprint(frame, blueprintName);
|
||||
});
|
||||
|
||||
await test.step('Edit BP and verify AAP configuration persists', async () => {
|
||||
await frame.getByRole('button', { name: 'Edit blueprint' }).click();
|
||||
await frame.getByLabel('Revisit Ansible Automation Platform step').click();
|
||||
|
||||
await expect(
|
||||
frame.getByRole('textbox', { name: 'ansible callback url' }),
|
||||
).toHaveValue(validCallbackUrl);
|
||||
await expect(
|
||||
frame.getByRole('textbox', { name: 'host config key' }),
|
||||
).toHaveValue(validHostConfigKey);
|
||||
await expect(
|
||||
frame.getByRole('textbox', { name: 'File upload' }),
|
||||
).toHaveValue(validCertificate);
|
||||
|
||||
await frame.getByRole('button', { name: 'Review and finish' }).click();
|
||||
await frame
|
||||
.getByRole('button', { name: 'Save changes to blueprint' })
|
||||
.click();
|
||||
});
|
||||
// This is for hosted service only as these features are not available in cockpit plugin
|
||||
await test.step('Export BP', async (step) => {
|
||||
step.skip(!isHosted(), 'Exporting is not available in the plugin');
|
||||
await exportBlueprint(page, blueprintName);
|
||||
});
|
||||
|
||||
await test.step('Import BP', async (step) => {
|
||||
step.skip(!isHosted(), 'Importing is not available in the plugin');
|
||||
await importBlueprint(page, blueprintName);
|
||||
});
|
||||
|
||||
await test.step('Review imported BP', async (step) => {
|
||||
step.skip(!isHosted(), 'Importing is not available in the plugin');
|
||||
await fillInImageOutputGuest(page);
|
||||
await page
|
||||
.getByRole('button', { name: 'Ansible Automation Platform' })
|
||||
.click();
|
||||
await expect(
|
||||
page.getByRole('textbox', { name: 'ansible callback url' }),
|
||||
).toHaveValue(validCallbackUrl);
|
||||
await expect(
|
||||
page.getByRole('textbox', { name: 'host config key' }),
|
||||
).toBeEmpty();
|
||||
await expect(
|
||||
page.getByRole('textbox', { name: 'File upload' }),
|
||||
).toHaveValue(validCertificate);
|
||||
await page.getByRole('button', { name: 'Cancel' }).click();
|
||||
});
|
||||
});
|
||||
|
|
@ -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();
|
||||
});
|
||||
|
|
|
|||
189
playwright/Customizations/OpenSCAP.spec.ts
Normal file
189
playwright/Customizations/OpenSCAP.spec.ts
Normal file
|
|
@ -0,0 +1,189 @@
|
|||
import { expect } from '@playwright/test';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import { test } from '../fixtures/cleanup';
|
||||
import { isHosted } from '../helpers/helpers';
|
||||
import { ensureAuthenticated } from '../helpers/login';
|
||||
import { ibFrame, navigateToLandingPage } from '../helpers/navHelpers';
|
||||
import {
|
||||
createBlueprint,
|
||||
deleteBlueprint,
|
||||
exportBlueprint,
|
||||
fillInDetails,
|
||||
fillInImageOutputGuest,
|
||||
importBlueprint,
|
||||
registerLater,
|
||||
} from '../helpers/wizardHelpers';
|
||||
|
||||
test('Create a blueprint with OpenSCAP customization', async ({
|
||||
page,
|
||||
cleanup,
|
||||
}) => {
|
||||
const blueprintName = 'test-' + uuidv4();
|
||||
test.skip(!isHosted(), 'Exporting is not available in the plugin');
|
||||
// Delete the blueprint after the run fixture
|
||||
await cleanup.add(() => deleteBlueprint(page, blueprintName));
|
||||
|
||||
await ensureAuthenticated(page);
|
||||
|
||||
// Navigate to IB landing page and get the frame
|
||||
await navigateToLandingPage(page);
|
||||
const frame = await ibFrame(page);
|
||||
|
||||
await test.step('Select RHEL 9 and go to optional steps in Wizard', async () => {
|
||||
await frame.getByRole('button', { name: 'Create image blueprint' }).click();
|
||||
await frame.getByTestId('release_select').click();
|
||||
await frame
|
||||
.getByRole('option', {
|
||||
name: 'Red Hat Enterprise Linux (RHEL) 9 Full support ends: May 2027 | Maintenance',
|
||||
})
|
||||
.click();
|
||||
await frame.getByRole('checkbox', { name: 'Virtualization' }).click();
|
||||
await frame.getByRole('button', { name: 'Next' }).click();
|
||||
await registerLater(frame);
|
||||
});
|
||||
|
||||
await test.step('Select only OpenSCAP, and check if dependencies are preselected', async () => {
|
||||
await frame.getByRole('button', { name: 'Compliance' }).click();
|
||||
await frame.getByRole('textbox', { name: 'Type to filter' }).fill('cis');
|
||||
await frame
|
||||
.getByRole('option', {
|
||||
name: 'CIS Red Hat Enterprise Linux 9 Benchmark for Level 1 - Server This profile',
|
||||
})
|
||||
.click();
|
||||
await frame
|
||||
.getByRole('button', { name: 'File system configuration' })
|
||||
.click();
|
||||
await expect(
|
||||
frame
|
||||
.getByRole('row', {
|
||||
name: 'Draggable row draggable button /tmp xfs 1 GiB',
|
||||
})
|
||||
.getByRole('button')
|
||||
.nth(3),
|
||||
).toBeVisible();
|
||||
await frame.getByRole('button', { name: 'Additional packages' }).click();
|
||||
await frame.getByRole('button', { name: 'Selected (8)' }).click();
|
||||
await expect(frame.getByRole('gridcell', { name: 'aide' })).toBeVisible();
|
||||
await expect(frame.getByRole('gridcell', { name: 'chrony' })).toBeVisible();
|
||||
await expect(
|
||||
frame.getByRole('gridcell', { name: 'firewalld' }),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
frame.getByRole('gridcell', { name: 'libpwquality' }),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
frame.getByRole('gridcell', { name: 'libselinux' }),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
frame.getByRole('gridcell', { name: 'nftables' }),
|
||||
).toBeVisible();
|
||||
await expect(frame.getByRole('gridcell', { name: 'sudo' })).toBeVisible();
|
||||
await expect(
|
||||
frame.getByRole('gridcell', { name: 'systemd-journal-remote' }),
|
||||
).toBeVisible();
|
||||
await frame.getByRole('button', { name: 'Systemd services' }).click();
|
||||
await expect(
|
||||
frame.getByText('Required by OpenSCAPcrondfirewalldsystemd-journald'),
|
||||
).toBeVisible();
|
||||
await frame.getByPlaceholder('Add masked service').fill('nftables');
|
||||
await frame.getByPlaceholder('Add masked service').press('Enter');
|
||||
await expect(
|
||||
frame.getByText('Masked service already exists'),
|
||||
).toBeVisible();
|
||||
await expect(frame.getByText('Required by OpenSCAPcupsnfs-')).toBeVisible();
|
||||
await expect(frame.getByText('nfs-server')).toBeVisible();
|
||||
await expect(frame.getByText('rpcbind')).toBeVisible();
|
||||
await expect(frame.getByText('avahi-daemon')).toBeVisible();
|
||||
await expect(frame.getByText('autofs')).toBeVisible();
|
||||
await expect(frame.getByText('bluetooth')).toBeVisible();
|
||||
await expect(frame.getByText('nftables')).toBeVisible();
|
||||
await frame.getByRole('button', { name: 'Review and finish' }).click();
|
||||
});
|
||||
|
||||
await test.step('Fill the BP details', async () => {
|
||||
await fillInDetails(frame, blueprintName);
|
||||
});
|
||||
|
||||
await test.step('Create BP', async () => {
|
||||
await createBlueprint(frame, blueprintName);
|
||||
});
|
||||
|
||||
await test.step('Edit BP', async () => {
|
||||
await frame.getByRole('button', { name: 'Edit blueprint' }).click();
|
||||
await frame.getByRole('button', { name: 'Compliance' }).click();
|
||||
await expect(frame.getByText('Level 1 - Server')).toBeVisible();
|
||||
await frame.getByRole('textbox', { name: 'Type to filter' }).fill('cis');
|
||||
await frame
|
||||
.getByRole('option', {
|
||||
name: 'CIS Red Hat Enterprise Linux 9 Benchmark for Level 2 - Server This profile',
|
||||
})
|
||||
.click();
|
||||
|
||||
await frame.getByRole('button', { name: 'Kernel' }).click();
|
||||
|
||||
await expect(
|
||||
frame.getByText('Required by OpenSCAPaudit_backlog_limit=8192audit='),
|
||||
).toBeVisible();
|
||||
await frame.getByRole('button', { name: 'Additional packages' }).click();
|
||||
await frame.getByRole('button', { name: 'Selected (10)' }).click();
|
||||
await expect(frame.getByRole('gridcell', { name: 'aide' })).toBeVisible();
|
||||
await expect(
|
||||
frame.getByRole('gridcell', { name: 'audit-libs' }),
|
||||
).toBeVisible();
|
||||
await expect(frame.getByRole('gridcell', { name: 'chrony' })).toBeVisible();
|
||||
await expect(
|
||||
frame.getByRole('gridcell', { name: 'firewalld' }),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
frame.getByRole('gridcell', { name: 'libpwquality' }),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
frame.getByRole('gridcell', { name: 'libselinux' }),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
frame.getByRole('gridcell', { name: 'nftables' }),
|
||||
).toBeVisible();
|
||||
await expect(frame.getByRole('gridcell', { name: 'sudo' })).toBeVisible();
|
||||
await frame.getByRole('button', { name: 'Systemd services' }).click();
|
||||
await expect(
|
||||
frame.getByText(
|
||||
'Required by OpenSCAPauditdcrondfirewalldsystemd-journald',
|
||||
),
|
||||
).toBeVisible();
|
||||
await frame.getByPlaceholder('Add masked service').fill('nftables');
|
||||
await frame.getByPlaceholder('Add masked service').press('Enter');
|
||||
await expect(
|
||||
frame.getByText('Masked service already exists'),
|
||||
).toBeVisible();
|
||||
await expect(frame.getByText('Required by OpenSCAPcupsnfs-')).toBeVisible();
|
||||
await expect(frame.getByText('nfs-server')).toBeVisible();
|
||||
await expect(frame.getByText('rpcbind')).toBeVisible();
|
||||
await expect(frame.getByText('avahi-daemon')).toBeVisible();
|
||||
await expect(frame.getByText('autofs')).toBeVisible();
|
||||
await expect(frame.getByText('bluetooth')).toBeVisible();
|
||||
await expect(frame.getByText('nftables')).toBeVisible();
|
||||
await frame.getByRole('button', { name: 'Review and finish' }).click();
|
||||
await frame
|
||||
.getByRole('button', { name: 'Save changes to blueprint' })
|
||||
.click();
|
||||
});
|
||||
|
||||
// This is for hosted service only as these features are not available in cockpit plugin
|
||||
await test.step('Export BP', async () => {
|
||||
await exportBlueprint(page, blueprintName);
|
||||
});
|
||||
|
||||
await test.step('Import BP', async () => {
|
||||
await importBlueprint(page, blueprintName);
|
||||
});
|
||||
|
||||
await test.step('Review imported BP', async () => {
|
||||
await fillInImageOutputGuest(page);
|
||||
await page.getByRole('button', { name: 'Compliance' }).click();
|
||||
|
||||
await expect(frame.getByText('Level 2 - Server')).toBeVisible();
|
||||
|
||||
await page.getByRole('button', { name: 'Cancel' }).click();
|
||||
});
|
||||
});
|
||||
|
|
@ -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,3 +1,6 @@
|
|||
import { execSync } from 'child_process';
|
||||
import { readFileSync } from 'node:fs';
|
||||
|
||||
import { expect, type Page } from '@playwright/test';
|
||||
|
||||
export const togglePreview = async (page: Page) => {
|
||||
|
|
@ -42,3 +45,43 @@ export const closePopupsIfExist = async (page: Page) => {
|
|||
});
|
||||
}
|
||||
};
|
||||
|
||||
// copied over from constants
|
||||
const ON_PREM_RELEASES = new Map([
|
||||
['centos-10', 'CentOS Stream 10'],
|
||||
['fedora-41', 'Fedora Linux 41'],
|
||||
['fedora-42', 'Fedora Linux 42'],
|
||||
['rhel-10', 'Red Hat Enterprise Linux (RHEL) 10'],
|
||||
]);
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
export const getHostDistroName = (): string => {
|
||||
const osRelData = readFileSync('/etc/os-release');
|
||||
const lines = osRelData
|
||||
.toString('utf-8')
|
||||
.split('\n')
|
||||
.filter((l) => l !== '');
|
||||
const osRel = {};
|
||||
|
||||
for (const l of lines) {
|
||||
const lineData = l.split('=');
|
||||
(osRel as any)[lineData[0]] = lineData[1].replace(/"/g, '');
|
||||
}
|
||||
|
||||
// strip minor version from rhel
|
||||
const distro = ON_PREM_RELEASES.get(
|
||||
`${(osRel as any)['ID']}-${(osRel as any)['VERSION_ID'].split('.')[0]}`,
|
||||
);
|
||||
|
||||
if (distro === undefined) {
|
||||
/* eslint-disable no-console */
|
||||
console.error('getHostDistroName failed, os-release config:', osRel);
|
||||
throw new Error('getHostDistroName failed, distro undefined');
|
||||
}
|
||||
|
||||
return distro;
|
||||
};
|
||||
|
||||
export const getHostArch = (): string => {
|
||||
return execSync('uname -m').toString('utf-8').replace(/\s/g, '');
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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 type { FrameLocator, Page } from '@playwright/test';
|
||||
import { expect, FrameLocator, Page } from '@playwright/test';
|
||||
|
||||
import { isHosted } from './helpers';
|
||||
import { getHostArch, getHostDistroName, isHosted } from './helpers';
|
||||
|
||||
/**
|
||||
* Opens the wizard, fills out the "Image Output" step, and navigates to the optional steps
|
||||
|
|
@ -8,6 +8,13 @@ import { isHosted } from './helpers';
|
|||
*/
|
||||
export const navigateToOptionalSteps = async (page: Page | FrameLocator) => {
|
||||
await page.getByRole('button', { name: 'Create image blueprint' }).click();
|
||||
if (!isHosted()) {
|
||||
// wait until the distro and architecture aligns with the host
|
||||
await expect(page.getByTestId('release_select')).toHaveText(
|
||||
getHostDistroName(),
|
||||
);
|
||||
await expect(page.getByTestId('arch_select')).toHaveText(getHostArch());
|
||||
}
|
||||
await page.getByRole('checkbox', { name: 'Virtualization' }).click();
|
||||
await page.getByRole('button', { name: 'Next' }).click();
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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,6 +72,11 @@ test.describe.serial('test', () => {
|
|||
frame.getByRole('heading', { name: 'Systemd services' });
|
||||
await frame.getByRole('button', { name: 'Next', exact: true }).click();
|
||||
|
||||
if (isHosted()) {
|
||||
frame.getByRole('heading', { name: 'Ansible Automation Platform' });
|
||||
await frame.getByRole('button', { name: 'Next', exact: true }).click();
|
||||
}
|
||||
|
||||
if (isHosted()) {
|
||||
frame.getByRole('heading', { name: 'First boot configuration' });
|
||||
await frame.getByRole('button', { name: 'Next', exact: true }).click();
|
||||
|
|
@ -87,7 +92,12 @@ test.describe.serial('test', () => {
|
|||
await frame.getByRole('button', { name: 'Create blueprint' }).click();
|
||||
|
||||
await expect(
|
||||
frame.locator('.pf-v6-c-card__title-text').getByText(blueprintName)
|
||||
frame.locator('.pf-v6-c-card__title-text').getByText(
|
||||
// if the name is too long, the blueprint card will have a truncated name.
|
||||
blueprintName.length > 24
|
||||
? blueprintName.slice(0, 24) + '...'
|
||||
: blueprintName,
|
||||
),
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
|
|
@ -121,6 +131,7 @@ test.describe.serial('test', () => {
|
|||
await frame.getByRole('button', { name: 'Review and finish' }).click();
|
||||
await frame.getByRole('button', { name: 'About packages' }).click();
|
||||
frame.getByRole('gridcell', { name: 'osbuild-composer' });
|
||||
await frame.getByRole('button', { name: 'Close', exact: true }).click();
|
||||
await frame
|
||||
.getByRole('button', { name: 'Save changes to blueprint' })
|
||||
.click();
|
||||
|
|
@ -128,6 +139,7 @@ test.describe.serial('test', () => {
|
|||
await frame.getByRole('button', { name: 'Edit blueprint' }).click();
|
||||
await frame.getByRole('button', { name: 'About packages' }).click();
|
||||
frame.getByRole('gridcell', { name: 'osbuild-composer' });
|
||||
await frame.getByRole('button', { name: 'Close', exact: true }).click();
|
||||
await frame.getByRole('button', { name: 'Cancel', exact: true }).click();
|
||||
frame.getByRole('heading', { name: 'All images' });
|
||||
});
|
||||
|
|
@ -196,14 +208,16 @@ test.describe.serial('test', () => {
|
|||
const switchInput = frame.locator('#aws-config-switch');
|
||||
await expect(switchInput).toBeVisible();
|
||||
|
||||
// the test is a little bit flaky, if it fails the first time
|
||||
// while setting the configs, the second time the config should
|
||||
// already be loaded and visible, if it is go back to the landing page
|
||||
// https://github.com/osbuild/image-builder-frontend/issues/3429
|
||||
// introduce a wait time, since it takes some time to load the
|
||||
// worker config file.
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
// If this test fails for any reason, the config should already be loaded
|
||||
// and visible on the retury. If it is go back to the landing page
|
||||
if (await switchInput.isChecked()) {
|
||||
await frame.getByRole('button', { name: 'Cancel' }).click();
|
||||
await expect(
|
||||
frame.getByRole('heading', { name: 'All images' })
|
||||
frame.getByRole('heading', { name: 'All images' }),
|
||||
).toBeVisible();
|
||||
} else {
|
||||
const switchToggle = frame.locator('.pf-v6-c-switch');
|
||||
|
|
@ -217,27 +231,84 @@ test.describe.serial('test', () => {
|
|||
await frame.getByPlaceholder('Path to AWS credentials').fill(credentials);
|
||||
await frame.getByRole('button', { name: 'Submit' }).click();
|
||||
await expect(
|
||||
frame.getByRole('heading', { name: 'All images' })
|
||||
frame.getByRole('heading', { name: 'All images' }),
|
||||
).toBeVisible();
|
||||
}
|
||||
|
||||
await frame
|
||||
.getByRole('button', { name: 'Configure Cloud Providers' })
|
||||
.click();
|
||||
await expect(header).toBeVisible();
|
||||
|
||||
// introduce a wait time, since it takes some time to load the
|
||||
// worker config file.
|
||||
await page.waitForTimeout(1500);
|
||||
|
||||
await expect(frame.locator('#aws-config-switch')).toBeChecked();
|
||||
|
||||
await expect(frame.getByPlaceholder('AWS bucket')).toHaveValue(bucket);
|
||||
await expect(frame.getByPlaceholder('Path to AWS credentials')).toHaveValue(
|
||||
credentials,
|
||||
);
|
||||
await frame.getByRole('button', { name: 'Cancel' }).click();
|
||||
|
||||
const config = readFileSync('/etc/osbuild-worker/osbuild-worker.toml');
|
||||
// this is for testing, the field `aws` should exist
|
||||
// eslint-disable-next-line
|
||||
const parsed = TOML.parse(config) as any;
|
||||
expect(parsed.aws?.bucket).toBe(bucket);
|
||||
expect(parsed.aws?.credentials).toBe(credentials);
|
||||
});
|
||||
|
||||
const cockpitBlueprintname = uuidv4();
|
||||
test('cockpit cloud upload', async ({ page }) => {
|
||||
if (isHosted()) {
|
||||
return;
|
||||
}
|
||||
|
||||
await ensureAuthenticated(page);
|
||||
await closePopupsIfExist(page);
|
||||
// Navigate to IB landing page and get the frame
|
||||
await navigateToLandingPage(page);
|
||||
await page.goto('/cockpit-image-builder');
|
||||
const frame = ibFrame(page);
|
||||
|
||||
frame.getByRole('heading', { name: 'Images About image builder' });
|
||||
frame.getByRole('heading', { name: 'Blueprints' });
|
||||
await frame.getByTestId('blueprints-create-button').click();
|
||||
|
||||
frame.getByRole('heading', { name: 'Image output' });
|
||||
// the first card should be the AWS card
|
||||
await frame.locator('.pf-v6-c-card').first().click();
|
||||
await frame.getByRole('button', { name: 'Next', exact: true }).click();
|
||||
await frame.getByRole('button', { name: 'Next', exact: true }).click();
|
||||
await frame.getByRole('button', { name: 'Review and finish' }).click();
|
||||
await frame.getByRole('button', { name: 'Back', exact: true }).click();
|
||||
|
||||
frame.getByRole('heading', { name: 'Details' });
|
||||
await frame.getByTestId('blueprint').fill(cockpitBlueprintname);
|
||||
await expect(frame.getByTestId('blueprint')).toHaveValue(
|
||||
cockpitBlueprintname,
|
||||
);
|
||||
await frame.getByRole('button', { name: 'Next', exact: true }).click();
|
||||
|
||||
await frame.getByRole('button', { name: 'Create blueprint' }).click();
|
||||
await frame.getByTestId('close-button-saveandbuild-modal').click();
|
||||
await frame.getByRole('button', { name: 'Create blueprint' }).click();
|
||||
|
||||
await frame
|
||||
.getByRole('button', { name: 'Configure Cloud Providers' })
|
||||
.click();
|
||||
await expect(header).toBeVisible();
|
||||
await expect(frame.locator('#aws-config-switch')).toBeChecked();
|
||||
.getByRole('textbox', { name: 'Search input' })
|
||||
.fill(cockpitBlueprintname);
|
||||
// the clickable blueprint cards are a bit awkward, so use the
|
||||
// button's id instead
|
||||
await frame.locator(`button[id="${cockpitBlueprintname}"]`).click();
|
||||
await frame.getByTestId('blueprint-build-image-menu-option').click();
|
||||
|
||||
await expect(frame.getByPlaceholder('AWS bucket')).toHaveValue(bucket);
|
||||
await expect(frame.getByPlaceholder('Path to AWS credentials')).toHaveValue(
|
||||
credentials
|
||||
);
|
||||
await frame.getByRole('button', { name: 'Cancel' }).click();
|
||||
// make sure the image is present
|
||||
await frame
|
||||
.getByTestId('images-table')
|
||||
.getByRole('button', { name: 'Details' })
|
||||
.click();
|
||||
frame.getByText('Build Information');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
8
schutzbot/playwright.fmf
Normal file
8
schutzbot/playwright.fmf
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
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
|
||||
|
||||
# As playwright isn't supported on fedora/el, install dependencies
|
||||
# beforehand.
|
||||
sudo dnf install -y \
|
||||
alsa-lib \
|
||||
libXrandr-devel \
|
||||
libXdamage-devel \
|
||||
libXcomposite-devel \
|
||||
at-spi2-atk-devel \
|
||||
cups \
|
||||
atk
|
||||
TMT_SOURCE_DIR=${TMT_SOURCE_DIR:-}
|
||||
if [ -n "$TMT_SOURCE_DIR" ]; then
|
||||
# Move to the directory with sources
|
||||
cd "${TMT_SOURCE_DIR}/cockpit-image-builder"
|
||||
npm ci
|
||||
elif [ "${CI:-}" != "true" ]; then
|
||||
# packit drops us into the schutzbot directory
|
||||
cd ../
|
||||
npm ci
|
||||
fi
|
||||
|
||||
sudo systemctl enable --now cockpit.socket
|
||||
|
||||
|
|
@ -19,10 +19,13 @@ sudo usermod -aG wheel admin
|
|||
echo "admin ALL=(ALL:ALL) NOPASSWD: ALL" | sudo tee "/etc/sudoers.d/admin-nopasswd"
|
||||
|
||||
function upload_artifacts {
|
||||
mkdir -p /tmp/artifacts/extra-screenshots
|
||||
USER="$(whoami)"
|
||||
sudo chown -R "$USER:$USER" playwright-report
|
||||
mv playwright-report /tmp/artifacts/
|
||||
if [ -n "${TMT_TEST_DATA:-}" ]; then
|
||||
mv playwright-report "$TMT_TEST_DATA"/playwright-report
|
||||
else
|
||||
USER="$(whoami)"
|
||||
sudo chown -R "$USER:$USER" playwright-report
|
||||
mv playwright-report /tmp/artifacts/
|
||||
fi
|
||||
}
|
||||
trap upload_artifacts EXIT
|
||||
|
||||
|
|
@ -73,11 +76,12 @@ sudo podman run \
|
|||
-e "CI=true" \
|
||||
-e "PLAYWRIGHT_USER=admin" \
|
||||
-e "PLAYWRIGHT_PASSWORD=foobar" \
|
||||
-e "CURRENTS_PROJECT_ID=$CURRENTS_PROJECT_ID" \
|
||||
-e "CURRENTS_RECORD_KEY=$CURRENTS_RECORD_KEY" \
|
||||
-e "CURRENTS_PROJECT_ID=${CURRENTS_PROJECT_ID:-}" \
|
||||
-e "CURRENTS_RECORD_KEY=${CURRENTS_RECORD_KEY:-}" \
|
||||
--net=host \
|
||||
-v "$PWD:/tests" \
|
||||
-v '/etc:/etc' \
|
||||
-v '/etc/os-release:/etc/os-release' \
|
||||
--privileged \
|
||||
--rm \
|
||||
--init \
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
7b4735d287dd0950e0a6f47dde65b62b0f239da1
|
||||
cf0a810fd3b75fa27139746c4dfe72222e13dcba
|
||||
|
|
|
|||
|
|
@ -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,11 +50,21 @@ const BlueprintCard = ({ blueprint }: blueprintProps) => {
|
|||
onChange: () => dispatch(setBlueprintId(blueprint.id)),
|
||||
}}
|
||||
>
|
||||
<CardTitle>
|
||||
<CardTitle aria-label={blueprint.name}>
|
||||
{isLoading && blueprint.id === selectedBlueprintId && (
|
||||
<Spinner size="md" />
|
||||
<Spinner size='md' />
|
||||
)}
|
||||
{blueprint.name}
|
||||
{
|
||||
// NOTE: This might be an issue with the pf6 truncate component.
|
||||
// Since we're not really using the popover, we can just
|
||||
// use vanilla js to truncate the string rather than use the
|
||||
// Truncate component. We can match the behaviour of the component
|
||||
// by also splitting on 24 characters.
|
||||
// https://github.com/patternfly/patternfly-react/issues/11964
|
||||
blueprint.name && blueprint.name.length > 24
|
||||
? blueprint.name.slice(0, 24) + '...'
|
||||
: blueprint.name
|
||||
}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardBody>{blueprint.description}</CardBody>
|
||||
|
|
|
|||
|
|
@ -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, useEffect, useState } from 'react';
|
||||
import React, { useCallback } from 'react';
|
||||
|
||||
import {
|
||||
Bullseye,
|
||||
|
|
@ -17,7 +17,6 @@ import {
|
|||
import { PlusCircleIcon, SearchIcon } from '@patternfly/react-icons';
|
||||
import { SVGIconProps } from '@patternfly/react-icons/dist/esm/createIcon';
|
||||
import useChrome from '@redhat-cloud-services/frontend-components/useChrome';
|
||||
import { ChromeUser } from '@redhat-cloud-services/types';
|
||||
import debounce from 'lodash/debounce';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
|
|
@ -29,6 +28,7 @@ import {
|
|||
PAGINATION_LIMIT,
|
||||
PAGINATION_OFFSET,
|
||||
} from '../../constants';
|
||||
import { useGetUser } from '../../Hooks';
|
||||
import { useGetBlueprintsQuery } from '../../store/backendApi';
|
||||
import {
|
||||
selectBlueprintSearchInput,
|
||||
|
|
@ -60,8 +60,8 @@ type emptyBlueprintStateProps = {
|
|||
};
|
||||
|
||||
const BlueprintsSidebar = () => {
|
||||
const [userData, setUserData] = useState<ChromeUser | void>(undefined);
|
||||
const { analytics, auth } = useChrome();
|
||||
const { userData } = useGetUser(auth);
|
||||
|
||||
const selectedBlueprintId = useAppSelector(selectSelectedBlueprintId);
|
||||
const blueprintSearchInput = useAppSelector(selectBlueprintSearchInput);
|
||||
|
|
@ -73,13 +73,6 @@ const BlueprintsSidebar = () => {
|
|||
offset: blueprintsOffset,
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
const data = await auth?.getUser();
|
||||
setUserData(data);
|
||||
})();
|
||||
}, [auth]);
|
||||
|
||||
if (blueprintSearchInput) {
|
||||
searchParams.search = blueprintSearchInput;
|
||||
}
|
||||
|
|
@ -97,7 +90,7 @@ const BlueprintsSidebar = () => {
|
|||
if (isLoading) {
|
||||
return (
|
||||
<Bullseye>
|
||||
<Spinner size="xl" />
|
||||
<Spinner size='xl' />
|
||||
</Bullseye>
|
||||
);
|
||||
}
|
||||
|
|
@ -111,8 +104,8 @@ const BlueprintsSidebar = () => {
|
|||
<EmptyBlueprintState
|
||||
icon={PlusCircleIcon}
|
||||
action={<Link to={resolveRelPath('imagewizard')}>Add blueprint</Link>}
|
||||
titleText="No blueprints yet"
|
||||
bodyText="Add a blueprint and optionally build related images."
|
||||
titleText='No blueprints yet'
|
||||
bodyText='Add a blueprint and optionally build related images.'
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
@ -123,7 +116,7 @@ const BlueprintsSidebar = () => {
|
|||
};
|
||||
|
||||
if (!process.env.IS_ON_PREMISE) {
|
||||
const orgId = userData?.identity?.internal?.org_id;
|
||||
const orgId = userData?.identity.internal?.org_id;
|
||||
|
||||
analytics.group(orgId, {
|
||||
imagebuilder_blueprint_count: blueprintsData?.meta.count,
|
||||
|
|
@ -144,7 +137,7 @@ const BlueprintsSidebar = () => {
|
|||
<Flex justifyContent={{ default: 'justifyContentCenter' }}>
|
||||
<FlexItem>
|
||||
<Button
|
||||
variant="link"
|
||||
variant='link'
|
||||
isDisabled={!selectedBlueprintId}
|
||||
onClick={handleClickViewAll}
|
||||
>
|
||||
|
|
@ -160,14 +153,14 @@ const BlueprintsSidebar = () => {
|
|||
icon={SearchIcon}
|
||||
action={
|
||||
<Button
|
||||
variant="link"
|
||||
variant='link'
|
||||
onClick={() => dispatch(setBlueprintSearchInput(undefined))}
|
||||
>
|
||||
Clear all filters
|
||||
</Button>
|
||||
}
|
||||
titleText="No blueprints found"
|
||||
bodyText="No blueprints match your search criteria. Try a different search."
|
||||
titleText='No blueprints found'
|
||||
bodyText='No blueprints match your search criteria. Try a different search.'
|
||||
/>
|
||||
)}
|
||||
{blueprintsTotal > 0 &&
|
||||
|
|
@ -191,7 +184,7 @@ const BlueprintSearch = ({ blueprintsTotal }: blueprintSearchProps) => {
|
|||
dispatch(imageBuilderApi.util.invalidateTags([{ type: 'Blueprints' }]));
|
||||
dispatch(setBlueprintSearchInput(filter.length > 0 ? filter : undefined));
|
||||
}, DEBOUNCED_SEARCH_WAIT_TIME),
|
||||
[]
|
||||
[],
|
||||
);
|
||||
React.useEffect(() => {
|
||||
return () => {
|
||||
|
|
@ -209,7 +202,7 @@ const BlueprintSearch = ({ blueprintsTotal }: blueprintSearchProps) => {
|
|||
return (
|
||||
<SearchInput
|
||||
value={blueprintSearchInput || ''}
|
||||
placeholder="Search by name or description"
|
||||
placeholder='Search by name or description'
|
||||
onChange={(_event, value) => onChange(value)}
|
||||
onClear={() => onChange('')}
|
||||
resultsCount={`${blueprintsTotal} blueprints`}
|
||||
|
|
@ -223,7 +216,7 @@ const EmptyBlueprintState = ({
|
|||
icon,
|
||||
action,
|
||||
}: emptyBlueprintStateProps) => (
|
||||
<EmptyState headingLevel="h4" icon={icon} titleText={titleText} variant="sm">
|
||||
<EmptyState headingLevel='h4' icon={icon} titleText={titleText} variant='sm'>
|
||||
<EmptyStateBody>{bodyText}</EmptyStateBody>
|
||||
<EmptyStateFooter>
|
||||
<EmptyStateActions>{action}</EmptyStateActions>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import {
|
||||
Button,
|
||||
|
|
@ -16,11 +16,13 @@ import {
|
|||
} from '@patternfly/react-core';
|
||||
import { MenuToggleElement } from '@patternfly/react-core/dist/esm/components/MenuToggle/MenuToggle';
|
||||
import useChrome from '@redhat-cloud-services/frontend-components/useChrome';
|
||||
import { ChromeUser } from '@redhat-cloud-services/types';
|
||||
import { skipToken } from '@reduxjs/toolkit/query';
|
||||
|
||||
import { AMPLITUDE_MODULE_NAME, targetOptions } from '../../constants';
|
||||
import { useComposeBPWithNotification as useComposeBlueprintMutation } from '../../Hooks';
|
||||
import {
|
||||
useComposeBPWithNotification as useComposeBlueprintMutation,
|
||||
useGetUser,
|
||||
} from '../../Hooks';
|
||||
import { useGetBlueprintQuery } from '../../store/backendApi';
|
||||
import { selectSelectedBlueprintId } from '../../store/BlueprintSlice';
|
||||
import { useAppSelector } from '../../store/hooks';
|
||||
|
|
@ -37,15 +39,7 @@ export const BuildImagesButton = ({ children }: BuildImagesButtonPropTypes) => {
|
|||
const { trigger: buildBlueprint, isLoading: imageBuildLoading } =
|
||||
useComposeBlueprintMutation();
|
||||
const { analytics, auth } = useChrome();
|
||||
|
||||
const [userData, setUserData] = useState<ChromeUser | void>(undefined);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
const data = await auth?.getUser();
|
||||
setUserData(data);
|
||||
})();
|
||||
}, [auth]);
|
||||
const { userData } = useGetUser(auth);
|
||||
|
||||
const onBuildHandler = async () => {
|
||||
if (selectedBlueprintId) {
|
||||
|
|
@ -53,15 +47,17 @@ export const BuildImagesButton = ({ children }: BuildImagesButtonPropTypes) => {
|
|||
id: selectedBlueprintId,
|
||||
body: {
|
||||
image_types: blueprintImageType?.filter(
|
||||
(target) => !deselectedTargets.includes(target)
|
||||
(target) => !deselectedTargets.includes(target),
|
||||
),
|
||||
},
|
||||
});
|
||||
analytics.track(`${AMPLITUDE_MODULE_NAME} - Image Requested`, {
|
||||
module: AMPLITUDE_MODULE_NAME,
|
||||
trigger: 'synchronize images',
|
||||
account_id: userData?.identity.internal?.account_id || 'Not found',
|
||||
});
|
||||
if (!process.env.IS_ON_PREMISE) {
|
||||
analytics.track(`${AMPLITUDE_MODULE_NAME} - Image Requested`, {
|
||||
module: AMPLITUDE_MODULE_NAME,
|
||||
trigger: 'synchronize images',
|
||||
account_id: userData?.identity.internal?.account_id || 'Not found',
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
|
@ -69,21 +65,21 @@ export const BuildImagesButton = ({ children }: BuildImagesButtonPropTypes) => {
|
|||
setIsOpen(!isOpen);
|
||||
};
|
||||
const { data: blueprintDetails } = useGetBlueprintQuery(
|
||||
selectedBlueprintId ? { id: selectedBlueprintId } : skipToken
|
||||
selectedBlueprintId ? { id: selectedBlueprintId } : skipToken,
|
||||
);
|
||||
const blueprintImageType = blueprintDetails?.image_requests.map(
|
||||
(image) => image.image_type
|
||||
(image) => image.image_type,
|
||||
);
|
||||
|
||||
const onSelect = (
|
||||
_event: React.MouseEvent<Element, MouseEvent>,
|
||||
itemId: number
|
||||
itemId: number,
|
||||
) => {
|
||||
const imageType = blueprintImageType?.[itemId];
|
||||
|
||||
if (imageType && deselectedTargets.includes(imageType)) {
|
||||
setDeselectedTargets(
|
||||
deselectedTargets.filter((target) => target !== imageType)
|
||||
deselectedTargets.filter((target) => target !== imageType),
|
||||
);
|
||||
} else if (imageType) {
|
||||
setDeselectedTargets([...deselectedTargets, imageType]);
|
||||
|
|
@ -96,17 +92,17 @@ export const BuildImagesButton = ({ children }: BuildImagesButtonPropTypes) => {
|
|||
onOpenChange={(isOpen: boolean) => setIsOpen(isOpen)}
|
||||
toggle={(toggleRef: React.Ref<MenuToggleElement>) => (
|
||||
<MenuToggle
|
||||
variant="primary"
|
||||
data-testid="blueprint-build-image-menu"
|
||||
variant='primary'
|
||||
data-testid='blueprint-build-image-menu'
|
||||
ref={toggleRef}
|
||||
onClick={onToggleClick}
|
||||
isExpanded={isOpen}
|
||||
splitButtonItems={[
|
||||
<MenuToggleAction
|
||||
data-testid="blueprint-build-image-menu-option"
|
||||
key="split-action"
|
||||
data-testid='blueprint-build-image-menu-option'
|
||||
key='split-action'
|
||||
onClick={onBuildHandler}
|
||||
id="wizard-build-image-btn"
|
||||
id='wizard-build-image-btn'
|
||||
isDisabled={
|
||||
!selectedBlueprintId ||
|
||||
deselectedTargets.length === blueprintImageType?.length
|
||||
|
|
@ -122,7 +118,7 @@ export const BuildImagesButton = ({ children }: BuildImagesButtonPropTypes) => {
|
|||
} as React.CSSProperties
|
||||
}
|
||||
isInline
|
||||
size="md"
|
||||
size='md'
|
||||
/>
|
||||
</FlexItem>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import React from 'react';
|
||||
|
||||
import {
|
||||
Button,
|
||||
|
|
@ -9,14 +9,16 @@ import {
|
|||
ModalVariant,
|
||||
} from '@patternfly/react-core';
|
||||
import useChrome from '@redhat-cloud-services/frontend-components/useChrome';
|
||||
import { ChromeUser } from '@redhat-cloud-services/types';
|
||||
|
||||
import {
|
||||
AMPLITUDE_MODULE_NAME,
|
||||
PAGINATION_LIMIT,
|
||||
PAGINATION_OFFSET,
|
||||
} from '../../constants';
|
||||
import { useDeleteBPWithNotification as useDeleteBlueprintMutation } from '../../Hooks';
|
||||
import {
|
||||
useDeleteBPWithNotification as useDeleteBlueprintMutation,
|
||||
useGetUser,
|
||||
} from '../../Hooks';
|
||||
import { backendApi, useGetBlueprintsQuery } from '../../store/backendApi';
|
||||
import {
|
||||
selectBlueprintSearchInput,
|
||||
|
|
@ -42,14 +44,7 @@ export const DeleteBlueprintModal: React.FunctionComponent<
|
|||
const blueprintsLimit = useAppSelector(selectLimit) || PAGINATION_LIMIT;
|
||||
const dispatch = useAppDispatch();
|
||||
const { analytics, auth } = useChrome();
|
||||
const [userData, setUserData] = useState<ChromeUser | void>(undefined);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
const data = await auth?.getUser();
|
||||
setUserData(data);
|
||||
})();
|
||||
}, [auth]);
|
||||
const { userData } = useGetUser(auth);
|
||||
|
||||
const searchParams: GetBlueprintsApiArg = {
|
||||
limit: blueprintsLimit,
|
||||
|
|
@ -64,7 +59,7 @@ export const DeleteBlueprintModal: React.FunctionComponent<
|
|||
selectFromResult: ({ data }) => ({
|
||||
blueprintName: data?.data.find(
|
||||
(blueprint: { id: string | undefined }) =>
|
||||
blueprint.id === selectedBlueprintId
|
||||
blueprint.id === selectedBlueprintId,
|
||||
)?.name,
|
||||
}),
|
||||
});
|
||||
|
|
@ -90,16 +85,16 @@ export const DeleteBlueprintModal: React.FunctionComponent<
|
|||
};
|
||||
return (
|
||||
<Modal variant={ModalVariant.small} isOpen={isOpen} onClose={onDeleteClose}>
|
||||
<ModalHeader title={'Delete blueprint?'} titleIconVariant="warning" />
|
||||
<ModalHeader title={'Delete blueprint?'} titleIconVariant='warning' />
|
||||
<ModalBody>
|
||||
All versions of {blueprintName} and its associated images will be
|
||||
deleted.
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button variant="danger" type="button" onClick={handleDelete}>
|
||||
<Button variant='danger' type='button' onClick={handleDelete}>
|
||||
Delete
|
||||
</Button>
|
||||
<Button variant="link" type="button" onClick={onDeleteClose}>
|
||||
<Button variant='link' type='button' onClick={onDeleteClose}>
|
||||
Cancel
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
|
|
|
|||
|
|
@ -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,9 +155,8 @@ export const ImportBlueprintModal: React.FunctionComponent<
|
|||
blueprintFromFile.content_sources &&
|
||||
blueprintFromFile.content_sources.length > 0
|
||||
) {
|
||||
const imported = await handleRepositoryImport(
|
||||
blueprintFromFile
|
||||
);
|
||||
const imported =
|
||||
await handleRepositoryImport(blueprintFromFile);
|
||||
customRepos = imported ?? [];
|
||||
}
|
||||
|
||||
|
|
@ -175,7 +174,7 @@ export const ImportBlueprintModal: React.FunctionComponent<
|
|||
undefined;
|
||||
const importBlueprintState = mapExportRequestToState(
|
||||
blueprintExportedResponse,
|
||||
blueprintFromFile.image_requests || []
|
||||
blueprintFromFile.image_requests || [],
|
||||
);
|
||||
|
||||
setIsOnPrem(false);
|
||||
|
|
@ -185,7 +184,7 @@ export const ImportBlueprintModal: React.FunctionComponent<
|
|||
mapOnPremToHosted(blueprintFromFile);
|
||||
const importBlueprintState = mapExportRequestToState(
|
||||
blueprintFromFileMapped,
|
||||
[]
|
||||
[],
|
||||
);
|
||||
setIsOnPrem(true);
|
||||
setImportedBlueprint(importBlueprintState);
|
||||
|
|
@ -259,9 +258,9 @@ export const ImportBlueprintModal: React.FunctionComponent<
|
|||
>
|
||||
<Button
|
||||
icon={<HelpIcon />}
|
||||
variant="plain"
|
||||
aria-label="About import"
|
||||
className="pf-v6-u-pl-sm"
|
||||
variant='plain'
|
||||
aria-label='About import'
|
||||
className='pf-v6-u-pl-sm'
|
||||
isInline
|
||||
/>
|
||||
</Popover>
|
||||
|
|
@ -270,23 +269,23 @@ export const ImportBlueprintModal: React.FunctionComponent<
|
|||
/>
|
||||
<ModalBody>
|
||||
<Form>
|
||||
<FormGroup fieldId="checkbox-import-custom-repositories">
|
||||
<FormGroup fieldId='checkbox-import-custom-repositories'>
|
||||
<Checkbox
|
||||
label="Import missing custom repositories after file upload."
|
||||
label='Import missing custom repositories after file upload.'
|
||||
isChecked={isCheckedImportRepos}
|
||||
onChange={() => setIsCheckedImportRepos((prev) => !prev)}
|
||||
aria-label="Import Custom Repositories checkbox"
|
||||
id="checkbox-import-custom-repositories"
|
||||
name="Import Repositories"
|
||||
aria-label='Import Custom Repositories checkbox'
|
||||
id='checkbox-import-custom-repositories'
|
||||
name='Import Repositories'
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup fieldId="import-blueprint-file-upload">
|
||||
<FormGroup fieldId='import-blueprint-file-upload'>
|
||||
<FileUpload
|
||||
id="import-blueprint-file-upload"
|
||||
type="text"
|
||||
id='import-blueprint-file-upload'
|
||||
type='text'
|
||||
value={fileContent}
|
||||
filename={filename}
|
||||
filenamePlaceholder="Drag and drop a file or upload one"
|
||||
filenamePlaceholder='Drag and drop a file or upload one'
|
||||
onFileInputChange={handleFileInputChange}
|
||||
onDataChange={handleDataChange}
|
||||
onReadStarted={handleFileReadStarted}
|
||||
|
|
@ -294,7 +293,7 @@ export const ImportBlueprintModal: React.FunctionComponent<
|
|||
onClearClick={handleClear}
|
||||
isLoading={isLoading}
|
||||
isReadOnly={true}
|
||||
browseButtonText="Upload"
|
||||
browseButtonText='Upload'
|
||||
dropzoneProps={{
|
||||
accept: { 'text/json': ['.json'], 'text/plain': ['.toml'] },
|
||||
maxSize: 512000,
|
||||
|
|
@ -308,10 +307,10 @@ export const ImportBlueprintModal: React.FunctionComponent<
|
|||
{isRejected
|
||||
? 'Must be a valid Blueprint JSON/TOML file no larger than 512 KB'
|
||||
: isInvalidFormat
|
||||
? 'Not compatible with the blueprints format.'
|
||||
: isOnPrem
|
||||
? 'Importing on-premises blueprints is currently in beta. Results may vary.'
|
||||
: 'Upload your blueprint file. Supported formats: JSON, TOML.'}
|
||||
? 'Not compatible with the blueprints format.'
|
||||
: isOnPrem
|
||||
? 'Importing on-premises blueprints is currently in beta. Results may vary.'
|
||||
: 'Upload your blueprint file. Supported formats: JSON, TOML.'}
|
||||
</HelperTextItem>
|
||||
</HelperText>
|
||||
</FormHelperText>
|
||||
|
|
@ -320,7 +319,7 @@ export const ImportBlueprintModal: React.FunctionComponent<
|
|||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button
|
||||
type="button"
|
||||
type='button'
|
||||
isDisabled={isRejected || isInvalidFormat || !fileContent}
|
||||
onClick={() =>
|
||||
navigate(resolveRelPath(`imagewizard/import`), {
|
||||
|
|
@ -330,7 +329,7 @@ export const ImportBlueprintModal: React.FunctionComponent<
|
|||
>
|
||||
Review and finish
|
||||
</Button>
|
||||
<Button variant="link" type="button" onClick={onImportClose}>
|
||||
<Button variant='link' type='button' onClick={onImportClose}>
|
||||
Cancel
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
|
|
|
|||
|
|
@ -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, { useState } from 'react';
|
||||
import React from 'react';
|
||||
|
||||
import {
|
||||
Button,
|
||||
|
|
@ -38,19 +38,19 @@ type ToggleGroupProps = Omit<FormGroupProps<boolean>, 'isDisabled'>;
|
|||
const AWSConfigToggle = ({ value, onChange }: ToggleGroupProps) => {
|
||||
const handleChange = (
|
||||
_event: React.FormEvent<HTMLInputElement>,
|
||||
checked: boolean
|
||||
checked: boolean,
|
||||
) => {
|
||||
onChange(checked);
|
||||
};
|
||||
|
||||
return (
|
||||
<FormGroup label="Configure AWS Uploads">
|
||||
<FormGroup label='Configure AWS Uploads'>
|
||||
<Switch
|
||||
id="aws-config-switch"
|
||||
ouiaId="aws-config-switch"
|
||||
aria-label="aws-config-switch"
|
||||
id='aws-config-switch'
|
||||
ouiaId='aws-config-switch'
|
||||
aria-label='aws-config-switch'
|
||||
// empty label so there is no icon
|
||||
label=""
|
||||
label=''
|
||||
isChecked={value}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
|
|
@ -79,19 +79,19 @@ const AWSBucket = ({ value, onChange, isDisabled }: FormGroupProps<string>) => {
|
|||
|
||||
if (isDisabled) {
|
||||
return (
|
||||
<DisabledInputGroup label={label} value={value} ariaLabel="aws-bucket" />
|
||||
<DisabledInputGroup label={label} value={value} ariaLabel='aws-bucket' />
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<FormGroup label={label}>
|
||||
<ValidatedInput
|
||||
placeholder="AWS bucket"
|
||||
ariaLabel="aws-bucket"
|
||||
placeholder='AWS bucket'
|
||||
ariaLabel='aws-bucket'
|
||||
value={value || ''}
|
||||
validator={isAwsBucketValid}
|
||||
onChange={(_event, value) => onChange(value)}
|
||||
helperText="Invalid AWS bucket name"
|
||||
helperText='Invalid AWS bucket name'
|
||||
/>
|
||||
</FormGroup>
|
||||
);
|
||||
|
|
@ -100,7 +100,7 @@ const AWSBucket = ({ value, onChange, isDisabled }: FormGroupProps<string>) => {
|
|||
const CredsPathPopover = () => {
|
||||
return (
|
||||
<Popover
|
||||
minWidth="35rem"
|
||||
minWidth='35rem'
|
||||
headerContent={'What is the AWS Credentials Path?'}
|
||||
bodyContent={
|
||||
<Content>
|
||||
|
|
@ -115,9 +115,9 @@ const CredsPathPopover = () => {
|
|||
>
|
||||
<Button
|
||||
icon={<HelpIcon />}
|
||||
variant="plain"
|
||||
aria-label="Credentials Path Info"
|
||||
className="pf-v6-u-pl-sm header-button"
|
||||
variant='plain'
|
||||
aria-label='Credentials Path Info'
|
||||
className='pf-v6-u-pl-sm header-button'
|
||||
/>
|
||||
</Popover>
|
||||
);
|
||||
|
|
@ -139,7 +139,7 @@ const AWSCredsPath = ({
|
|||
<DisabledInputGroup
|
||||
value={value}
|
||||
label={label}
|
||||
ariaLabel="aws-creds-path"
|
||||
ariaLabel='aws-creds-path'
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
@ -147,30 +147,35 @@ const AWSCredsPath = ({
|
|||
return (
|
||||
<FormGroup label={label}>
|
||||
<ValidatedInput
|
||||
placeholder="Path to AWS credentials"
|
||||
ariaLabel="aws-creds-path"
|
||||
placeholder='Path to AWS credentials'
|
||||
ariaLabel='aws-creds-path'
|
||||
value={value || ''}
|
||||
validator={isAwsCredsPathValid}
|
||||
onChange={(_event, value) => onChange(value)}
|
||||
helperText="Invalid filepath for AWS credentials"
|
||||
helperText='Invalid filepath for AWS credentials'
|
||||
/>
|
||||
</FormGroup>
|
||||
);
|
||||
};
|
||||
|
||||
type AWSConfigProps = {
|
||||
isEnabled: boolean;
|
||||
enabled: boolean;
|
||||
setEnabled: (enabled: boolean) => void;
|
||||
reinit: (config: AWSWorkerConfig | undefined) => void;
|
||||
refetch: () => Promise<{
|
||||
data?: WorkerConfigResponse | undefined;
|
||||
}>;
|
||||
};
|
||||
|
||||
export const AWSConfig = ({ isEnabled, refetch, reinit }: AWSConfigProps) => {
|
||||
export const AWSConfig = ({
|
||||
enabled,
|
||||
setEnabled,
|
||||
refetch,
|
||||
reinit,
|
||||
}: AWSConfigProps) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const bucket = useAppSelector(selectAWSBucketName);
|
||||
const credentials = useAppSelector(selectAWSCredsPath);
|
||||
const [enabled, setEnabled] = useState<boolean>(isEnabled);
|
||||
|
||||
const onToggle = async (v: boolean) => {
|
||||
if (v) {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,9 @@
|
|||
import React, { MouseEventHandler, useCallback, useEffect } from 'react';
|
||||
import React, {
|
||||
MouseEventHandler,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useState,
|
||||
} from 'react';
|
||||
|
||||
import {
|
||||
Button,
|
||||
|
|
@ -43,9 +48,9 @@ const ConfigError = ({
|
|||
<EmptyState
|
||||
variant={EmptyStateVariant.xl}
|
||||
icon={ExclamationIcon}
|
||||
color="#C9190B"
|
||||
color='#C9190B'
|
||||
>
|
||||
<Title headingLevel="h4" size="lg">
|
||||
<Title headingLevel='h4' size='lg'>
|
||||
Error
|
||||
</Title>
|
||||
<EmptyStateBody>
|
||||
|
|
@ -54,7 +59,7 @@ const ConfigError = ({
|
|||
</EmptyStateBody>
|
||||
<EmptyStateFooter>
|
||||
<EmptyStateActions>
|
||||
<Button variant="primary" onClick={onClose}>
|
||||
<Button variant='primary' onClick={onClose}>
|
||||
Go back
|
||||
</Button>
|
||||
</EmptyStateActions>
|
||||
|
|
@ -68,6 +73,7 @@ export const CloudProviderConfig = () => {
|
|||
const dispatch = useAppDispatch();
|
||||
const config = useAppSelector(selectAWSConfig);
|
||||
const handleClose = () => navigate(resolveRelPath(''));
|
||||
const [enabled, setEnabled] = useState<boolean>(false);
|
||||
|
||||
const [updateConfig] = useUpdateWorkerConfigMutation();
|
||||
const { data, error, refetch, isLoading } = useGetWorkerConfigQuery({});
|
||||
|
|
@ -76,9 +82,12 @@ export const CloudProviderConfig = () => {
|
|||
(config: AWSWorkerConfig | undefined) => {
|
||||
if (!config) {
|
||||
dispatch(reinitializeAWSConfig());
|
||||
setEnabled(false);
|
||||
return;
|
||||
}
|
||||
|
||||
setEnabled(true);
|
||||
|
||||
const { bucket, credentials } = config;
|
||||
if (bucket && bucket !== '') {
|
||||
dispatch(changeAWSBucketName(bucket));
|
||||
|
|
@ -88,7 +97,7 @@ export const CloudProviderConfig = () => {
|
|||
dispatch(changeAWSCredsPath(credentials));
|
||||
}
|
||||
},
|
||||
[dispatch]
|
||||
[dispatch, setEnabled],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -109,8 +118,8 @@ export const CloudProviderConfig = () => {
|
|||
<PageSection>
|
||||
<Wizard onClose={handleClose}>
|
||||
<WizardStep
|
||||
name="AWS Config"
|
||||
id="aws-config"
|
||||
name='AWS Config'
|
||||
id='aws-config'
|
||||
footer={{
|
||||
nextButtonText: 'Submit',
|
||||
isNextDisabled: !isAwsStepValid(config),
|
||||
|
|
@ -126,7 +135,8 @@ export const CloudProviderConfig = () => {
|
|||
<AWSConfig
|
||||
refetch={refetch}
|
||||
reinit={initAWSConfig}
|
||||
isEnabled={!!(data?.aws && Object.keys(data.aws).length > 0)}
|
||||
enabled={enabled}
|
||||
setEnabled={setEnabled}
|
||||
/>
|
||||
</WizardStep>
|
||||
</Wizard>
|
||||
|
|
|
|||
|
|
@ -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,6 +15,7 @@ import { WizardStepType } from '@patternfly/react-core/dist/esm/components/Wizar
|
|||
import useChrome from '@redhat-cloud-services/frontend-components/useChrome';
|
||||
import { useNavigate, useSearchParams } from 'react-router-dom';
|
||||
|
||||
import AAPStep from './steps/AAP';
|
||||
import DetailsStep from './steps/Details';
|
||||
import FileSystemStep from './steps/FileSystem';
|
||||
import { FileSystemContext } from './steps/FileSystem/components/FileSystemTable';
|
||||
|
|
@ -40,6 +41,7 @@ import UsersStep from './steps/Users';
|
|||
import { getHostArch, getHostDistro } from './utilities/getHostInfo';
|
||||
import { useHasSpecificTargetOnly } from './utilities/hasSpecificTargetOnly';
|
||||
import {
|
||||
useAAPValidation,
|
||||
useDetailsValidation,
|
||||
useFilesystemValidation,
|
||||
useFirewallValidation,
|
||||
|
|
@ -65,7 +67,6 @@ import {
|
|||
AARCH64,
|
||||
AMPLITUDE_MODULE_NAME,
|
||||
RHEL_10,
|
||||
RHEL_10_BETA,
|
||||
RHEL_8,
|
||||
RHEL_9,
|
||||
} from '../../constants';
|
||||
|
|
@ -74,14 +75,13 @@ import './CreateImageWizard.scss';
|
|||
import {
|
||||
addImageType,
|
||||
changeArchitecture,
|
||||
changeAwsShareMethod,
|
||||
changeDistribution,
|
||||
initializeWizard,
|
||||
selectAwsAccountId,
|
||||
selectAwsShareMethod,
|
||||
selectAwsSourceId,
|
||||
selectAzureResourceGroup,
|
||||
selectAzureShareMethod,
|
||||
selectAzureSource,
|
||||
selectAzureSubscriptionId,
|
||||
selectAzureTenantId,
|
||||
selectDistribution,
|
||||
|
|
@ -118,7 +118,7 @@ export const CustomWizardFooter = ({
|
|||
<WizardFooterWrapper>
|
||||
<Flex columnGap={{ default: 'columnGapSm' }}>
|
||||
<Button
|
||||
variant="primary"
|
||||
variant='primary'
|
||||
onClick={() => {
|
||||
if (!process.env.IS_ON_PREMISE) {
|
||||
analytics.track(`${AMPLITUDE_MODULE_NAME} - Button Clicked`, {
|
||||
|
|
@ -134,7 +134,7 @@ export const CustomWizardFooter = ({
|
|||
Next
|
||||
</Button>
|
||||
<Button
|
||||
variant="secondary"
|
||||
variant='secondary'
|
||||
onClick={() => {
|
||||
if (!process.env.IS_ON_PREMISE) {
|
||||
analytics.track(`${AMPLITUDE_MODULE_NAME} - Button Clicked`, {
|
||||
|
|
@ -151,7 +151,7 @@ export const CustomWizardFooter = ({
|
|||
</Button>
|
||||
{optional && (
|
||||
<Button
|
||||
variant="tertiary"
|
||||
variant='tertiary'
|
||||
onClick={() => {
|
||||
if (!process.env.IS_ON_PREMISE) {
|
||||
analytics.track(`${AMPLITUDE_MODULE_NAME} - Button Clicked`, {
|
||||
|
|
@ -168,7 +168,7 @@ export const CustomWizardFooter = ({
|
|||
</Button>
|
||||
)}
|
||||
<Button
|
||||
variant="link"
|
||||
variant='link'
|
||||
onClick={() => {
|
||||
if (!process.env.IS_ON_PREMISE) {
|
||||
analytics.track(`${AMPLITUDE_MODULE_NAME} - Button Clicked`, {
|
||||
|
|
@ -199,6 +199,7 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
|
|||
|
||||
// Feature flags
|
||||
const complianceEnabled = useFlag('image-builder.compliance.enabled');
|
||||
const isAAPRegistrationEnabled = useFlag('image-builder.aap.enabled');
|
||||
|
||||
// IMPORTANT: Ensure the wizard starts with a fresh initial state
|
||||
useEffect(() => {
|
||||
|
|
@ -209,9 +210,6 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
|
|||
if (searchParams.get('release') === 'rhel9') {
|
||||
dispatch(changeDistribution(RHEL_9));
|
||||
}
|
||||
if (searchParams.get('release') === 'rhel10beta') {
|
||||
dispatch(changeDistribution(RHEL_10_BETA));
|
||||
}
|
||||
if (searchParams.get('release') === 'rhel10') {
|
||||
dispatch(changeDistribution(RHEL_10));
|
||||
}
|
||||
|
|
@ -235,6 +233,10 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
|
|||
dispatch(changeArchitecture(arch));
|
||||
};
|
||||
|
||||
if (process.env.IS_ON_PREMISE) {
|
||||
dispatch(changeAwsShareMethod('manual'));
|
||||
}
|
||||
|
||||
if (process.env.IS_ON_PREMISE && !isEdit) {
|
||||
if (!searchParams.get('release')) {
|
||||
initializeHostDistro();
|
||||
|
|
@ -262,11 +264,9 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
|
|||
const gcpShareMethod = useAppSelector(selectGcpShareMethod);
|
||||
const gcpEmail = useAppSelector(selectGcpEmail);
|
||||
// AZURE
|
||||
const azureShareMethod = useAppSelector(selectAzureShareMethod);
|
||||
const azureTenantId = useAppSelector(selectAzureTenantId);
|
||||
const azureSubscriptionId = useAppSelector(selectAzureSubscriptionId);
|
||||
const azureResourceGroup = useAppSelector(selectAzureResourceGroup);
|
||||
const azureSource = useAppSelector(selectAzureSource);
|
||||
// Registration
|
||||
const registrationValidation = useRegistrationValidation();
|
||||
// Snapshots
|
||||
|
|
@ -286,6 +286,8 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
|
|||
const firewallValidation = useFirewallValidation();
|
||||
// Services
|
||||
const servicesValidation = useServicesValidation();
|
||||
// AAP
|
||||
const aapValidation = useAAPValidation();
|
||||
// Firstboot
|
||||
const firstBootValidation = useFirstBootValidation();
|
||||
// Details
|
||||
|
|
@ -296,8 +298,10 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
|
|||
const hasWslTargetOnly = useHasSpecificTargetOnly('wsl');
|
||||
|
||||
let startIndex = 1; // default index
|
||||
const JUMP_TO_REVIEW_STEP = 23;
|
||||
|
||||
if (isEdit) {
|
||||
startIndex = 22;
|
||||
startIndex = JUMP_TO_REVIEW_STEP;
|
||||
}
|
||||
|
||||
const [wasRegisterVisited, setWasRegisterVisited] = useState(false);
|
||||
|
|
@ -308,7 +312,7 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
|
|||
step: WizardStepType,
|
||||
activeStep: WizardStepType,
|
||||
steps: WizardStepType[],
|
||||
goToStepByIndex: (index: number) => void
|
||||
goToStepByIndex: (index: number) => void,
|
||||
) => {
|
||||
const isVisitOptional =
|
||||
'parentId' in step && step.parentId === 'step-optional-steps';
|
||||
|
|
@ -324,7 +328,7 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
|
|||
}, [step.id, step.isVisited]);
|
||||
|
||||
const hasVisitedNextStep = steps.some(
|
||||
(s) => s.index > step.index && s.isVisited
|
||||
(s) => s.index > step.index && s.isVisited,
|
||||
);
|
||||
|
||||
// Only this code is different from the original
|
||||
|
|
@ -352,7 +356,7 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
|
|||
{
|
||||
module: AMPLITUDE_MODULE_NAME,
|
||||
isPreview: isBeta(),
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}}
|
||||
|
|
@ -371,8 +375,8 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
|
|||
isVisitRequired
|
||||
>
|
||||
<WizardStep
|
||||
name="Image output"
|
||||
id="step-image-output"
|
||||
name='Image output'
|
||||
id='step-image-output'
|
||||
footer={
|
||||
<CustomWizardFooter
|
||||
disableNext={targetEnvironments.length === 0}
|
||||
|
|
@ -383,25 +387,29 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
|
|||
<ImageOutputStep />
|
||||
</WizardStep>
|
||||
<WizardStep
|
||||
name="Target Environment"
|
||||
id="step-target-environment"
|
||||
name='Target Environment'
|
||||
id='step-target-environment'
|
||||
isHidden={
|
||||
!targetEnvironments.find(
|
||||
(target: string) =>
|
||||
target === 'aws' || target === 'gcp' || target === 'azure'
|
||||
target === 'aws' || target === 'gcp' || target === 'azure',
|
||||
)
|
||||
}
|
||||
steps={[
|
||||
<WizardStep
|
||||
name="Amazon Web Services"
|
||||
id="wizard-target-aws"
|
||||
key="wizard-target-aws"
|
||||
name='Amazon Web Services'
|
||||
id='wizard-target-aws'
|
||||
key='wizard-target-aws'
|
||||
footer={
|
||||
<CustomWizardFooter
|
||||
disableNext={
|
||||
awsShareMethod === 'manual'
|
||||
? !isAwsAccountIdValid(awsAccountId)
|
||||
: awsSourceId === undefined
|
||||
// we don't need the account id for
|
||||
// on-prem aws.
|
||||
process.env.IS_ON_PREMISE
|
||||
? false
|
||||
: awsShareMethod === 'manual'
|
||||
? !isAwsAccountIdValid(awsAccountId)
|
||||
: awsSourceId === undefined
|
||||
}
|
||||
/>
|
||||
}
|
||||
|
|
@ -410,9 +418,9 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
|
|||
<Aws />
|
||||
</WizardStep>,
|
||||
<WizardStep
|
||||
name="Google Cloud Platform"
|
||||
id="wizard-target-gcp"
|
||||
key="wizard-target-gcp"
|
||||
name='Google Cloud Platform'
|
||||
id='wizard-target-gcp'
|
||||
key='wizard-target-gcp'
|
||||
footer={
|
||||
<CustomWizardFooter
|
||||
disableNext={
|
||||
|
|
@ -426,21 +434,15 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
|
|||
<Gcp />
|
||||
</WizardStep>,
|
||||
<WizardStep
|
||||
name="Azure"
|
||||
id="wizard-target-azure"
|
||||
key="wizard-target-azure"
|
||||
name='Azure'
|
||||
id='wizard-target-azure'
|
||||
key='wizard-target-azure'
|
||||
footer={
|
||||
<CustomWizardFooter
|
||||
disableNext={
|
||||
azureShareMethod === 'manual'
|
||||
? !isAzureTenantGUIDValid(azureTenantId) ||
|
||||
!isAzureSubscriptionIdValid(azureSubscriptionId) ||
|
||||
!isAzureResourceGroupValid(azureResourceGroup)
|
||||
: azureShareMethod === 'sources'
|
||||
? !isAzureTenantGUIDValid(azureTenantId) ||
|
||||
!isAzureSubscriptionIdValid(azureSubscriptionId) ||
|
||||
!isAzureResourceGroupValid(azureResourceGroup)
|
||||
: azureSource === undefined
|
||||
!isAzureTenantGUIDValid(azureTenantId) ||
|
||||
!isAzureSubscriptionIdValid(azureSubscriptionId) ||
|
||||
!isAzureResourceGroupValid(azureResourceGroup)
|
||||
}
|
||||
/>
|
||||
}
|
||||
|
|
@ -451,13 +453,13 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
|
|||
]}
|
||||
/>
|
||||
<WizardStep
|
||||
name="Optional steps"
|
||||
id="step-optional-steps"
|
||||
name='Optional steps'
|
||||
id='step-optional-steps'
|
||||
steps={[
|
||||
<WizardStep
|
||||
name="Register"
|
||||
id="step-register"
|
||||
key="step-register"
|
||||
name='Register'
|
||||
id='step-register'
|
||||
key='step-register'
|
||||
isHidden={!!process.env.IS_ON_PREMISE || !isRhel(distribution)}
|
||||
navItem={CustomStatusNavItem}
|
||||
status={
|
||||
|
|
@ -478,9 +480,8 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
|
|||
</WizardStep>,
|
||||
<WizardStep
|
||||
name={complianceEnabled ? 'Compliance' : 'OpenSCAP'}
|
||||
id="step-oscap"
|
||||
key="step-oscap"
|
||||
isHidden={distribution === RHEL_10_BETA}
|
||||
id='step-oscap'
|
||||
key='step-oscap'
|
||||
navItem={CustomStatusNavItem}
|
||||
footer={
|
||||
<CustomWizardFooter disableNext={false} optional={true} />
|
||||
|
|
@ -489,9 +490,9 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
|
|||
<OscapStep />
|
||||
</WizardStep>,
|
||||
<WizardStep
|
||||
name="File system configuration"
|
||||
id="step-file-system"
|
||||
key="step-file-system"
|
||||
name='File system configuration'
|
||||
id='step-file-system'
|
||||
key='step-file-system'
|
||||
navItem={CustomStatusNavItem}
|
||||
isHidden={hasWslTargetOnly}
|
||||
footer={
|
||||
|
|
@ -515,14 +516,12 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
|
|||
</FileSystemContext.Provider>
|
||||
</WizardStep>,
|
||||
<WizardStep
|
||||
name="Repeatable build"
|
||||
id="wizard-repository-snapshot"
|
||||
key="wizard-repository-snapshot"
|
||||
name='Repeatable build'
|
||||
id='wizard-repository-snapshot'
|
||||
key='wizard-repository-snapshot'
|
||||
navItem={CustomStatusNavItem}
|
||||
status={snapshotValidation.disabledNext ? 'error' : 'default'}
|
||||
isHidden={
|
||||
distribution === RHEL_10_BETA || !!process.env.IS_ON_PREMISE
|
||||
}
|
||||
isHidden={!!process.env.IS_ON_PREMISE}
|
||||
footer={
|
||||
<CustomWizardFooter
|
||||
disableNext={snapshotValidation.disabledNext}
|
||||
|
|
@ -533,13 +532,11 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
|
|||
<SnapshotStep />
|
||||
</WizardStep>,
|
||||
<WizardStep
|
||||
name="Custom repositories"
|
||||
id="wizard-custom-repositories"
|
||||
key="wizard-custom-repositories"
|
||||
name='Custom repositories'
|
||||
id='wizard-custom-repositories'
|
||||
key='wizard-custom-repositories'
|
||||
navItem={CustomStatusNavItem}
|
||||
isHidden={
|
||||
distribution === RHEL_10_BETA || !!process.env.IS_ON_PREMISE
|
||||
}
|
||||
isHidden={!!process.env.IS_ON_PREMISE}
|
||||
isDisabled={snapshotValidation.disabledNext}
|
||||
footer={
|
||||
<CustomWizardFooter disableNext={false} optional={true} />
|
||||
|
|
@ -548,9 +545,9 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
|
|||
<RepositoriesStep />
|
||||
</WizardStep>,
|
||||
<WizardStep
|
||||
name="Additional packages"
|
||||
id="wizard-additional-packages"
|
||||
key="wizard-additional-packages"
|
||||
name='Additional packages'
|
||||
id='wizard-additional-packages'
|
||||
key='wizard-additional-packages'
|
||||
navItem={CustomStatusNavItem}
|
||||
isDisabled={snapshotValidation.disabledNext}
|
||||
footer={
|
||||
|
|
@ -560,9 +557,9 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
|
|||
<PackagesStep />
|
||||
</WizardStep>,
|
||||
<WizardStep
|
||||
name="Users"
|
||||
id="wizard-users"
|
||||
key="wizard-users"
|
||||
name='Users'
|
||||
id='wizard-users'
|
||||
key='wizard-users'
|
||||
navItem={CustomStatusNavItem}
|
||||
status={usersValidation.disabledNext ? 'error' : 'default'}
|
||||
footer={
|
||||
|
|
@ -575,9 +572,9 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
|
|||
<UsersStep />
|
||||
</WizardStep>,
|
||||
<WizardStep
|
||||
name="Timezone"
|
||||
id="wizard-timezone"
|
||||
key="wizard-timezone"
|
||||
name='Timezone'
|
||||
id='wizard-timezone'
|
||||
key='wizard-timezone'
|
||||
navItem={CustomStatusNavItem}
|
||||
status={timezoneValidation.disabledNext ? 'error' : 'default'}
|
||||
footer={
|
||||
|
|
@ -590,9 +587,9 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
|
|||
<TimezoneStep />
|
||||
</WizardStep>,
|
||||
<WizardStep
|
||||
name="Locale"
|
||||
id="wizard-locale"
|
||||
key="wizard-locale"
|
||||
name='Locale'
|
||||
id='wizard-locale'
|
||||
key='wizard-locale'
|
||||
navItem={CustomStatusNavItem}
|
||||
status={localeValidation.disabledNext ? 'error' : 'default'}
|
||||
footer={
|
||||
|
|
@ -605,9 +602,9 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
|
|||
<LocaleStep />
|
||||
</WizardStep>,
|
||||
<WizardStep
|
||||
name="Hostname"
|
||||
id="wizard-hostname"
|
||||
key="wizard-hostname"
|
||||
name='Hostname'
|
||||
id='wizard-hostname'
|
||||
key='wizard-hostname'
|
||||
navItem={CustomStatusNavItem}
|
||||
status={hostnameValidation.disabledNext ? 'error' : 'default'}
|
||||
footer={
|
||||
|
|
@ -620,9 +617,9 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
|
|||
<HostnameStep />
|
||||
</WizardStep>,
|
||||
<WizardStep
|
||||
name="Kernel"
|
||||
id="wizard-kernel"
|
||||
key="wizard-kernel"
|
||||
name='Kernel'
|
||||
id='wizard-kernel'
|
||||
key='wizard-kernel'
|
||||
navItem={CustomStatusNavItem}
|
||||
isHidden={hasWslTargetOnly}
|
||||
status={kernelValidation.disabledNext ? 'error' : 'default'}
|
||||
|
|
@ -636,9 +633,9 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
|
|||
<KernelStep />
|
||||
</WizardStep>,
|
||||
<WizardStep
|
||||
name="Firewall"
|
||||
id="wizard-firewall"
|
||||
key="wizard-firewall"
|
||||
name='Firewall'
|
||||
id='wizard-firewall'
|
||||
key='wizard-firewall'
|
||||
navItem={CustomStatusNavItem}
|
||||
status={firewallValidation.disabledNext ? 'error' : 'default'}
|
||||
footer={
|
||||
|
|
@ -651,9 +648,9 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
|
|||
<FirewallStep />
|
||||
</WizardStep>,
|
||||
<WizardStep
|
||||
name="Systemd services"
|
||||
id="wizard-services"
|
||||
key="wizard-services"
|
||||
name='Systemd services'
|
||||
id='wizard-services'
|
||||
key='wizard-services'
|
||||
navItem={CustomStatusNavItem}
|
||||
status={servicesValidation.disabledNext ? 'error' : 'default'}
|
||||
footer={
|
||||
|
|
@ -666,9 +663,25 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
|
|||
<ServicesStep />
|
||||
</WizardStep>,
|
||||
<WizardStep
|
||||
name="First boot script configuration"
|
||||
id="wizard-first-boot"
|
||||
key="wizard-first-boot"
|
||||
name='Ansible Automation Platform'
|
||||
id='wizard-aap'
|
||||
isHidden={!isAAPRegistrationEnabled}
|
||||
key='wizard-aap'
|
||||
navItem={CustomStatusNavItem}
|
||||
status={aapValidation.disabledNext ? 'error' : 'default'}
|
||||
footer={
|
||||
<CustomWizardFooter
|
||||
disableNext={aapValidation.disabledNext}
|
||||
optional={true}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<AAPStep />
|
||||
</WizardStep>,
|
||||
<WizardStep
|
||||
name='First boot script configuration'
|
||||
id='wizard-first-boot'
|
||||
key='wizard-first-boot'
|
||||
navItem={CustomStatusNavItem}
|
||||
status={firstBootValidation.disabledNext ? 'error' : 'default'}
|
||||
isHidden={!!process.env.IS_ON_PREMISE}
|
||||
|
|
@ -684,8 +697,8 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
|
|||
]}
|
||||
/>
|
||||
<WizardStep
|
||||
name="Details"
|
||||
id="step-details"
|
||||
name='Details'
|
||||
id='step-details'
|
||||
navItem={CustomStatusNavItem}
|
||||
status={detailsValidation.disabledNext ? 'error' : 'default'}
|
||||
footer={
|
||||
|
|
@ -697,8 +710,8 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
|
|||
<DetailsStep />
|
||||
</WizardStep>
|
||||
<WizardStep
|
||||
name="Review"
|
||||
id="step-review"
|
||||
name='Review'
|
||||
id='step-review'
|
||||
footer={<ReviewWizardFooter />}
|
||||
>
|
||||
<ReviewStep />
|
||||
|
|
|
|||
|
|
@ -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,6 +138,7 @@ export const ValidatedInput = ({
|
|||
value,
|
||||
placeholder,
|
||||
onChange,
|
||||
...props
|
||||
}: ValidatedTextInputPropTypes) => {
|
||||
const [isPristine, setIsPristine] = useState(!value ? true : false);
|
||||
|
||||
|
|
@ -158,16 +159,17 @@ export const ValidatedInput = ({
|
|||
<TextInput
|
||||
value={value}
|
||||
data-testid={dataTestId}
|
||||
type="text"
|
||||
type='text'
|
||||
onChange={onChange!}
|
||||
validated={handleValidation()}
|
||||
aria-label={ariaLabel || ''}
|
||||
onBlur={handleBlur}
|
||||
placeholder={placeholder || ''}
|
||||
{...props}
|
||||
/>
|
||||
{!isPristine && !validator(value) && (
|
||||
<HelperText>
|
||||
<HelperTextItem variant="error">{helperText}</HelperTextItem>
|
||||
<HelperTextItem variant='error'>{helperText}</HelperTextItem>
|
||||
</HelperText>
|
||||
)}
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,178 @@
|
|||
import React from 'react';
|
||||
|
||||
import {
|
||||
Checkbox,
|
||||
DropEvent,
|
||||
FileUpload,
|
||||
FormGroup,
|
||||
FormHelperText,
|
||||
HelperText,
|
||||
HelperTextItem,
|
||||
} from '@patternfly/react-core';
|
||||
|
||||
import { useAppDispatch, useAppSelector } from '../../../../../store/hooks';
|
||||
import {
|
||||
changeAapCallbackUrl,
|
||||
changeAapHostConfigKey,
|
||||
changeAapTlsCertificateAuthority,
|
||||
changeAapTlsConfirmation,
|
||||
selectAapCallbackUrl,
|
||||
selectAapHostConfigKey,
|
||||
selectAapTlsCertificateAuthority,
|
||||
selectAapTlsConfirmation,
|
||||
} from '../../../../../store/wizardSlice';
|
||||
import { useAAPValidation } from '../../../utilities/useValidation';
|
||||
import { ValidatedInputAndTextArea } from '../../../ValidatedInput';
|
||||
import { validateMultipleCertificates } from '../../../validators';
|
||||
|
||||
const AAPRegistration = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const callbackUrl = useAppSelector(selectAapCallbackUrl);
|
||||
const hostConfigKey = useAppSelector(selectAapHostConfigKey);
|
||||
const tlsCertificateAuthority = useAppSelector(
|
||||
selectAapTlsCertificateAuthority,
|
||||
);
|
||||
const tlsConfirmation = useAppSelector(selectAapTlsConfirmation);
|
||||
const [isRejected, setIsRejected] = React.useState(false);
|
||||
const stepValidation = useAAPValidation();
|
||||
|
||||
const isHttpsUrl = callbackUrl?.toLowerCase().startsWith('https://') || false;
|
||||
const shouldShowCaInput = !isHttpsUrl || (isHttpsUrl && !tlsConfirmation);
|
||||
|
||||
const validated = stepValidation.errors['certificate']
|
||||
? 'error'
|
||||
: stepValidation.errors['certificate'] === undefined &&
|
||||
tlsCertificateAuthority &&
|
||||
validateMultipleCertificates(tlsCertificateAuthority).validCertificates
|
||||
.length > 0
|
||||
? 'success'
|
||||
: 'default';
|
||||
|
||||
const handleCallbackUrlChange = (value: string) => {
|
||||
dispatch(changeAapCallbackUrl(value));
|
||||
};
|
||||
|
||||
const handleHostConfigKeyChange = (value: string) => {
|
||||
dispatch(changeAapHostConfigKey(value));
|
||||
};
|
||||
|
||||
const handleClear = () => {
|
||||
dispatch(changeAapTlsCertificateAuthority(''));
|
||||
};
|
||||
|
||||
const handleTextChange = (
|
||||
_event: React.ChangeEvent<HTMLTextAreaElement>,
|
||||
value: string,
|
||||
) => {
|
||||
dispatch(changeAapTlsCertificateAuthority(value));
|
||||
setIsRejected(false);
|
||||
};
|
||||
|
||||
const handleDataChange = (_: DropEvent, value: string) => {
|
||||
dispatch(changeAapTlsCertificateAuthority(value));
|
||||
setIsRejected(false);
|
||||
};
|
||||
|
||||
const handleFileRejected = () => {
|
||||
dispatch(changeAapTlsCertificateAuthority(''));
|
||||
setIsRejected(true);
|
||||
};
|
||||
|
||||
const handleTlsConfirmationChange = (checked: boolean) => {
|
||||
dispatch(changeAapTlsConfirmation(checked));
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<FormGroup label='Ansible Callback URL' isRequired>
|
||||
<ValidatedInputAndTextArea
|
||||
value={callbackUrl || ''}
|
||||
onChange={(_event, value) => handleCallbackUrlChange(value.trim())}
|
||||
ariaLabel='ansible callback url'
|
||||
isRequired
|
||||
stepValidation={stepValidation}
|
||||
fieldName='callbackUrl'
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup label='Host Config Key' isRequired>
|
||||
<ValidatedInputAndTextArea
|
||||
value={hostConfigKey || ''}
|
||||
onChange={(_event, value) => handleHostConfigKeyChange(value.trim())}
|
||||
ariaLabel='host config key'
|
||||
isRequired
|
||||
stepValidation={stepValidation}
|
||||
fieldName='hostConfigKey'
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
{shouldShowCaInput && (
|
||||
<FormGroup label='Certificate authority (CA) for Ansible Controller'>
|
||||
<FileUpload
|
||||
id='aap-certificate-upload'
|
||||
type='text'
|
||||
value={tlsCertificateAuthority || ''}
|
||||
filename={tlsCertificateAuthority ? 'CA detected' : ''}
|
||||
onDataChange={handleDataChange}
|
||||
onTextChange={handleTextChange}
|
||||
onClearClick={handleClear}
|
||||
dropzoneProps={{
|
||||
accept: {
|
||||
'application/x-pem-file': ['.pem'],
|
||||
'application/x-x509-ca-cert': ['.cer', '.crt'],
|
||||
'application/pkix-cert': ['.der'],
|
||||
},
|
||||
maxSize: 512000,
|
||||
onDropRejected: handleFileRejected,
|
||||
}}
|
||||
validated={isRejected ? 'error' : validated}
|
||||
browseButtonText='Upload'
|
||||
allowEditingUploadedText={true}
|
||||
/>
|
||||
<FormHelperText>
|
||||
<HelperText>
|
||||
<HelperTextItem
|
||||
variant={
|
||||
isRejected || validated === 'error'
|
||||
? 'error'
|
||||
: validated === 'success'
|
||||
? 'success'
|
||||
: 'default'
|
||||
}
|
||||
>
|
||||
{isRejected
|
||||
? 'Must be a .PEM/.CER/.CRT file'
|
||||
: validated === 'error'
|
||||
? stepValidation.errors['certificate']
|
||||
: validated === 'success'
|
||||
? 'Certificate was uploaded'
|
||||
: 'Drag and drop a valid certificate file or upload one'}
|
||||
</HelperTextItem>
|
||||
</HelperText>
|
||||
</FormHelperText>
|
||||
</FormGroup>
|
||||
)}
|
||||
{isHttpsUrl && (
|
||||
<FormGroup>
|
||||
<Checkbox
|
||||
id='tls-confirmation-checkbox'
|
||||
label='Insecure'
|
||||
isChecked={tlsConfirmation || false}
|
||||
onChange={(_event, checked) => handleTlsConfirmationChange(checked)}
|
||||
/>
|
||||
{stepValidation.errors['tlsConfirmation'] && (
|
||||
<FormHelperText>
|
||||
<HelperText>
|
||||
<HelperTextItem variant='error'>
|
||||
{stepValidation.errors['tlsConfirmation']}
|
||||
</HelperTextItem>
|
||||
</HelperText>
|
||||
</FormHelperText>
|
||||
)}
|
||||
</FormGroup>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default AAPRegistration;
|
||||
18
src/Components/CreateImageWizard/steps/AAP/index.tsx
Normal file
18
src/Components/CreateImageWizard/steps/AAP/index.tsx
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
import React from 'react';
|
||||
|
||||
import { Form, Title } from '@patternfly/react-core';
|
||||
|
||||
import AAPRegistration from './components/AAPRegistration';
|
||||
|
||||
const AAPStep = () => {
|
||||
return (
|
||||
<Form>
|
||||
<Title headingLevel='h1' size='xl'>
|
||||
Ansible Automation Platform
|
||||
</Title>
|
||||
<AAPRegistration />
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
export default AAPStep;
|
||||
|
|
@ -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 &&
|
||||
{partitions.length > 0 &&
|
||||
partitions.map((partition) => (
|
||||
<Row
|
||||
onDrop={onDrop}
|
||||
|
|
|
|||
|
|
@ -9,17 +9,18 @@ import FileSystemPartition from './components/FileSystemPartition';
|
|||
import { useAppSelector } from '../../../../store/hooks';
|
||||
import { selectFileSystemConfigurationType } from '../../../../store/wizardSlice';
|
||||
import { useHasSpecificTargetOnly } from '../../utilities/hasSpecificTargetOnly';
|
||||
|
||||
export type FileSystemConfigurationType = 'automatic' | 'manual';
|
||||
|
||||
const FileSystemStep = () => {
|
||||
const fileSystemConfigurationType = useAppSelector(
|
||||
selectFileSystemConfigurationType
|
||||
selectFileSystemConfigurationType,
|
||||
);
|
||||
const hasIsoTargetOnly = useHasSpecificTargetOnly('image-installer');
|
||||
|
||||
return (
|
||||
<Form>
|
||||
<Title headingLevel="h1" size="xl">
|
||||
<Title headingLevel='h1' size='xl'>
|
||||
File system configuration
|
||||
</Title>
|
||||
<Content>Define the partitioning of the image.</Content>
|
||||
|
|
@ -30,13 +31,11 @@ const FileSystemStep = () => {
|
|||
<FileSystemPartition />
|
||||
<FileSystemAutomaticPartition />
|
||||
</>
|
||||
) : fileSystemConfigurationType === 'manual' ? (
|
||||
) : (
|
||||
<>
|
||||
<FileSystemPartition />
|
||||
<FileSystemConfiguration />
|
||||
</>
|
||||
) : (
|
||||
fileSystemConfigurationType === 'oscap' && <FileSystemConfiguration />
|
||||
)}
|
||||
</Form>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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,14 +13,12 @@ import {
|
|||
ON_PREM_RELEASES,
|
||||
RELEASES,
|
||||
RHEL_10,
|
||||
RHEL_10_BETA,
|
||||
RHEL_10_FULL_SUPPORT,
|
||||
RHEL_10_MAINTENANCE_SUPPORT,
|
||||
RHEL_8,
|
||||
RHEL_8_FULL_SUPPORT,
|
||||
RHEL_8_MAINTENANCE_SUPPORT,
|
||||
RHEL_9,
|
||||
RHEL_9_BETA,
|
||||
RHEL_9_FULL_SUPPORT,
|
||||
RHEL_9_MAINTENANCE_SUPPORT,
|
||||
} from '../../../../../constants';
|
||||
|
|
@ -33,7 +31,6 @@ import {
|
|||
} from '../../../../../store/wizardSlice';
|
||||
import isRhel from '../../../../../Utilities/isRhel';
|
||||
import { toMonthAndYear } from '../../../../../Utilities/time';
|
||||
import { useFlag } from '../../../../../Utilities/useGetEnvironment';
|
||||
|
||||
const ReleaseSelect = () => {
|
||||
// What the UI refers to as the "release" is referred to as the "distribution" in the API.
|
||||
|
|
@ -44,9 +41,6 @@ const ReleaseSelect = () => {
|
|||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [showDevelopmentOptions, setShowDevelopmentOptions] = useState(false);
|
||||
|
||||
const isRHEL9BetaEnabled = useFlag('image-builder.rhel9.beta.enabled');
|
||||
const isRHEL10BetaEnabled = useFlag('image-builder.rhel10.beta.enabled');
|
||||
|
||||
const releases = process.env.IS_ON_PREMISE ? ON_PREM_RELEASES : RELEASES;
|
||||
|
||||
const handleSelect = (_event: React.MouseEvent, selection: Distributions) => {
|
||||
|
|
@ -70,10 +64,6 @@ const ReleaseSelect = () => {
|
|||
return '';
|
||||
}
|
||||
|
||||
if (key === RHEL_9_BETA || key === RHEL_10_BETA) {
|
||||
return '';
|
||||
}
|
||||
|
||||
let fullSupportEnd = '';
|
||||
let maintenanceSupportEnd = '';
|
||||
|
||||
|
|
@ -105,20 +95,12 @@ const ReleaseSelect = () => {
|
|||
return key === distribution;
|
||||
}
|
||||
|
||||
if (key === RHEL_9_BETA) {
|
||||
return isRHEL9BetaEnabled;
|
||||
}
|
||||
|
||||
if (key === RHEL_10_BETA) {
|
||||
return isRHEL10BetaEnabled;
|
||||
}
|
||||
|
||||
// Only show non-RHEL distros if expanded
|
||||
if (showDevelopmentOptions) {
|
||||
return true;
|
||||
}
|
||||
return isRhel(key);
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
filteredRhel.forEach((value, key) => {
|
||||
|
|
@ -129,7 +111,7 @@ const ReleaseSelect = () => {
|
|||
description={setDescription(key as Distributions)}
|
||||
>
|
||||
{releases.get(key)}
|
||||
</SelectOption>
|
||||
</SelectOption>,
|
||||
);
|
||||
});
|
||||
|
||||
|
|
@ -145,7 +127,7 @@ const ReleaseSelect = () => {
|
|||
ref={toggleRef}
|
||||
onClick={onToggleClick}
|
||||
isExpanded={isOpen}
|
||||
data-testid="release_select"
|
||||
data-testid='release_select'
|
||||
style={
|
||||
{
|
||||
maxWidth: '100%',
|
||||
|
|
@ -157,7 +139,7 @@ const ReleaseSelect = () => {
|
|||
);
|
||||
|
||||
return (
|
||||
<FormGroup isRequired={true} label="Release">
|
||||
<FormGroup isRequired={true} label='Release'>
|
||||
<Select
|
||||
isOpen={isOpen}
|
||||
onOpenChange={(isOpen) => setIsOpen(isOpen)}
|
||||
|
|
@ -178,7 +160,7 @@ const ReleaseSelect = () => {
|
|||
ev.stopPropagation();
|
||||
handleExpand();
|
||||
}}
|
||||
value="loader"
|
||||
value='loader'
|
||||
isLoadButton
|
||||
>
|
||||
Show options for further development of RHEL
|
||||
|
|
|
|||
|
|
@ -1,16 +1,19 @@
|
|||
import React, { MouseEventHandler, useEffect } from 'react';
|
||||
|
||||
import {
|
||||
Alert,
|
||||
Button,
|
||||
Card,
|
||||
CardHeader,
|
||||
Checkbox,
|
||||
Content,
|
||||
EmptyState,
|
||||
Flex,
|
||||
FlexItem,
|
||||
FormGroup,
|
||||
Gallery,
|
||||
Popover,
|
||||
Spinner,
|
||||
Title,
|
||||
} from '@patternfly/react-core';
|
||||
import { ExternalLinkAltIcon, HelpIcon } from '@patternfly/react-icons';
|
||||
|
|
@ -69,11 +72,15 @@ const TargetEnvironmentCard = ({
|
|||
}}
|
||||
>
|
||||
<Flex direction={{ default: 'column' }}>
|
||||
{!process.env.IS_ON_PREMISE && (
|
||||
// the logos don't display in cockpit,
|
||||
// so we can just hide them
|
||||
<FlexItem>
|
||||
<img className='provider-icon' src={imageSrc} alt={imageAlt} />
|
||||
</FlexItem>
|
||||
)}
|
||||
<FlexItem>
|
||||
<img className="provider-icon" src={imageSrc} alt={imageAlt} />
|
||||
</FlexItem>
|
||||
<FlexItem>
|
||||
<Title headingLevel="h5" size="md">
|
||||
<Title headingLevel='h5' size='md'>
|
||||
{title}
|
||||
</Title>
|
||||
</FlexItem>
|
||||
|
|
@ -88,11 +95,9 @@ const TargetEnvironment = () => {
|
|||
const environments = useAppSelector(selectImageTypes);
|
||||
const distribution = useAppSelector(selectDistribution);
|
||||
|
||||
const { data } = useGetArchitecturesQuery({
|
||||
const { data, isFetching, isError } = useGetArchitecturesQuery({
|
||||
distribution: distribution,
|
||||
});
|
||||
// TODO: Handle isFetching state (add skeletons)
|
||||
// TODO: Handle isError state (very unlikely...)
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
const prefetchSources = provisioningApi.usePrefetch('getSourceList');
|
||||
|
|
@ -100,10 +105,13 @@ const TargetEnvironment = () => {
|
|||
|
||||
useEffect(() => {
|
||||
prefetchActivationKeys();
|
||||
// This useEffect hook should run *only* on mount and therefore has an empty
|
||||
// dependency array. eslint's exhaustive-deps rule does not support this use.
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const supportedEnvironments = data?.find(
|
||||
(elem) => elem.arch === arch
|
||||
(elem) => elem.arch === arch,
|
||||
)?.image_types;
|
||||
|
||||
const handleToggleEnvironment = (environment: ImageTypes) => {
|
||||
|
|
@ -133,20 +141,45 @@ const TargetEnvironment = () => {
|
|||
);
|
||||
};
|
||||
|
||||
if (isFetching) {
|
||||
return (
|
||||
<EmptyState
|
||||
titleText='Loading target environments'
|
||||
headingLevel='h6'
|
||||
icon={Spinner}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (isError) {
|
||||
return (
|
||||
<Alert
|
||||
title="Couldn't fetch target environments"
|
||||
variant='danger'
|
||||
isInline
|
||||
>
|
||||
Target environments couldn't be loaded, please refresh the page or
|
||||
try again later.
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<FormGroup
|
||||
isRequired={true}
|
||||
label="Select target environments"
|
||||
data-testid="target-select"
|
||||
label='Select target environments'
|
||||
data-testid='target-select'
|
||||
>
|
||||
{publicCloudsSupported() && (
|
||||
<FormGroup label={<small>Public cloud</small>}>
|
||||
<Gallery hasGutter>
|
||||
{supportedEnvironments?.includes('aws') && (
|
||||
<TargetEnvironmentCard
|
||||
title="Amazon Web Services"
|
||||
imageSrc={'/apps/frontend-assets/partners-icons/aws.svg'}
|
||||
imageAlt="Amazon Web Services logo"
|
||||
title='Amazon Web Services'
|
||||
imageSrc={
|
||||
'/apps/frontend-assets/partners-icons/aws-logomark.svg'
|
||||
}
|
||||
imageAlt='Amazon Web Services logo'
|
||||
handleOnClick={() => handleToggleEnvironment('aws')}
|
||||
onMouseEnter={() => prefetchSources({ provider: 'aws' })}
|
||||
isClicked={environments.includes('aws')}
|
||||
|
|
@ -154,11 +187,11 @@ const TargetEnvironment = () => {
|
|||
)}
|
||||
{supportedEnvironments?.includes('gcp') && (
|
||||
<TargetEnvironmentCard
|
||||
title="Google Cloud Platform"
|
||||
title='Google Cloud Platform'
|
||||
imageSrc={
|
||||
'/apps/frontend-assets/partners-icons/google-cloud-short.svg'
|
||||
'/apps/frontend-assets/partners-icons/google-cloud-logomark.svg'
|
||||
}
|
||||
imageAlt="Google Cloud Platform logo"
|
||||
imageAlt='Google Cloud Platform logo'
|
||||
handleOnClick={() => handleToggleEnvironment('gcp')}
|
||||
onMouseEnter={() => prefetchSources({ provider: 'gcp' })}
|
||||
isClicked={environments.includes('gcp')}
|
||||
|
|
@ -166,11 +199,11 @@ const TargetEnvironment = () => {
|
|||
)}
|
||||
{supportedEnvironments?.includes('azure') && (
|
||||
<TargetEnvironmentCard
|
||||
title="Microsoft Azure"
|
||||
title='Microsoft Azure'
|
||||
imageSrc={
|
||||
'/apps/frontend-assets/partners-icons/microsoft-azure-short.svg'
|
||||
'/apps/frontend-assets/partners-icons/microsoft-azure-logomark.svg'
|
||||
}
|
||||
imageAlt="Microsoft Azure logo"
|
||||
imageAlt='Microsoft Azure logo'
|
||||
handleOnClick={() => handleToggleEnvironment('azure')}
|
||||
onMouseEnter={() => prefetchSources({ provider: 'azure' })}
|
||||
isClicked={environments.includes('azure')}
|
||||
|
|
@ -178,11 +211,11 @@ const TargetEnvironment = () => {
|
|||
)}
|
||||
{supportedEnvironments?.includes('oci') && (
|
||||
<TargetEnvironmentCard
|
||||
title="Oracle Cloud Infrastructure"
|
||||
title='Oracle Cloud Infrastructure'
|
||||
imageSrc={
|
||||
'/apps/frontend-assets/partners-icons/oracle-short.svg'
|
||||
}
|
||||
imageAlt="Oracle Cloud Infrastructure logo"
|
||||
imageAlt='Oracle Cloud Infrastructure logo'
|
||||
handleOnClick={() => handleToggleEnvironment('oci')}
|
||||
isClicked={environments.includes('oci')}
|
||||
/>
|
||||
|
|
@ -194,19 +227,19 @@ const TargetEnvironment = () => {
|
|||
supportedEnvironments?.includes('vsphere-ova')) && (
|
||||
<FormGroup
|
||||
label={<small>Private cloud</small>}
|
||||
className="pf-v6-u-mt-sm"
|
||||
className='pf-v6-u-mt-sm'
|
||||
>
|
||||
{supportedEnvironments?.includes('vsphere-ova') && (
|
||||
{supportedEnvironments.includes('vsphere-ova') && (
|
||||
<Checkbox
|
||||
name="vsphere-checkbox-ova"
|
||||
aria-label="VMware vSphere checkbox OVA"
|
||||
id="vsphere-checkbox-ova"
|
||||
name='vsphere-checkbox-ova'
|
||||
aria-label='VMware vSphere checkbox OVA'
|
||||
id='vsphere-checkbox-ova'
|
||||
label={
|
||||
<>
|
||||
VMware vSphere - Open virtualization format (.ova)
|
||||
<Popover
|
||||
maxWidth="30rem"
|
||||
position="right"
|
||||
maxWidth='30rem'
|
||||
position='right'
|
||||
bodyContent={
|
||||
<Content>
|
||||
<Content>
|
||||
|
|
@ -221,9 +254,9 @@ const TargetEnvironment = () => {
|
|||
>
|
||||
<Button
|
||||
icon={<HelpIcon />}
|
||||
className="pf-v6-u-pl-sm pf-v6-u-pt-0 pf-v6-u-pb-0"
|
||||
variant="plain"
|
||||
aria-label="About OVA file"
|
||||
className='pf-v6-u-pl-sm pf-v6-u-pt-0 pf-v6-u-pb-0'
|
||||
variant='plain'
|
||||
aria-label='About OVA file'
|
||||
isInline
|
||||
/>
|
||||
</Popover>
|
||||
|
|
@ -235,18 +268,18 @@ const TargetEnvironment = () => {
|
|||
isChecked={environments.includes('vsphere-ova')}
|
||||
/>
|
||||
)}
|
||||
{supportedEnvironments?.includes('vsphere') && (
|
||||
{supportedEnvironments.includes('vsphere') && (
|
||||
<Checkbox
|
||||
className="pf-v6-u-mt-sm"
|
||||
name="vsphere-checkbox-vmdk"
|
||||
aria-label="VMware vSphere checkbox VMDK"
|
||||
id="vsphere-checkbox-vmdk"
|
||||
className='pf-v6-u-mt-sm'
|
||||
name='vsphere-checkbox-vmdk'
|
||||
aria-label='VMware vSphere checkbox VMDK'
|
||||
id='vsphere-checkbox-vmdk'
|
||||
label={
|
||||
<>
|
||||
VMware vSphere - Virtual disk (.vmdk)
|
||||
<Popover
|
||||
maxWidth="30rem"
|
||||
position="right"
|
||||
maxWidth='30rem'
|
||||
position='right'
|
||||
bodyContent={
|
||||
<Content>
|
||||
<Content>
|
||||
|
|
@ -260,9 +293,9 @@ const TargetEnvironment = () => {
|
|||
>
|
||||
<Button
|
||||
icon={<HelpIcon />}
|
||||
className="pf-v6-u-pl-sm pf-v6-u-pt-0 pf-v6-u-pb-0"
|
||||
variant="plain"
|
||||
aria-label="About VMDK file"
|
||||
className='pf-v6-u-pl-sm pf-v6-u-pt-0 pf-v6-u-pb-0'
|
||||
variant='plain'
|
||||
aria-label='About VMDK file'
|
||||
isInline
|
||||
/>
|
||||
</Popover>
|
||||
|
|
@ -279,26 +312,26 @@ const TargetEnvironment = () => {
|
|||
<FormGroup label={<small>Other</small>}>
|
||||
{supportedEnvironments?.includes('guest-image') && (
|
||||
<Checkbox
|
||||
label="Virtualization - Guest image (.qcow2)"
|
||||
label='Virtualization - Guest image (.qcow2)'
|
||||
isChecked={environments.includes('guest-image')}
|
||||
onChange={() => {
|
||||
handleToggleEnvironment('guest-image');
|
||||
}}
|
||||
aria-label="Virtualization guest image checkbox"
|
||||
id="checkbox-guest-image"
|
||||
name="Virtualization guest image"
|
||||
aria-label='Virtualization guest image checkbox'
|
||||
id='checkbox-guest-image'
|
||||
name='Virtualization guest image'
|
||||
/>
|
||||
)}
|
||||
{supportedEnvironments?.includes('image-installer') && (
|
||||
<Checkbox
|
||||
label="Bare metal - Installer (.iso)"
|
||||
label='Bare metal - Installer (.iso)'
|
||||
isChecked={environments.includes('image-installer')}
|
||||
onChange={() => {
|
||||
handleToggleEnvironment('image-installer');
|
||||
}}
|
||||
aria-label="Bare metal installer checkbox"
|
||||
id="checkbox-image-installer"
|
||||
name="Bare metal installer"
|
||||
aria-label='Bare metal installer checkbox'
|
||||
id='checkbox-image-installer'
|
||||
name='Bare metal installer'
|
||||
/>
|
||||
)}
|
||||
{supportedEnvironments?.includes('wsl') && (
|
||||
|
|
@ -307,8 +340,8 @@ const TargetEnvironment = () => {
|
|||
<>
|
||||
WSL - Windows Subsystem for Linux (.wsl)
|
||||
<Popover
|
||||
maxWidth="30rem"
|
||||
position="right"
|
||||
maxWidth='30rem'
|
||||
position='right'
|
||||
headerContent={
|
||||
<Content>
|
||||
<Content>
|
||||
|
|
@ -329,13 +362,13 @@ const TargetEnvironment = () => {
|
|||
}
|
||||
footerContent={
|
||||
<Button
|
||||
component="a"
|
||||
target="_blank"
|
||||
variant="link"
|
||||
component='a'
|
||||
target='_blank'
|
||||
variant='link'
|
||||
icon={<ExternalLinkAltIcon />}
|
||||
iconPosition="right"
|
||||
iconPosition='right'
|
||||
isInline
|
||||
href="https://access.redhat.com/articles/7115538"
|
||||
href='https://access.redhat.com/articles/7115538'
|
||||
>
|
||||
Learn more about Red Hat's WSL support
|
||||
</Button>
|
||||
|
|
@ -343,9 +376,9 @@ const TargetEnvironment = () => {
|
|||
>
|
||||
<Button
|
||||
icon={<HelpIcon />}
|
||||
className="pf-v6-u-pl-sm pf-v6-u-pt-0 pf-v6-u-pb-0"
|
||||
variant="plain"
|
||||
aria-label="About WSL file"
|
||||
className='pf-v6-u-pl-sm pf-v6-u-pt-0 pf-v6-u-pb-0'
|
||||
variant='plain'
|
||||
aria-label='About WSL file'
|
||||
isInline
|
||||
/>
|
||||
</Popover>
|
||||
|
|
@ -355,9 +388,9 @@ const TargetEnvironment = () => {
|
|||
onChange={() => {
|
||||
handleToggleEnvironment('wsl');
|
||||
}}
|
||||
aria-label="windows subsystem for linux checkbox"
|
||||
id="checkbox-wsl"
|
||||
name="WSL"
|
||||
aria-label='windows subsystem for linux checkbox'
|
||||
id='checkbox-wsl'
|
||||
name='WSL'
|
||||
/>
|
||||
)}
|
||||
</FormGroup>
|
||||
|
|
|
|||
|
|
@ -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