Skip to content

Commit

Permalink
Merge pull request #5113 from rvykydal/webui-pre-blivet-dialogs
Browse files Browse the repository at this point in the history
Webui pre blivet dialogs
  • Loading branch information
rvykydal committed Sep 6, 2023
2 parents d258229 + 4476af5 commit caea6ad
Show file tree
Hide file tree
Showing 7 changed files with 226 additions and 28 deletions.
3 changes: 2 additions & 1 deletion ui/webui/src/components/Error.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -158,10 +158,11 @@ const addExceptionDataToReportURL = (url, exception) => {
};

const exceptionInfo = (exception, idPrefix) => {
const exceptionNamePrefix = exception.name ? exception.name + ": " : "";
return (
<TextContent id={idPrefix + "-bz-report-modal-details"}>
<Text component={TextVariants.p}>
{exception.name + ": " + exception.message}
{exceptionNamePrefix + exception.message}
</Text>
</TextContent>
);
Expand Down
188 changes: 164 additions & 24 deletions ui/webui/src/components/storage/InstallationMethod.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,20 @@ import {
Form,
FormGroup,
MenuToggle,
Modal,
Select,
SelectList,
SelectOption,
Spinner,
Text,
TextContent,
TextInputGroup,
TextInputGroupMain,
TextInputGroupUtilities,
TextVariants,
Title,
} from "@patternfly/react-core";
import { SyncAltIcon, TimesIcon, WrenchIcon } from "@patternfly/react-icons";
import { SyncAltIcon, TimesIcon, WrenchIcon, ExternalLinkAltIcon } from "@patternfly/react-icons";

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

Expand Down Expand Up @@ -288,9 +292,38 @@ const LocalDisksSelect = ({ deviceData, diskSelection, idPrefix, isRescanningDis
);
};

const InstallationDestination = ({ deviceData, diskSelection, dispatch, idPrefix, isBootIso, setIsFormValid, onCritFail }) => {
const rescanDisks = (setIsRescanningDisks, refUsableDisks, dispatch, errorHandler) => {
setIsRescanningDisks(true);
refUsableDisks.current = undefined;
scanDevicesWithTask()
.then(res => {
return runStorageTask({
task: res[0],
onSuccess: () => resetPartitioning()
.then(() => Promise.all([
dispatch(getDevicesAction()),
dispatch(getDiskSelectionAction())
]))
.catch(errorHandler),
onFail: errorHandler
});
})
.finally(() => setIsRescanningDisks(false));
};

const InstallationDestination = ({
deviceData,
diskSelection,
dispatch,
idPrefix,
isBootIso,
setIsFormValid,
onRescanDisks,
onCritFail
}) => {
const [isRescanningDisks, setIsRescanningDisks] = useState(false);
const [equalDisksNotify, setEqualDisksNotify] = useState(false);
const [openedDialog, setOpenedDialog] = useState("");
const refUsableDisks = useRef();

debug("DiskSelector: deviceData: ", JSON.stringify(Object.keys(deviceData)), ", diskSelection: ", JSON.stringify(diskSelection));
Expand Down Expand Up @@ -327,7 +360,7 @@ const InstallationDestination = ({ deviceData, diskSelection, dispatch, idPrefix

const loading = !deviceData || diskSelection.usableDisks.some(disk => !deviceData[disk]);

const errorHandler = onCritFail({
const rescanErrorHandler = onCritFail({
context: N_("Rescanning of the disks failed.")
});

Expand All @@ -340,24 +373,12 @@ const InstallationDestination = ({ deviceData, diskSelection, dispatch, idPrefix
variant="link"
isLoading={isRescanningDisks}
icon={<SyncAltIcon />}
onClick={() => {
setIsRescanningDisks(true);
refUsableDisks.current = undefined;
scanDevicesWithTask()
.then(res => {
return runStorageTask({
task: res[0],
onSuccess: () => resetPartitioning()
.then(() => Promise.all([
dispatch(getDevicesAction()),
dispatch(getDiskSelectionAction())
]))
.catch(errorHandler),
onFail: errorHandler
});
})
.finally(() => setIsRescanningDisks(false));
}}
onClick={() => rescanDisks(
setIsRescanningDisks,
refUsableDisks,
dispatch,
rescanErrorHandler
)}
>
{_("Rescan")}
</Button>
Expand Down Expand Up @@ -410,25 +431,144 @@ const InstallationDestination = ({ deviceData, diskSelection, dispatch, idPrefix
: _("No usable disks detected")
)}
{rescanDisksButton}
<ModifyStorageButton isBootIso={isBootIso} />
<ModifyStorageButton idPrefix={idPrefix} isBootIso={isBootIso} onModifyStorage={() => setOpenedDialog("modify")} />
</Flex>
</FormGroup>
{openedDialog === "modify" &&
<ModifyStorageModal
onClose={() => setOpenedDialog("")}
onToolStarted={() => setOpenedDialog("rescan")}
errorHandler={onCritFail({ context: N_("Modifying the storage failed.") })}
/>}
{openedDialog === "rescan" &&
<StorageModifiedModal
onClose={() => setOpenedDialog("")}
onRescan={() => rescanDisks(
setIsRescanningDisks,
refUsableDisks,
dispatch,
rescanErrorHandler
)}
/>}
</>
);
};

const ModifyStorageButton = ({ isBootIso }) => {
const ModifyStorageButton = ({ idPrefix, isBootIso, onModifyStorage }) => {
if (isBootIso) {
return null;
}

return (
<Button variant="link" icon={<WrenchIcon />} onClick={() => cockpit.spawn(["blivet-gui"])}>
<Button
id={idPrefix + "-modify-storage"}
variant="link"
icon={<WrenchIcon />}
onClick={() => onModifyStorage()}>
{_("Modify storage")}
</Button>
);
};

const startBlivetGUI = (onStart, onStarted, errorHandler) => {
console.log("Spawning blivet-gui.");
// We don't have an event informing that blivet-gui started so just wait a bit.
const timeoutId = window.setTimeout(onStarted, 3000);
cockpit.spawn(["blivet-gui"], { err: "message" })
.then(() => {
console.log("blivet-gui exited.");
// If the blivet-gui exits earlier cancel the delay
window.clearTimeout(timeoutId);
return onStarted();
})
.catch((error) => { window.clearTimeout(timeoutId); errorHandler(error) });
onStart();
};

const StorageModifiedModal = ({ onClose, onRescan }) => {
return (
<Modal
id="storage-modified-modal"
title={_("Modified storage")}
isOpen
variant="small"
showClose={false}
footer={
<>
<Button
onClick={() => { onClose(); onRescan() }}
variant="primary"
id="storage-modified-modal-rescan-btn"
key="rescan"
>
{_("Rescan storage")}
</Button>
<Button
variant="secondary"
onClick={() => onClose()}
id="storage-modified-modal-ignore-btn"
key="ignore"
>
{_("Ignore")}
</Button>
</>
}>
{_("If you have made changes on partitions or disks, please rescan storage.")}
</Modal>
);
};

const ModifyStorageModal = ({ onClose, onToolStarted, errorHandler }) => {
const [toolIsStarting, setToolIsStarting] = useState(false);
const onStart = () => setToolIsStarting(true);
const onStarted = () => { setToolIsStarting(false); onToolStarted() };
return (
<Modal
id="modify-storage-modal"
title={_("Modify storage")}
isOpen
variant="small"
titleIconVariant="warning"
showClose={false}
footer={
<>
<Button
onClick={() => startBlivetGUI(
onStart,
onStarted,
errorHandler
)}
id="modify-storage-modal-modify-btn"
icon={toolIsStarting ? null : <ExternalLinkAltIcon />}
isLoading={toolIsStarting}
isDisabled={toolIsStarting}
variant="primary"
>
{_("Launch Blivet-gui storage editor")}
</Button>
<Button
variant="link"
onClick={() => onClose()}
id="modify-storage-modal-cancel-btn"
key="cancel"
isDisabled={toolIsStarting}
>
{_("Cancel")}
</Button>
</>
}>
<TextContent>
<Text component={TextVariants.p}>
{_("Blivet-gui is and advanced storage editor that lets you resize, delete, and create partitions. It can set up LVM and much more.")}
</Text>
<Text component={TextVariants.p}>
{_("Changes made in Blivet-gui will directly affect your storage.")}
</Text>
</TextContent>
</Modal>
);
};

export const InstallationMethod = ({
deviceData,
diskSelection,
Expand Down
4 changes: 2 additions & 2 deletions ui/webui/test/check-basic
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ from language import Language
from review import Review
from storage import Storage
from testlib import nondestructive, test_main, wait # pylint: disable=import-error
from utils import pretend_live_iso


@nondestructive
Expand Down Expand Up @@ -83,8 +84,7 @@ class TestBasic(anacondalib.VirtInstallMachineCase):
i = Installer(b, m)
r = Review(b)

self.restore_file('/run/anaconda/anaconda.conf')
m.execute("sed -i 's/type = BOOT_ISO/type = LIVE_OS/g' /run/anaconda/anaconda.conf")
pretend_live_iso(self)

# For live media the first screen is the installation-method
i.open(step="installation-method")
Expand Down
50 changes: 50 additions & 0 deletions ui/webui/test/check-storage
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ from storage import Storage
from review import Review
from testlib import nondestructive, test_main # pylint: disable=import-error
from storagelib import StorageHelpers # pylint: disable=import-error
from utils import pretend_live_iso


@nondestructive
Expand Down Expand Up @@ -78,6 +79,55 @@ class TestStorage(anacondalib.VirtInstallMachineCase, StorageHelpers):
s.select_disk("vda", True)
s.select_none_disks_and_check([dev, "vda"])

def testModifyStorage(self):
b = self.browser
m = self.machine
i = Installer(b, self.machine)
s = Storage(b, self.machine)

self.addCleanup(m.execute, "killall blivet-gui")

pretend_live_iso(self)

# For live media the first screen is the installation-method
i.open(step="installation-method")

disk="vda"

# Check the auto-selected disk's details
s.check_single_disk_destination(disk, "16.1 GB")

# Pixel test the storage step
b.assert_pixels(
"#app",
"storage-step-basic-live",
ignore=["#betanag-icon"],
wait_animations=False,
)

s.modify_storage()
b.click("#modify-storage-modal-cancel-btn")

s.modify_storage()
# Run the tool
b.click("#modify-storage-modal-modify-btn")
b.wait_visible(f"#modify-storage-modal-modify-btn:not([aria-disabled={True}]")
b.wait_visible(f"#storage-modified-modal-rescan-btn")
b.click("#storage-modified-modal-ignore-btn")
# The disk is still selected
s.check_single_disk_destination(disk, "16.1 GB")

#s.modify_storage()
#b.click("#modify-storage-modal-cancel-btn")

s.modify_storage()
b.click("#modify-storage-modal-modify-btn")
b.wait_visible(f"#modify-storage-modal-modify-btn:not([aria-disabled={True}]")
b.wait_visible(f"#storage-modified-modal-rescan-btn")
b.click("#storage-modified-modal-rescan-btn")
# The disk is still selected
s.check_single_disk_destination(disk, "16.1 GB")

# Test moving back and forth between screens.
# Disk initialization mode is applied to the backend in the test.
# Partitioning is not applied to the backend in the test.
Expand Down
3 changes: 3 additions & 0 deletions ui/webui/test/helpers/storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,9 @@ def rescan_disks(self):
self.browser.click(f"#{self._step}-rescan-disks")
self.browser.wait_not_present(f"#{self._step}-rescan-disks.pf-m-disabled")

def modify_storage(self):
self.browser.click(f"#{self._step}-modify-storage")

@log_step(snapshot_before=True)
def check_disk_visible(self, disk, visible=True):
if not self.browser.is_present(f".pf-v5-c-menu[aria-labelledby='{id_prefix}-disk-selector-title']"):
Expand Down
4 changes: 4 additions & 0 deletions ui/webui/test/helpers/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,7 @@ def add_public_key(machine):
authorized_keys = os.path.join(sysroot_ssh, 'authorized_keys')
machine.execute(f"chmod 700 {sysroot_ssh}")
machine.write(authorized_keys, public_key, perm="0600")

def pretend_live_iso(test):
test.restore_file('/run/anaconda/anaconda.conf')
test.machine.execute("sed -i 's/type = BOOT_ISO/type = LIVE_OS/g' /run/anaconda/anaconda.conf")
2 changes: 1 addition & 1 deletion ui/webui/test/reference

0 comments on commit caea6ad

Please sign in to comment.