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

/*
 * Copyright (C) 2025 Red Hat, Inc.
 * SPDX-License-Identifier: LGPL-2.1-or-later
 */

import cockpit from "cockpit";

import React, { useContext, useEffect, useState } from "react";
import { Button } from "@patternfly/react-core/dist/esm/components/Button/index.js";
import { useWizardContext } from "@patternfly/react-core/dist/esm/components/Wizard/index.js";

import { applyStorage } from "../../../apis/storage_partitioning.js";

import { PageContext, StorageContext } from "../../../contexts/Common.jsx";

import {
    useFreeSpaceForSystem,
    useOriginalDevices,
    useRequiredSpace,
} from "../../../hooks/Storage.jsx";

import { createStorageValidationNotification } from "../Common.jsx";

const _ = cockpit.gettext;

const REVIEW_STEP_ID = "anaconda-screen-review";
const STORAGE_METHOD_STEP_ID = "anaconda-screen-method";
const MOUNT_POINT_MAPPING_STEP_ID = "anaconda-screen-mount-point-mapping";

/** Readiness of storage configuration for the review step */
const StorageCompleteStatus = Object.freeze({
    INSUFFICIENT: "insufficient",
    OK: "ok",
    PENDING: "pending",
});

/**
 * Storage readiness for the review screen (partitioning applied, scenario set, and enough free space).
 * ``status`` is one of ``StorageCompleteStatus``.
 */
export const useStorageComplete = () => {
    const { appliedPartitioning, storageScenarioId } = useContext(StorageContext);
    const freeSpace = useFreeSpaceForSystem();
    const requiredSpace = useRequiredSpace();

    const hasInsufficientSpace =
        requiredSpace != null && freeSpace != null && requiredSpace > freeSpace;

    const pending =
        appliedPartitioning === undefined ||
        requiredSpace === undefined ||
        freeSpace === undefined;

    if (pending) {
        return { freeSpace, requiredSpace, status: StorageCompleteStatus.PENDING };
    }

    return {
        freeSpace,
        requiredSpace,
        status: hasInsufficientSpace || !appliedPartitioning || !storageScenarioId
            ? StorageCompleteStatus.INSUFFICIENT
            : StorageCompleteStatus.OK,
    };
};

const useApplyStorageOnReview = () => {
    const [validationPending, setValidationPending] = useState(false);
    const [validationReport, setValidationReport] = useState(null);
    const { appliedPartitioning, partitioning } = useContext(StorageContext);
    const { setStepNotification } = useContext(PageContext) ?? {};
    const devices = useOriginalDevices();
    const partitioningPath = partitioning?.path;

    useEffect(() => {
        if (appliedPartitioning || !partitioningPath) {
            return;
        }

        const step = REVIEW_STEP_ID;

        setValidationPending(true);
        (async () => {
            try {
                const { validationReport } = await applyStorage({ devices, partitioning: partitioningPath });
                setValidationReport(validationReport);
                setStepNotification?.(createStorageValidationNotification(validationReport, step));
            } catch (ex) {
                setStepNotification?.({ message: ex.message || String(ex), step });
            } finally {
                setValidationPending(false);
            }
        })();
    }, [appliedPartitioning, devices, partitioningPath, setStepNotification]);

    return { validationPending, validationReport };
};

const useStorageSpaceNotification = (status, freeSpace, requiredSpace) => {
    const { appliedPartitioning, storageScenarioId } = useContext(StorageContext);
    const { setStepNotification } = useContext(PageContext) ?? {};
    const { goToStepById } = useWizardContext();
    const fixupStepId = storageScenarioId === "mount-point-mapping"
        ? MOUNT_POINT_MAPPING_STEP_ID
        : STORAGE_METHOD_STEP_ID;

    useEffect(() => {
        if (!appliedPartitioning) {
            return;
        }
        if (status === StorageCompleteStatus.INSUFFICIENT) {
            const title = _("Not enough available free space");
            const message = cockpit.format(
                _("$0 is required, but only $1 is available."),
                cockpit.format_bytes(requiredSpace),
                cockpit.format_bytes(freeSpace)
            );
            const actionLinks = (
                <Button
                  id={`${REVIEW_STEP_ID}-change-partition-layout`}
                  variant="link"
                  isInline
                  onClick={() => {
                      cockpit.location.go([fixupStepId]);
                      goToStepById(fixupStepId);
                  }}
                >
                    {_("Change partition layout")}
                </Button>
            );
            setStepNotification?.({
                actionLinks,
                message,
                step: REVIEW_STEP_ID,
                title,
            });
        }
    }, [
        appliedPartitioning,
        fixupStepId,
        freeSpace,
        goToStepById,
        requiredSpace,
        setStepNotification,
        status,
    ]);
};

/**
 * Storage completion check for the Review step
 * (1) run ``applyStorage`` on the current partitioning path when nothing is applied yet
 * (2) check for sufficient free space
 */
export const usePageComplete = () => {
    const spaceState = useStorageComplete();

    const { validationPending } = useApplyStorageOnReview();
    useStorageSpaceNotification(spaceState.status, spaceState.freeSpace, spaceState.requiredSpace);

    let complete;
    if (spaceState.status === StorageCompleteStatus.PENDING) {
        complete = undefined;
    } else if (spaceState.status === StorageCompleteStatus.INSUFFICIENT) {
        complete = false;
    } else {
        complete = true;
    }

    return { complete, validationPending };
};