From 0766591e3ff03c3a379d9d5875ddcb9800410040 Mon Sep 17 00:00:00 2001 From: Jelle van der Waa Date: Thu, 11 May 2023 12:55:05 +0200 Subject: [PATCH] webui: mount point assignment support Support a new storage configuration method where a user first has to manually partition and create file systems using the tools on the installation image. When selecting the custom mount point option the user can now select which partition to map to mount point either from a well known list of Mount points or a manually created one. Co-authored-by: Katerina Koukiou --- ui/webui/src/actions/storage-actions.js | 19 ++ ui/webui/src/apis/storage.js | 93 ++++++- ui/webui/src/components/AnacondaWizard.jsx | 44 ++- ui/webui/src/components/app.jsx | 3 +- .../components/review/ReviewConfiguration.jsx | 66 ++++- .../components/storage/CustomMountPoint.jsx | 253 +++++++++++++++++ .../components/storage/CustomMountPoint.scss | 3 + .../storage/HelpAutopartOptions.jsx | 9 + .../storage/InstallationDestination.jsx | 2 +- .../storage/StorageConfiguration.jsx | 38 ++- ui/webui/src/reducer.js | 13 + ui/webui/test/check-storage | 262 ++++++++++++++++++ ui/webui/test/helpers/installer.py | 5 +- ui/webui/test/helpers/review.py | 9 + ui/webui/test/helpers/storage.py | 9 +- 15 files changed, 794 insertions(+), 34 deletions(-) create mode 100644 ui/webui/src/components/storage/CustomMountPoint.jsx create mode 100644 ui/webui/src/components/storage/CustomMountPoint.scss diff --git a/ui/webui/src/actions/storage-actions.js b/ui/webui/src/actions/storage-actions.js index df5b9cd7b216..ab460c84a68d 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,20 @@ export const getDiskSelectionAction = () => { }); }; }; + +export const getPartitioningDataAction = ({ requests, partitioning, updateOnly }) => { + return async function fetchUserThunk (dispatch) { + const reqs = updateOnly ? [requests] : await gatherRequests({ partitioning }); + const method = updateOnly ? null : await getPartitioningMethod({ partitioning }); + const props = { path: partitioning }; + + if (!updateOnly) { + props.method = method; + } + + return dispatch({ + type: "GET_PARTITIONING_DATA", + payload: { ...props, requests: reqs?.[0].map(request => Object.entries(request).reduce((acc, [key, value]) => ({ ...acc, [key]: value.v }), {})) } + }); + }; +}; diff --git a/ui/webui/src/apis/storage.js b/ui/webui/src/apis/storage.js index 29adafd04c95..979d0cb0f71f 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,6 +479,10 @@ 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")) { + dispatch(getPartitioningDataAction({ partitioning: args[1].CreatedPartitioning.v[args[1].CreatedPartitioning.v.length - 1] })); } break; default: @@ -416,3 +490,20 @@ export const startEventMonitorStorage = ({ dispatch }) => { } }); }; + +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 Promise.all(res.v.map(path => dispatch(getPartitioningDataAction({ partitioning: path })))); + } + }); +}; diff --git a/ui/webui/src/components/AnacondaWizard.jsx b/ui/webui/src/components/AnacondaWizard.jsx index 71f747e5e8be..4afd976ef4a0 100644 --- a/ui/webui/src/components/AnacondaWizard.jsx +++ b/ui/webui/src/components/AnacondaWizard.jsx @@ -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,8 @@ 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 currentPartitioningIndex = storageData.partitioning.length - 1; + console.info({ storageData }); // On live media rebooting the system will actually shut it off const isBootIso = conf["Installation System"].type === "BOOT_ISO"; @@ -75,18 +78,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: currentPartitioningIndex !== -1 ? storageData.partitioning[currentPartitioningIndex] : null, 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: currentPartitioningIndex !== -1 ? storageData.partitioning[currentPartitioningIndex].requests : null }, id: "installation-review", label: _("Review and install"), }, @@ -101,7 +112,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 +139,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 +194,7 @@ export const AnacondaWizard = ({ dispatch, storageData, localizationData, onAddE id="installation-wizard" footer={