diff --git a/ui/webui/src/actions/storage-actions.js b/ui/webui/src/actions/storage-actions.js index df5b9cd7b216..20bb0916c88b 100644 --- a/ui/webui/src/actions/storage-actions.js +++ b/ui/webui/src/actions/storage-actions.js @@ -18,12 +18,14 @@ import cockpit from "cockpit"; import { + gatherRequests, getAllDiskSelection, getDeviceData, getDevices, getDiskFreeSpace, getDiskTotalSpace, getFormatData, + getPartitioningMethod, getUsableDisks, } from "../apis/storage.js"; @@ -82,3 +84,29 @@ export const getDiskSelectionAction = () => { }); }; }; + +export const getPartitioningDataAction = ({ requests, partitioning, idx, updateOnly }) => { + return async function fetchUserThunk (dispatch) { + const props = { path: partitioning }; + const convertRequests = reqs => reqs.map(request => Object.entries(request).reduce((acc, [key, value]) => ({ ...acc, [key]: value.v }), {})); + + if (!updateOnly) { + props.method = await getPartitioningMethod({ partitioning }); + if (props.method === "MANUAL") { + const reqs = await gatherRequests({ partitioning }); + + props.requests = convertRequests(reqs[0]); + } + if (idx) { + props.idx = idx; + } + } else { + props.requests = convertRequests(requests); + } + + return dispatch({ + type: "GET_PARTITIONING_DATA", + payload: { path: partitioning, partitioningData: props } + }); + }; +}; diff --git a/ui/webui/src/apis/storage.js b/ui/webui/src/apis/storage.js index 29adafd04c95..ed9ffce3a638 100644 --- a/ui/webui/src/apis/storage.js +++ b/ui/webui/src/apis/storage.js @@ -16,7 +16,10 @@ */ import cockpit from "cockpit"; -import { getDiskSelectionAction } from "../actions/storage-actions.js"; +import { + getDiskSelectionAction, + getPartitioningDataAction +} from "../actions/storage-actions.js"; export class StorageClient { constructor (address) { @@ -62,6 +65,22 @@ export const createPartitioning = ({ method }) => { ); }; +/** + * @param {string} method A partitioning method + * + * @returns {Promise} Resolves the DBus path to the partitioning + */ +export const findPartitioning = ({ partitioning, method }) => { + if (partitioning?.method !== method) { + return createPartitioning({ method }); + } else { + return new Promise((resolve) => { + // Return as list to mimmick createPartitioning + resolve([partitioning[partitioning.length - 1]]); + }); + } +}; + /** * @returns {Promise} Resolves all properties of DiskSelection interface */ @@ -222,6 +241,26 @@ export const getPartitioningRequest = ({ partitioning }) => { ); }; +/** + * @param {string} partitioning DBus path to a partitioning + * + * @returns {Promise} The partitioning method + */ +export const getPartitioningMethod = ({ partitioning }) => { + return ( + new StorageClient().client.call( + partitioning, + "org.freedesktop.DBus.Properties", + "Get", + [ + "org.fedoraproject.Anaconda.Modules.Storage.Partitioning", + "PartitioningMethod", + ] + ) + .then(res => res[0].v) + ); +}; + /** * @returns {Promise} The applied partitioning */ @@ -401,6 +440,37 @@ export const setSelectedDisks = ({ drives }) => { ); }; +/* + * @param {string} partitioning DBus path to a partitioning + * @param {Array.} requests An array of request objects + */ +export const setManualPartitioningRequests = ({ partitioning, requests }) => { + return new StorageClient().client.call( + partitioning, + "org.freedesktop.DBus.Properties", + "Set", + [ + "org.fedoraproject.Anaconda.Modules.Storage.Partitioning.Manual", + "Requests", + cockpit.variant("aa{sv}", requests) + ] + ); +}; + +/** + * @param {string} partitioning DBus path to a partitioning + * + * @returns {Promise} The gathered requests for manual partitioning + */ +export const gatherRequests = ({ partitioning }) => { + return new StorageClient().client.call( + partitioning, + "org.fedoraproject.Anaconda.Modules.Storage.Partitioning.Manual", + "GatherRequests", + [] + ); +}; + export const startEventMonitorStorage = ({ dispatch }) => { return new StorageClient().client.subscribe( { }, @@ -409,10 +479,35 @@ export const startEventMonitorStorage = ({ dispatch }) => { case "PropertiesChanged": if (args[0] === "org.fedoraproject.Anaconda.Modules.Storage.DiskSelection") { dispatch(getDiskSelectionAction()); + } else if (args[0] === "org.fedoraproject.Anaconda.Modules.Storage.Partitioning.Manual" && Object.hasOwn(args[1], "Requests")) { + dispatch(getPartitioningDataAction({ requests: args[1].Requests.v, partitioning: path, updateOnly: true })); + } else if (args[0] === "org.fedoraproject.Anaconda.Modules.Storage" && Object.hasOwn(args[1], "CreatedPartitioning")) { + const idx = args[1].CreatedPartitioning.v.length - 1; + + dispatch(getPartitioningDataAction({ partitioning: args[1].CreatedPartitioning.v[idx], idx })); + } else { + console.debug(`Unhandled signal on ${path}: ${iface}.${signal} ${JSON.stringify(args)}`); } break; default: - console.debug(`Unhandled signal on ${path}: ${iface}.${signal}`); + console.debug(`Unhandled signal on ${path}: ${iface}.${signal} ${JSON.stringify(args)}`); } }); }; + +export const initDataStorage = ({ dispatch }) => { + return new StorageClient().client.call( + "/org/fedoraproject/Anaconda/Modules/Storage", + "org.freedesktop.DBus.Properties", + "Get", + [ + "org.fedoraproject.Anaconda.Modules.Storage", + "CreatedPartitioning", + ] + ) + .then(([res]) => { + if (res.v.length !== 0) { + return res.v.forEach((path, idx) => dispatch(getPartitioningDataAction({ partitioning: path, idx }))); + } + }); +}; diff --git a/ui/webui/src/components/AnacondaWizard.jsx b/ui/webui/src/components/AnacondaWizard.jsx index 71f747e5e8be..84f48d042342 100644 --- a/ui/webui/src/components/AnacondaWizard.jsx +++ b/ui/webui/src/components/AnacondaWizard.jsx @@ -15,7 +15,7 @@ * along with This program; If not, see . */ import cockpit from "cockpit"; -import React, { useState } from "react"; +import React, { useState, useMemo } from "react"; import { ActionList, @@ -34,6 +34,7 @@ import { import { InstallationDestination, applyDefaultStorage } from "./storage/InstallationDestination.jsx"; import { StorageConfiguration, getScenario, getDefaultScenario } from "./storage/StorageConfiguration.jsx"; +import { CustomMountPoint, applyMountPointStorage } from "./storage/CustomMountPoint.jsx"; import { DiskEncryption, StorageEncryptionState } from "./storage/DiskEncryption.jsx"; import { InstallationLanguage } from "./localization/InstallationLanguage.jsx"; import { InstallationProgress } from "./installation/InstallationProgress.jsx"; @@ -53,6 +54,12 @@ export const AnacondaWizard = ({ dispatch, storageData, localizationData, onAddE const [storageEncryption, setStorageEncryption] = useState(new StorageEncryptionState()); const [showPassphraseScreen, setShowPassphraseScreen] = useState(false); const [storageScenarioId, setStorageScenarioId] = useState(window.sessionStorage.getItem("storage-scenario-id") || getDefaultScenario().id); + const lastPartitioning = useMemo(() => { + const lastPartitioningKey = Object.keys(storageData.partitioning).find(path => storageData.partitioning[path].idx === Object.keys(storageData.partitioning).length - 1); + + return storageData.partitioning?.[lastPartitioningKey]; + }, [storageData.partitioning]); + console.info({ storageData, lastPartitioning }); // On live media rebooting the system will actually shut it off const isBootIso = conf["Installation System"].type === "BOOT_ISO"; @@ -75,18 +82,26 @@ export const AnacondaWizard = ({ dispatch, storageData, localizationData, onAddE label: _("Storage devices") }, { component: StorageConfiguration, - data: { selectedDisks: storageData.diskSelection.selectedDisks }, + data: { deviceData: storageData.devices, diskSelection: storageData.diskSelection }, id: "storage-configuration", label: _("Storage configuration") + }, { + component: CustomMountPoint, + data: { deviceData: storageData.devices, partitioningData: lastPartitioning, dispatch }, + id: "custom-mountpoint", + label: _("Custom mount point"), + isHidden: storageScenarioId !== "custom-mount-point" + }, { component: DiskEncryption, id: "disk-encryption", - label: _("Disk encryption") + label: _("Disk encryption"), + isHidden: storageScenarioId === "custom-mount-point" }] }, { component: ReviewConfiguration, - data: { deviceData: storageData.devices, diskSelection: storageData.diskSelection }, + data: { deviceData: storageData.devices, diskSelection: storageData.diskSelection, requests: lastPartitioning ? lastPartitioning.requests : null }, id: "installation-review", label: _("Review and install"), }, @@ -101,7 +116,9 @@ export const AnacondaWizard = ({ dispatch, storageData, localizationData, onAddE for (const step of steps) { if (step.steps) { for (const childStep of step.steps) { - stepIds.push(childStep.id); + if (childStep?.isHidden !== true) { + stepIds.push(childStep.id); + } } } else { stepIds.push(step.id); @@ -126,7 +143,7 @@ export const AnacondaWizard = ({ dispatch, storageData, localizationData, onAddE }; const createSteps = (stepsOrder) => { - const steps = stepsOrder.map((s, idx) => { + const steps = stepsOrder.filter(s => !s.isHidden).map(s => { let step = ({ id: s.id, name: s.label, @@ -181,6 +198,7 @@ export const AnacondaWizard = ({ dispatch, storageData, localizationData, onAddE id="installation-wizard" footer={