Skip to content

Commit

Permalink
webui: mount point assignment support
Browse files Browse the repository at this point in the history
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 <kkoukiou@redhat.com>
  • Loading branch information
jelly and KKoukiou committed Jun 20, 2023
1 parent e325b66 commit 0f55651
Show file tree
Hide file tree
Showing 13 changed files with 786 additions and 29 deletions.
103 changes: 103 additions & 0 deletions ui/webui/src/apis/storage.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,40 @@ export const createPartitioning = ({ method }) => {
);
};

/**
* @param {string} method A partitioning method
*
* @returns {Promise} Resolves the DBus path to the partitioning
*/
export const findPartitioning = ({ method }) => {
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 createPartitioning({ method });
} else {
const lastPartitiong = res.v[res.v.length - 1];
return getPartitioningMethod({ partitioning: lastPartitiong }).then(partitioningMethod => {
if (partitioningMethod !== method) {
return createPartitioning({ method });
} else {
return new Promise((resolve) => {
// Return as list to mimmick createPartitioning
resolve([res.v[res.v.length - 1]]);
});
}
});
}
});
};

/**
* @returns {Promise} Resolves all properties of DiskSelection interface
*/
Expand Down Expand Up @@ -220,6 +254,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
*/
Expand Down Expand Up @@ -390,3 +444,52 @@ export const setSelectedDisks = ({ drives }) => {
]
);
};

/*
* @param {string} partitioning DBus path to a partitioning
* @param {Array.<Object>} 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)
]
);
};

/**
* @returns {Promise} The request of automatic partitioning
*/
export const getManualPartitioningRequests = ({ partitioning }) => {
return (
new StorageClient().client.call(
partitioning,
"org.freedesktop.DBus.Properties",
"Get",
[
"org.fedoraproject.Anaconda.Modules.Storage.Partitioning.Manual",
"Requests",
]
)
.then(res => res[0].v)
);
};

/**
* @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",
[]
);
};
38 changes: 33 additions & 5 deletions ui/webui/src/components/AnacondaWizard.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,9 @@ import {
WizardContextConsumer,
} from "@patternfly/react-core";

import { InstallationDestination, applyDefaultStorage } from "./storage/InstallationDestination.jsx";
import { InstallationDestination, applyDefaultStorage, applyMountPointStorage } from "./storage/InstallationDestination.jsx";
import { StorageConfiguration, getScenario, getDefaultScenario } from "./storage/StorageConfiguration.jsx";
import { CustomMountPoint } from "./storage/CustomMountPoint.jsx";
import { DiskEncryption, StorageEncryptionState } from "./storage/DiskEncryption.jsx";
import { InstallationLanguage } from "./localization/InstallationLanguage.jsx";
import { InstallationProgress } from "./installation/InstallationProgress.jsx";
Expand Down Expand Up @@ -105,17 +106,24 @@ export const AnacondaWizard = ({ onAddErrorNotification, toggleContextHelp, hide
label: _("Installation destination"),
steps: [{
component: InstallationDestination,
data: { deviceData },
data: { deviceData, refreshDeviceData },
id: "storage-devices",
label: _("Storage devices")
}, {
component: StorageConfiguration,
id: "storage-configuration",
label: _("Storage configuration")
}, {
component: CustomMountPoint,
data: { deviceData },
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"
}]
},
{
Expand All @@ -134,7 +142,9 @@ export const AnacondaWizard = ({ onAddErrorNotification, toggleContextHelp, hide
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);
Expand All @@ -158,7 +168,7 @@ export const AnacondaWizard = ({ onAddErrorNotification, toggleContextHelp, hide
};

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,
Expand Down Expand Up @@ -280,6 +290,24 @@ const Footer = ({
});
} else if (activeStep.id === "installation-review") {
setNextWaitsConfirmation(true);
} else if (activeStep.id === "custom-mountpoint") {
setIsInProgress(true);

applyMountPointStorage({
onFail: ex => {
console.error(ex);
setIsInProgress(false);
setStepNotification({ step: activeStep.id, ...ex });
},
onSuccess: () => {
onNext();

// Reset the state after the onNext call. Otherwise,
// React will try to render the current step again.
setIsInProgress(false);
setStepNotification();
},
});
} else {
onNext();
}
Expand Down
64 changes: 52 additions & 12 deletions ui/webui/src/components/review/ReviewConfiguration.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
DataListToggle,
DataListItemRow, DataListItemCells,
DataListCell,
DataListContent,
DescriptionList, DescriptionListGroup,
DescriptionListTerm, DescriptionListDescription,
ExpandableSection,
Expand All @@ -38,7 +39,9 @@ import {
getSelectedDisks,
getDeviceData,
getAppliedPartitioning,
getManualPartitioningRequests,
getPartitioningRequest,
getPartitioningMethod,
} from "../../apis/storage.js";

import {
Expand All @@ -47,6 +50,9 @@ import {
import { AnacondaPage } from "../AnacondaPage.jsx";

import { getScenario } from "../storage/StorageConfiguration.jsx";
import { CheckCircleIcon } from "@patternfly/react-icons";

import { ListingTable } from "cockpit-components-table.jsx";

import "./ReviewConfiguration.scss";

Expand All @@ -70,15 +76,30 @@ export const ReviewDescriptionList = ({ children }) => {
);
};

const DeviceRow = ({ name, data }) => {
const DeviceRow = ({ name, data, requests }) => {
const [isExpanded, setIsExpanded] = useState(false);

const renderRow = row => {
const iconColumn = row.reformat.v ? <CheckCircleIcon /> : null;
return {
props: { key: row["device-spec"].v },
columns: [
{ title: row["device-spec"].v },
{ title: row["format-type"].v },
{ title: row["mount-point"].v },
{ title: iconColumn },
]
};
};

const partitionRows = requests?.filter(req => req["device-spec"].v.includes(name)).map(renderRow) || [];

return (
<DataListItem id={`data-list-${name}`} isExpanded={isExpanded} key={name}>
<DataListItemRow>
<DataListToggle
buttonProps={{ isDisabled: true }}
onClick={() => setIsExpanded(!isExpanded)}
buttonProps={{ isDisabled: requests === null }}
onClick={() => requests !== null ? setIsExpanded(!isExpanded) : {}}
isExpanded={isExpanded}
id={name + "-expander"}
/>
Expand All @@ -96,6 +117,15 @@ const DeviceRow = ({ name, data }) => {
]}
/>
</DataListItemRow>
<DataListContent isHidden={!isExpanded}>
<ListingTable
id="partitions-table"
aria-label={_("Disk partitions")}
emptyCaption={_("No partitions found")}
variant="compact"
columns={[_("Partition"), _("Format type"), _("Mount point"), _("Reformat")]}
rows={partitionRows} />
</DataListContent>
</DataListItem>
);
};
Expand All @@ -105,6 +135,7 @@ export const ReviewConfiguration = ({ idPrefix, storageScenarioId }) => {
const [selectedDisks, setSelectedDisks] = useState();
const [systemLanguage, setSystemLanguage] = useState();
const [encrypt, setEncrypt] = useState();
const [requests, setRequests] = useState(null);
const [showLanguageSection, setShowLanguageSection] = useState(true);
const [showInstallationDestSection, setShowInstallationDestSection] = useState(true);

Expand All @@ -124,8 +155,14 @@ export const ReviewConfiguration = ({ idPrefix, storageScenarioId }) => {
};
const initializeEncrypt = async () => {
const partitioning = await getAppliedPartitioning().catch(console.error);
const request = await getPartitioningRequest({ partitioning }).catch(console.error);
setEncrypt(request.encrypted.v);
const method = await getPartitioningMethod({ partitioning }).catch(console.error);
if (method === "AUTOMATIC") {
const request = await getPartitioningRequest({ partitioning }).catch(console.error);
setEncrypt(request.encrypted.v);
} else {
const requests = await getManualPartitioningRequests({ partitioning });
setRequests(requests);
}
};
initializeLanguage();
initializeDisks();
Expand Down Expand Up @@ -181,18 +218,21 @@ export const ReviewConfiguration = ({ idPrefix, storageScenarioId }) => {
<DescriptionListDescription className="description-list-description" id={idPrefix + "-target-system-mode"}>
{getScenario(storageScenarioId).label}
</DescriptionListDescription>
<DescriptionListTerm className="description-list-term">
{_("Disk Encryption")}
</DescriptionListTerm>
<DescriptionListDescription className="description-list-description" id={idPrefix + "-target-system-encrypt"}>
{encrypt ? _("Enabled") : _("Disabled")}
</DescriptionListDescription>
{storageScenarioId !== "custom-mount-point" &&
<>
<DescriptionListTerm className="description-list-term">
{_("Disk Encryption")}
</DescriptionListTerm>
<DescriptionListDescription className="description-list-description" id={idPrefix + "-target-system-encrypt"}>
{encrypt ? _("Enabled") : _("Disabled")}
</DescriptionListDescription>
</>}
</DescriptionListGroup>
</ReviewDescriptionList>
<Title className="storage-devices-configuration-title" headingLevel="h4">{_("Storage devices and configurations")}</Title>
<DataList isCompact>
{Object.keys(deviceData).map(deviceName =>
<DeviceRow key={deviceName} name={deviceName} data={deviceData[deviceName]} />
<DeviceRow key={deviceName} name={deviceName} data={deviceData[deviceName]} requests={requests} />
)}
</DataList>
</ExpandableSection>
Expand Down
Loading

0 comments on commit 0f55651

Please sign in to comment.