/* * Copyright (C) 2022 Red Hat, Inc. * SPDX-License-Identifier: LGPL-2.1-or-later */ import cockpit from "cockpit"; import React, { useContext, useEffect, useRef, useState } from "react"; import { Alert } from "@patternfly/react-core/dist/esm/components/Alert/index.js"; import { Button } from "@patternfly/react-core/dist/esm/components/Button/index.js"; import { Divider } from "@patternfly/react-core/dist/esm/components/Divider/index.js"; import { FormGroup, FormSection } from "@patternfly/react-core/dist/esm/components/Form/index.js"; import { Menu, MenuContent, MenuItem, MenuList } from "@patternfly/react-core/dist/esm/components/Menu/index.js"; import { Modal, ModalBody, ModalFooter, ModalHeader } from "@patternfly/react-core/dist/esm/components/Modal/index.js"; import { Title } from "@patternfly/react-core/dist/esm/components/Title/index.js"; import { Flex, FlexItem } from "@patternfly/react-core/dist/esm/layouts/Flex/index.js"; import { Stack } from "@patternfly/react-core/dist/esm/layouts/Stack/index.js"; import { SyncAltIcon } from "@patternfly/react-icons/dist/esm/icons/sync-alt-icon"; import { runStorageTask, scanDevicesWithTask, } from "../../../apis/storage.js"; import { setBootloaderDrive } from "../../../apis/storage_bootloader.js"; import { setSelectedDisks } from "../../../apis/storage_disks_selection.js"; import { resetPartitioning } from "../../../apis/storage_partitioning.js"; import { getDevicesAction, getDiskSelectionAction } from "../../../actions/storage-actions.js"; import { debug as loggerDebug } from "../../../helpers/log.js"; import { getDeviceChildren, selectDefaultDisks } from "../../../helpers/storage.js"; import { checkIfArraysAreEqual } from "../../../helpers/utils.js"; import { StorageContext } from "../../../contexts/Common.jsx"; import { useDiskTotalSpace, useOriginalDevices, useOriginalExistingSystems, useRequiredSize, } from "../../../hooks/Storage.jsx"; import { EmptyStatePanel } from "cockpit-components-empty-state.jsx"; import "./InstallationDestination.scss"; const _ = cockpit.gettext; const N_ = cockpit.noop; const idPrefix = "anaconda-screen-method"; const debug = loggerDebug.bind(null, idPrefix + ":"); const DeviceExistingInstallation = ({ device }) => { const originalExistingSystems = useOriginalExistingSystems(); const devices = useOriginalDevices(); const children = getDeviceChildren({ device, deviceData: devices }); const existingSystemsOnDevice = originalExistingSystems.filter( system => system.devices.v.some(device => children.includes(device)) ); if (existingSystemsOnDevice.length === 0) { return null; } return ( cockpit.format( _("Currently installed: $0"), existingSystemsOnDevice.map(system => system["os-name"].v).join(", ") ) ); }; const LocalDisksSelect = ({ dispatch, idPrefix, isRescanningDisks, onCritFail, setIsRescanningDisks, setUnappliedSelection, unappliedSelection, }) => { const { diskSelection } = useContext(StorageContext); const devices = useOriginalDevices(); const onSelect = (event, disk) => { if (disk === "rescan") { return; } const newSelection = unappliedSelection.includes(disk) ? unappliedSelection.filter(selectedDisk => selectedDisk !== disk) : unappliedSelection.concat(disk); setUnappliedSelection(newSelection); }; const rescanErrorHandler = onCritFail({ context: N_("Rescanning of the disks failed.") }); const onClickRescan = () => rescanDisks( setIsRescanningDisks, dispatch, rescanErrorHandler, ); const rescanDisksButton = ( ); return ( {rescanDisksButton} {diskSelection.usableDisks.length === 0 && ( )} {diskSelection.usableDisks.length > 0 && <> {diskSelection.usableDisks.map(disk => ( {cockpit.format( _("$0 $1"), cockpit.format_bytes(devices[disk]?.total.v), devices[disk]?.type.v )} } hasCheckbox id={idPrefix + "-disk-selection-menu-item-" + disk} isAriaDisabled={isRescanningDisks} isSelected={unappliedSelection.includes(disk)} itemId={disk} key={disk} > {cockpit.format( _("$0 ($1)"), devices[disk]?.description.v, devices[disk]?.name.v )} ))} } ); }; const rescanDisks = (setIsRescanningDisks, dispatch, errorHandler) => { setIsRescanningDisks(true); scanDevicesWithTask() .then(task => { return runStorageTask({ onFail: exc => { setIsRescanningDisks(false); errorHandler(exc); }, onSuccess: () => Promise.all([ resetPartitioning(), setBootloaderDrive({ drive: "" }), ]) .then(() => Promise.all([ dispatch(getDevicesAction()), dispatch(getDiskSelectionAction()) ])) .finally(() => { setIsRescanningDisks(false); }) .catch(errorHandler), task }); }); }; export const InstallationDestination = ({ dispatch, idPrefix, isFirstScreen, onCritFail, setIsDestinationValid, }) => { const refUsableDisks = useRef(); const { diskSelection } = useContext(StorageContext); const devices = useOriginalDevices(); const usableDisksStr = diskSelection.usableDisks.join(","); debug("DiskSelector: devices: ", JSON.stringify(Object.keys(devices)), ", diskSelection: ", JSON.stringify(diskSelection)); useEffect(() => { refUsableDisks.current = false; }, [usableDisksStr]); useEffect(() => { // Select default disks for the partitioning on component mount if (refUsableDisks.current === true) { return; } refUsableDisks.current = true; const defaultDisks = selectDefaultDisks({ ignoredDisks: diskSelection.ignoredDisks, selectedDisks: diskSelection.selectedDisks, usableDisks: diskSelection.usableDisks, }); if (!checkIfArraysAreEqual(diskSelection.selectedDisks, defaultDisks)) { setSelectedDisks({ drives: defaultDisks }); } }, [diskSelection]); const selectedDisksCnt = diskSelection.selectedDisks.length; const diskTotalSpace = useDiskTotalSpace(); const requiredSize = useRequiredSize(); useEffect(() => { if (selectedDisksCnt === 0) { setIsDestinationValid(false); return; } if (diskTotalSpace == null || requiredSize == null) { setIsDestinationValid(undefined); return; } setIsDestinationValid(diskTotalSpace >= requiredSize); }, [selectedDisksCnt, diskTotalSpace, requiredSize, setIsDestinationValid]); const headingLevel = isFirstScreen ? "h3" : "h2"; return ( {_("Destination")}} > {diskSelection.selectedDisks.map(disk => ( {cockpit.format( _("$0 ($1)"), devices[disk]?.description.v, devices[disk]?.name.v )} {cockpit.format( _("$0 $1"), cockpit.format_bytes(devices[disk]?.total.v), devices[disk]?.type.v )} ))} {diskSelection.selectedDisks.length > 0 && } {diskSelection.selectedDisks.length === 0 && } ); }; const InsufficientSpace = () => { const diskTotalSpace = useDiskTotalSpace(); const requiredSize = useRequiredSize(); if (diskTotalSpace >= requiredSize) { return null; } return ( {cockpit.format(_("Minimum of $0 required"), cockpit.format_bytes(requiredSize))} ); }; const NoDisksSelected = () => ( {_("Please select at least one disk to continue")} ); const ChangeDestination = ({ dispatch, idPrefix, onCritFail }) => { const [isRescanningDisks, setIsRescanningDisks] = useState(false); const [isModalOpen, setIsModalOpen] = useState(false); const { diskSelection } = useContext(StorageContext); const [unappliedSelection, setUnappliedSelection] = useState(diskSelection.selectedDisks); useEffect(() => { setUnappliedSelection(diskSelection.selectedDisks); }, [diskSelection.selectedDisks]); const onSave = async () => { await Promise.all([ setSelectedDisks({ drives: unappliedSelection }), setBootloaderDrive({ drive: "" }), ]); setIsModalOpen(false); }; return ( <> {isModalOpen && ( setIsModalOpen(false)} > {diskSelection.usableDisks.length > 0 && ( <> )} {diskSelection.usableDisks.length === 0 && ( )} )} ); };