* Copyright (C) 2024 Red Hat, Inc.
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
import { useContext, useEffect, useMemo, useRef, useState } from "react";
import {
getRequiredSpace
} from "../apis/payloads.js";
import {
getDiskFreeSpace,
getDiskTotalSpace,
getFormatTypeData,
getFreeSpaceForSystem,
getMountPointConstraints,
getRequiredDeviceSize,
} from "../apis/storage_devicetree.js";
import {
setInitializationMode,
setInitializeLabelsEnabled,
} from "../apis/storage_disk_initialization.js";
import {
createPartitioning,
getDeviceTree,
partitioningSetEncrypt,
partitioningSetHomeReuse,
partitioningSetPassphrase,
resetPartitioning,
} from "../apis/storage_partitioning.js";
import {
getDeviceAncestors,
hasReusableFedoraWithWindowsOS,
intersectSelectedDisksWithUsable,
isDeviceEncrypted,
systemMountPoints,
} from "../helpers/storage.js";
import { PageContext, StorageContext, StorageDefaultsContext, TargetSystemRootContext } from "../contexts/Common.jsx";
import { scenarios } from "../components/storage/scenarios/index.js";
export const useDiskTotalSpace = () => {
const [diskTotalSpace, setDiskTotalSpace] = useState();
const devices = useOriginalDevices();
const { diskSelection } = useContext(StorageContext);
const selectedDisks = diskSelection.selectedDisks;
const usableDisks = diskSelection.usableDisks;
useEffect(() => {
const update = async () => {
const diskNames = intersectSelectedDisksWithUsable(selectedDisks, usableDisks);
const diskTotalSpace = await getDiskTotalSpace({ diskNames });
setDiskTotalSpace(diskTotalSpace);
};
update();
}, [selectedDisks, usableDisks, devices]);
return diskTotalSpace;
};
export const useDiskFreeSpace = () => {
const [diskFreeSpace, setDiskFreeSpace] = useState();
const devices = useOriginalDevices();
const { diskSelection } = useContext(StorageContext);
const selectedDisks = diskSelection.selectedDisks;
const usableDisks = diskSelection.usableDisks;
useEffect(() => {
const update = async () => {
const diskNames = intersectSelectedDisksWithUsable(selectedDisks, usableDisks);
const diskFreeSpace = await getDiskFreeSpace({ diskNames });
setDiskFreeSpace(diskFreeSpace);
};
update();
}, [selectedDisks, usableDisks, devices]);
return diskFreeSpace;
};
export const useFreeSpaceForSystem = () => {
const [freeSpaceForSystem, setFreeSpaceForSystem] = useState();
const devices = useOriginalDevices();
const { diskSelection } = useContext(StorageContext);
const selectedDisks = diskSelection.selectedDisks;
const plannedDeviceTree = useDeviceTree();
useEffect(() => {
const update = async () => {
const freeSpaceForSystem = await getFreeSpaceForSystem({ mountPoints: systemMountPoints });
setFreeSpaceForSystem(freeSpaceForSystem);
};
update();
}, [selectedDisks, devices, plannedDeviceTree]);
return freeSpaceForSystem;
};
export const useUsablePartitions = () => {
const [usablePartitions, setUsablePartitions] = useState();
const devices = useOriginalDevices();
const { diskSelection } = useContext(StorageContext);
const selectedDisks = diskSelection.selectedDisks;
useEffect(() => {
const _usablePartitions = Object.values(devices).filter(device => {
const ancestors = getDeviceAncestors(devices, device["device-id"].v);
return (
device.formatData?.type.v === "biosboot" ||
device.formatData?.mountable.v ||
isDeviceEncrypted(device)) &&
ancestors.some(ancestor => selectedDisks.includes(ancestor));
});
setUsablePartitions(_usablePartitions);
}, [selectedDisks, devices]);
return usablePartitions;
};
export const useRequiredSpace = () => {
const [requiredSpace, setRequiredSpace] = useState();
useEffect(() => {
const update = async () => {
const requiredSpace = await getRequiredSpace();
setRequiredSpace(requiredSpace);
};
update();
}, []);
return requiredSpace;
};
export const useRequiredSize = () => {
const [requiredSize, setRequiredSize] = useState();
useEffect(() => {
const update = async () => {
const requiredSpace = await getRequiredSpace();
const requiredSize = await getRequiredDeviceSize({ requiredSpace });
setRequiredSize(requiredSize);
};
update();
}, []);
return requiredSize;
};
export const useMountPointConstraints = () => {
const [mountPointConstraints, setMountPointConstraints] = useState();
const devices = useOriginalDevices();
const { diskSelection } = useContext(StorageContext);
const selectedDisks = diskSelection.selectedDisks;
const usableDisks = diskSelection.usableDisks;
useEffect(() => {
const update = async () => {
const diskNames = intersectSelectedDisksWithUsable(selectedDisks, usableDisks);
let _mountPointConstraints = await getMountPointConstraints({ diskNames });
_mountPointConstraints = await Promise.all(_mountPointConstraints.map(async c => {
let description = "";
const formatType = c["required-filesystem-type"].v;
if (formatType) {
const formatData = await getFormatTypeData({ formatType });
description = formatData.description.v;
}
return { ...c, description };
}));
setMountPointConstraints(_mountPointConstraints);
};
update();
}, [selectedDisks, usableDisks, devices]);
return mountPointConstraints;
};
export const useHomeReuseOptions = () => {
const originalExistingSystems = useOriginalExistingSystems();
const { defaultScheme } = useContext(StorageDefaultsContext);
const devices = useOriginalDevices();
const { diskSelection } = useContext(StorageContext);
const selectedDisks = diskSelection.selectedDisks;
const reuseEFIPart = useMemo(
() => hasReusableFedoraWithWindowsOS(devices, selectedDisks, originalExistingSystems),
[devices, selectedDisks, originalExistingSystems]
);
return {
reuseEFIPart,
scheme: defaultScheme,
};
};
export const getNewPartitioning = async ({
currentPartitioning,
homeReuseOptions,
method = "AUTOMATIC",
storageScenarioId,
}) => {
const initializationMode = scenarios.find(sc => sc.id === storageScenarioId).initializationMode;
await setInitializationMode({ mode: initializationMode });
if (method === "AUTOMATIC") {
await setInitializeLabelsEnabled({ enabled: true });
}
const part = await createPartitioning({ method });
if (storageScenarioId === "home-reuse") {
await partitioningSetHomeReuse({ homeReuseOptions, partitioning: part });
}
if (currentPartitioning?.method === method &&
method === "AUTOMATIC" &&
storageScenarioId !== "home-reuse" &&
currentPartitioning.requests[0].encrypted) {
await partitioningSetEncrypt({ encrypt: true, partitioning: part });
await partitioningSetPassphrase({ partitioning: part, passphrase: currentPartitioning.requests[0].passphrase });
}
return part;
};
export const usePartitioningReset = () => {
const { setIsFormDisabled } = useContext(PageContext) ?? {};
const { appliedPartitioning, partitioning } = useContext(StorageContext);
const pageHasMounted = useRef(false);
const needsReset = partitioning.storageScenarioId !== "use-configured-storage" &&
partitioning.storageScenarioId !== "use-configured-storage-kickstart" &&
appliedPartitioning &&
pageHasMounted.current !== true;
useEffect(() => {
pageHasMounted.current = true;
if (needsReset) {
resetPartitioning();
} else {
setIsFormDisabled(false);
}
}, [needsReset, setIsFormDisabled]);
return !needsReset;
};
export const useDeviceTree = () => {
const [deviceTreePath, setDeviceTreePath] = useState();
const { appliedPartitioning, deviceTrees } = useContext(StorageContext);
useEffect(() => {
const _getDeviceTree = async () => {
const _deviceTreePath = appliedPartitioning ? await getDeviceTree({ partitioning: appliedPartitioning }) : "";
setDeviceTreePath(_deviceTreePath);
};
_getDeviceTree();
}, [appliedPartitioning, deviceTrees]);
return deviceTrees[deviceTreePath];
};
export const useOriginalDeviceTree = () => {
const { deviceTrees } = useContext(StorageContext);
return deviceTrees[""];
};
export const usePlannedActions = () => {
const plannedDeviceTree = useDeviceTree();
return plannedDeviceTree ? plannedDeviceTree.actions : [];
};
export const usePlannedDevices = () => {
const plannedDeviceTree = useDeviceTree();
return plannedDeviceTree ? plannedDeviceTree.devices : {};
};
export const usePlannedExistingSystems = () => {
const plannedDeviceTree = useDeviceTree();
return plannedDeviceTree ? plannedDeviceTree.existingSystems : [];
};
export const usePlannedMountPoints = () => {
const plannedDeviceTree = useDeviceTree();
return plannedDeviceTree ? plannedDeviceTree.mountPoints : [];
};
export const useOriginalExistingSystems = () => {
const originalDeviceTree = useOriginalDeviceTree();
return originalDeviceTree ? originalDeviceTree.existingSystems : [];
};
export const useOriginalDevices = () => {
const originalDeviceTree = useOriginalDeviceTree();
return originalDeviceTree ? originalDeviceTree.devices : {};
};
export const useLaunchStorageEditor = ({ setShowStorage }) => {
const targetSystemRoot = useContext(TargetSystemRootContext);
const { diskSelection } = useContext(StorageContext);
const devices = useOriginalDevices();
const mountPointConstraints = useMountPointConstraints();
const launchStorageEditor = useMemo(() => {
const availableDevices = [
...diskSelection.selectedDisks,
...diskSelection.selectedDisks.map(disk => getDeviceAncestors(devices, disk)).flat(),
];
const isEfi = mountPointConstraints?.some(c => c["required-filesystem-type"]?.v === "efi");
const cockpitAnaconda = JSON.stringify({
available_devices: availableDevices.map(device => devices[device]?.path.v).filter(Boolean),
efi: isEfi,
mount_point_prefix: targetSystemRoot,
});
return () => {
window.sessionStorage.setItem("cockpit_anaconda", cockpitAnaconda);
setShowStorage(true);
};
}, [devices, diskSelection.selectedDisks, mountPointConstraints, setShowStorage, targetSystemRoot]);
return launchStorageEditor;
};