Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 20 additions & 14 deletions docs/installation-steps.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@ Installation Flow Overview
This document describes the step-by-step process for installing the system using the Web UI.

1. **Welcome**
2. **Date and time**
3. **Software selection**
4. **Installation method**
5. **Storage configuration**
6. **Create Account**
7. **Review and install**
8. **Installation progress**
2. **Network**
3. **Date and time**
4. **Software selection**
5. **Installation method**
6. **Storage configuration**
7. **Create Account**
8. **Review and install**
9. **Installation progress**

Detailed Step Descriptions
=============================
Expand All @@ -21,17 +22,22 @@ Detailed Step Descriptions

Select the language & keyboard to use during installation and for the target system.

2. Date and time
2. Network
----------

Configure network connections for the system.

3. Date and time
----------------

Configure your system's timezone, date, and time settings. You can also set up network time synchronization.

3. Software selection
4. Software selection
---------------------

Select packages to install by choosing a base environment.

4. Installation method
5. Installation method
----------------------

Choose the target device(s) for the installation and the partitioning scenario.
Expand Down Expand Up @@ -73,22 +79,22 @@ Reinstalls Fedora while preserving your existing home directory and user data. U

Installs using only unallocated free space, preserving existing partitions and data. Use when you want to dual-boot with existing operating systems. This option only appears when existing partitions are detected on the selected disks.

5. Storage configuration
6. Storage configuration
------------------------

Automatic partitioning configuration, disk encryption, and storage options.

6. Create Account
7. Create Account
-----------------

Set up user accounts and administrator passwords for your system.

7. Review and install
8. Review and install
---------------------

Review your installation settings and start the installation process.

8. Installation progress
9. Installation progress
------------------------

Monitor the installation progress and completion.
Expand Down
1 change: 1 addition & 0 deletions src/components/AnacondaWizard.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ export const AnacondaWizard = ({ currentStepId, dispatch, isFetching, onCritFail
let stepProps = {
id: s.id,
isAriaDisabled: isFormDisabled || isFetching,
isDisabled: isFormDisabled || isFetching,
isHidden: s.isHidden || s.isFinal,
isVisited,
name: s.label,
Expand Down
24 changes: 2 additions & 22 deletions src/components/network/CockpitNetworkConfiguration.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,32 +11,12 @@ import { PageSection } from "@patternfly/react-core/dist/esm/components/Page/ind

import { useMaybeBackdrop } from "../../hooks/CockpitIntegration.jsx";

import { useNetworkStatus } from "./useNetworkStatus.js";

import "./CockpitNetworkConfiguration.scss";

const _ = cockpit.gettext;

// Hook to track checkpoint status from the Cockpit networkmanager iframe
const useNetworkStatus = () => {
const [hasActiveCheckpoint, setHasActiveCheckpoint] = useState(false);

useEffect(() => {
const checkpointState = window.sessionStorage.getItem("cockpit_has_checkpoint");
setHasActiveCheckpoint(checkpointState === "true");

const handleCheckpointEvent = (event) => {
if (event.key === "cockpit_has_checkpoint") {
setHasActiveCheckpoint(event.newValue === "true");
}
};

window.addEventListener("storage", handleCheckpointEvent);

return () => window.removeEventListener("storage", handleCheckpointEvent);
}, []);

return { hasActiveCheckpoint };
};

export const CockpitNetworkConfiguration = ({
onCritFail,
setIsNetworkOpen,
Expand Down
70 changes: 70 additions & 0 deletions src/components/network/NetworkConfiguration.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
* Copyright (C) 2025 Red Hat, Inc.
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
import cockpit from "cockpit";

import React, { useEffect, useState } from "react";

import { useMaybeBackdrop } from "../../hooks/CockpitIntegration.jsx";

import { useNetworkStatus } from "./useNetworkStatus.js";

import "./NetworkConfiguration.scss";

const _ = cockpit.gettext;

export const NetworkConfiguration = ({
onCritFail,
setIsFormDisabled,
setIsFormValid,
}) => {
const [isIframeMounted, setIsIframeMounted] = useState(false);
const { hasActiveCheckpoint } = useNetworkStatus();
const backdropClass = useMaybeBackdrop();
const handleIframeLoad = () => setIsIframeMounted(true);
const idPrefix = "network-configuration";

const hasModal = backdropClass !== "";
const isBlocked = hasActiveCheckpoint || hasModal;

useEffect(() => {
setIsFormValid(!isBlocked);
setIsFormDisabled(isBlocked);
}, [isBlocked, setIsFormDisabled, setIsFormValid]);

useEffect(() => {
if (isIframeMounted) {
const iframe = document.getElementById("network-configuration-frame");
iframe.contentWindow.addEventListener("error", exception => {
onCritFail({ context: _("Network plugin failed") })({ message: exception.error.message, stack: exception.error.stack });
});

// Hide elements not needed in the installer context
const hideSelectors = ["#networking-graphs", ".cockpit-log-panel"];
const iframeDoc = iframe.contentDocument;
const observer = new MutationObserver(() => {
hideSelectors.forEach(sel => {
const el = iframeDoc.querySelector(sel);
if (el && el.style.display !== "none") {
el.style.display = "none";
}
});
});
observer.observe(iframeDoc.body, { childList: true, subtree: true });

return () => observer.disconnect();
}
}, [isIframeMounted, onCritFail]);

return (
<div className={backdropClass + " " + idPrefix + "-page-section"}>
<iframe
src="/cockpit/@localhost/network/index.html"
name="network-configuration"
id="network-configuration-frame"
onLoad={handleIframeLoad}
className={idPrefix + "-iframe"} />
</div>
);
};
18 changes: 18 additions & 0 deletions src/components/network/NetworkConfiguration.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* Copyright (C) 2025 Red Hat, Inc.
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
@import "global-variables";
@import "../../styles/cockpit-iframe-backdrop";

.network-configuration-page-section,
.network-configuration-iframe {
width: 100%;
height: 100%;
}

.network-configuration-iframe {
border-radius: var(--pf-t--global--border--radius--medium);
border: var(--pf-t--global--border--width--box--default) solid var(--pf-t--global--border--color--default);
background-clip: content-box;
}
22 changes: 22 additions & 0 deletions src/components/network/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* Copyright (C) 2025 Red Hat, Inc.
* SPDX-License-Identifier: LGPL-2.1-or-later
*/

import cockpit from "cockpit";

import { NetworkConfiguration } from "./NetworkConfiguration.jsx";

const _ = cockpit.gettext;

export class Page {
_description = "Configure network connections for the system.";

constructor ({ isBootIso }) {
this.component = NetworkConfiguration;
this.id = "anaconda-screen-network";
this.isHidden = !isBootIso;
this.label = _("Network");
this.title = _("Network Configuration");
}
}
27 changes: 27 additions & 0 deletions src/components/network/useNetworkStatus.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Copyright (C) 2025 Red Hat, Inc.
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
import { useEffect, useState } from "react";

// Hook to track checkpoint status from the Cockpit networkmanager iframe
export const useNetworkStatus = () => {
const [hasActiveCheckpoint, setHasActiveCheckpoint] = useState(false);

useEffect(() => {
const checkpointState = window.sessionStorage.getItem("cockpit_has_checkpoint");
setHasActiveCheckpoint(checkpointState === "true");

const handleCheckpointEvent = (event) => {
if (event.key === "cockpit_has_checkpoint") {
setHasActiveCheckpoint(event.newValue === "true");
}
};

window.addEventListener("storage", handleCheckpointEvent);

return () => window.removeEventListener("storage", handleCheckpointEvent);
}, []);

return { hasActiveCheckpoint };
};
2 changes: 2 additions & 0 deletions src/components/steps.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { debug } from "../helpers/log.js";
import { Page as PageDateAndTime } from "./datetime/index.js";
import { Page as PageProgress } from "./installation/index.js";
import { Page as PageInstallationLanguage } from "./localization/index.js";
import { Page as PageNetworkConfiguration } from "./network/index.js";
import { Page as PageReviewConfiguration } from "./review/index.js";
import { Page as PageSoftwareSelection } from "./software/index.js";
import { Page as PageInstallationMethod } from "./storage/installation-method/index.js";
Expand All @@ -23,6 +24,7 @@ export const getSteps = (userInterfaceConfig, args) => {
const hiddenScreens = userInterfaceConfig.hidden_webui_pages || [];
const stepsOrder = [
new PageInstallationLanguage(args),
new PageNetworkConfiguration(args),
new PageDateAndTime(args),
new PageSoftwareSelection(args),
new PageInstallationMethod(args),
Expand Down
6 changes: 5 additions & 1 deletion test/helpers/installer.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ class InstallerSteps(UserDict):
DATE_TIME = steps.DATE_TIME
CUSTOM_MOUNT_POINT = steps.CUSTOM_MOUNT_POINT
INSTALLATION_METHOD = steps.INSTALLATION_METHOD
NETWORK = steps.NETWORK
SOFTWARE_SELECTION = steps.SOFTWARE_SELECTION
LANGUAGE = steps.LANGUAGE
PROGRESS = steps.PROGRESS
Expand All @@ -30,14 +31,16 @@ def __init__(self, hidden_steps=None, scenario=None, machine=None):
CUSTOM_MOUNT_POINT = self.CUSTOM_MOUNT_POINT
DATE_TIME = self.DATE_TIME
INSTALLATION_METHOD = self.INSTALLATION_METHOD
NETWORK = self.NETWORK
SOFTWARE_SELECTION = self.SOFTWARE_SELECTION
LANGUAGE = self.LANGUAGE
PROGRESS = self.PROGRESS
REVIEW = self.REVIEW
STORAGE_CONFIGURATION = self.STORAGE_CONFIGURATION

_steps_jump = {
LANGUAGE: [DATE_TIME],
LANGUAGE: [NETWORK],
NETWORK: [DATE_TIME],
DATE_TIME: [SOFTWARE_SELECTION],
SOFTWARE_SELECTION: [INSTALLATION_METHOD],
STORAGE_CONFIGURATION: [ACCOUNTS],
Expand Down Expand Up @@ -182,6 +185,7 @@ def open(self, step=None):

def click_step_on_sidebar(self, step=None):
step = step or self.get_current_page()
self.browser.wait_visible(f"#{step}:not([disabled])")
self.browser.click(f"#{step}")

@log_step()
Expand Down
1 change: 1 addition & 0 deletions test/helpers/steps.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@


LANGUAGE = "anaconda-screen-language"
NETWORK = "anaconda-screen-network"
DATE_TIME = "anaconda-screen-date-time"
INSTALLATION_METHOD = "anaconda-screen-method"
SOFTWARE_SELECTION = "anaconda-screen-software-selection"
Expand Down
2 changes: 1 addition & 1 deletion test/reference
Submodule reference updated 27 files
+ TestInteractiveDefaults-testBasic-review-kickstarted-dark-pixels.png
+ TestInteractiveDefaults-testBasic-review-kickstarted-pixels.png
+ TestInteractiveDefaults-testBasic-review-kickstarted-rtl-pixels.png
+ TestInteractiveDefaults-testBasic-users-kickstarted-dark-pixels.png
+ TestInteractiveDefaults-testBasic-users-kickstarted-pixels.png
+ TestInteractiveDefaults-testBasic-users-kickstarted-rtl-pixels.png
+ TestLanguage-testLanguageSwitching-language-step-basic-dark-pixels.png
+ TestLanguage-testLanguageSwitching-language-step-basic-pixels.png
+ TestLanguage-testLanguageSwitching-language-step-basic-rtl-pixels.png
+ TestStorageBasic-testLocalStandardDisks-storage-step-basic-dark-pixels.png
+ TestStorageBasic-testLocalStandardDisks-storage-step-basic-pixels.png
+ TestStorageBasic-testLocalStandardDisks-storage-step-basic-rtl-pixels.png
+ TestStorageBasic-testLocalStandardDisks-storage-step-no-disks-dark-pixels.png
+ TestStorageBasic-testLocalStandardDisks-storage-step-no-disks-pixels.png
+ TestStorageBasic-testLocalStandardDisks-storage-step-no-disks-rtl-pixels.png
+ TestStorageEncryption-testEncryptionPassword-storage-step-encrypt-dark-pixels.png
+ TestStorageEncryption-testEncryptionPassword-storage-step-encrypt-pixels.png
+ TestStorageEncryption-testEncryptionPassword-storage-step-encrypt-rtl-pixels.png
+ TestStorageEncryption-testEncryptionPassword-storage-step-password-dark-pixels.png
+ TestStorageEncryption-testEncryptionPassword-storage-step-password-pixels.png
+ TestStorageEncryption-testEncryptionPassword-storage-step-password-rtl-pixels.png
+ TestStorageMountPoints-testMultipleDisks-review-multiple-disks-dark-pixels.png
+ TestStorageMountPoints-testMultipleDisks-review-multiple-disks-pixels.png
+ TestStorageMountPoints-testMultipleDisks-review-multiple-disks-rtl-pixels.png
+ TestUsers-testBasic-users-step-basic-dark-pixels.png
+ TestUsers-testBasic-users-step-basic-pixels.png
+ TestUsers-testBasic-users-step-basic-rtl-pixels.png
3 changes: 2 additions & 1 deletion webui-desktop
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,8 @@ else
XDG_SESSION_TYPE=wayland \
XDG_RUNTIME_DIR="${INSTALLER_XDG_RUNTIME_DIR}" \
WAYLAND_DISPLAY="${ROOT_WAYLAND_SOCKET}" \
dbus-run-session -- "${BROWSER[@]}" "$WEBUI_URL" &
dbus-run-session -- "${BROWSER[@]}" "$WEBUI_URL" \
>/tmp/webui-browser.log 2>&1 &
BLOCK_ON_PID=$!
fi
else
Expand Down
Loading