* 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";
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 };
};