🏡 index : quine-global/quine-anaconda-webui.git

/*
 * 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);
    // Always reset the partitioning when entering the installation destination page
    // If the last partitioning applied was from the cockpit storage integration
    // or from a kickstart we should not reset it
    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;
};