diff --git a/ui/webui/src/actions/localization-actions.js b/ui/webui/src/actions/localization-actions.js
new file mode 100644
index 00000000000..a7ec46e36ba
--- /dev/null
+++ b/ui/webui/src/actions/localization-actions.js
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2023 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with This program; If not, see .
+ */
+
+import {
+ getCommonLocales,
+ getLanguages,
+ getLanguageData,
+ getLocales,
+ getLocaleData,
+} from "../apis/localization.js";
+
+export const getLanguagesAction = () => {
+ return async function fetchUserThunk (dispatch) {
+ const languageIds = await getLanguages();
+
+ dispatch(getCommonLocalesAction());
+ return languageIds.map(language => dispatch(getLanguageDataAction({ language })));
+ };
+};
+
+export const getLanguageDataAction = ({ language }) => {
+ return async function fetchUserThunk (dispatch) {
+ const localeIds = await getLocales({ lang: language });
+ const languageData = await getLanguageData({ lang: language });
+ const locales = await Promise.all(localeIds.map(async locale => await getLocaleData({ locale })));
+
+ return dispatch({
+ type: "GET_LANGUAGE_DATA",
+ payload: { languageData: { [language]: { languageData, locales } } }
+ });
+ };
+};
+
+export const getCommonLocalesAction = () => {
+ return async function fetchUserThunk (dispatch) {
+ const commonLocales = await getCommonLocales();
+
+ return dispatch({
+ type: "GET_COMMON_LOCALES",
+ payload: { commonLocales }
+ });
+ };
+};
diff --git a/ui/webui/src/actions/storage-actions.js b/ui/webui/src/actions/storage-actions.js
new file mode 100644
index 00000000000..df5b9cd7b21
--- /dev/null
+++ b/ui/webui/src/actions/storage-actions.js
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2023 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with This program; If not, see .
+ */
+
+import cockpit from "cockpit";
+
+import {
+ getAllDiskSelection,
+ getDeviceData,
+ getDevices,
+ getDiskFreeSpace,
+ getDiskTotalSpace,
+ getFormatData,
+ getUsableDisks,
+} from "../apis/storage.js";
+
+export const getDevicesAction = () => {
+ return async function fetchUserThunk (dispatch) {
+ const devices = await getDevices();
+ return devices[0].map(device => dispatch(getDeviceDataAction({ device })));
+ };
+};
+
+export const getDeviceDataAction = ({ device }) => {
+ return async function fetchUserThunk (dispatch) {
+ let devData = {};
+ const deviceData = await getDeviceData({ disk: device })
+ .then(res => {
+ devData = res[0];
+ return getDiskFreeSpace({ diskNames: [device] });
+ })
+ .then(free => {
+ // Since the getDeviceData returns an object with variants as values,
+ // extend it with variants to keep the format consistent
+ devData.free = cockpit.variant(String, free[0]);
+ return getDiskTotalSpace({ diskNames: [device] });
+ })
+ .then(total => {
+ devData.total = cockpit.variant(String, total[0]);
+ return getFormatData({ diskName: device });
+ })
+ .then(formatData => {
+ devData.formatData = formatData[0];
+ return ({ [device]: devData });
+ })
+ .catch(console.error);
+
+ return dispatch({
+ type: "GET_DEVICE_DATA",
+ payload: { deviceData }
+ });
+ };
+};
+
+export const getDiskSelectionAction = () => {
+ return async function fetchUserThunk (dispatch) {
+ const usableDisks = await getUsableDisks();
+ const diskSelection = await getAllDiskSelection();
+
+ return dispatch({
+ type: "GET_DISK_SELECTION",
+ payload: {
+ diskSelection: {
+ ignoredDisks: diskSelection[0].IgnoredDisks.v,
+ selectedDisks: diskSelection[0].SelectedDisks.v,
+ usableDisks: usableDisks[0],
+ }
+ },
+ });
+ };
+};
diff --git a/ui/webui/src/apis/storage.js b/ui/webui/src/apis/storage.js
index e5d4a86bcdd..29adafd04c9 100644
--- a/ui/webui/src/apis/storage.js
+++ b/ui/webui/src/apis/storage.js
@@ -16,6 +16,8 @@
*/
import cockpit from "cockpit";
+import { getDiskSelectionAction } from "../actions/storage-actions.js";
+
export class StorageClient {
constructor (address) {
if (StorageClient.instance) {
@@ -284,13 +286,21 @@ export const resetPartitioning = () => {
* @returns {Promise} Resolves a DBus path to a task
*/
export const runStorageTask = ({ task, onSuccess, onFail }) => {
+ // FIXME: This is a workaround for 'Succeeded' signal being emited twice
+ let succeededEmitted = false;
const taskProxy = new StorageClient().client.proxy(
"org.fedoraproject.Anaconda.Task",
task
);
const addEventListeners = () => {
taskProxy.addEventListener("Stopped", () => taskProxy.Finish().catch(onFail));
- taskProxy.addEventListener("Succeeded", onSuccess);
+ taskProxy.addEventListener("Succeeded", () => {
+ if (succeededEmitted) {
+ return;
+ }
+ succeededEmitted = true;
+ onSuccess();
+ });
};
taskProxy.wait(() => {
addEventListeners();
@@ -390,3 +400,19 @@ export const setSelectedDisks = ({ drives }) => {
]
);
};
+
+export const startEventMonitorStorage = ({ dispatch }) => {
+ return new StorageClient().client.subscribe(
+ { },
+ (path, iface, signal, args) => {
+ switch (signal) {
+ case "PropertiesChanged":
+ if (args[0] === "org.fedoraproject.Anaconda.Modules.Storage.DiskSelection") {
+ dispatch(getDiskSelectionAction());
+ }
+ break;
+ default:
+ console.debug(`Unhandled signal on ${path}: ${iface}.${signal}`);
+ }
+ });
+};
diff --git a/ui/webui/src/components/AnacondaWizard.jsx b/ui/webui/src/components/AnacondaWizard.jsx
index 288e2abf714..32c78228ae3 100644
--- a/ui/webui/src/components/AnacondaWizard.jsx
+++ b/ui/webui/src/components/AnacondaWizard.jsx
@@ -40,11 +40,13 @@ import { InstallationProgress } from "./installation/InstallationProgress.jsx";
import { ReviewConfiguration, ReviewConfigurationConfirmModal } from "./review/ReviewConfiguration.jsx";
import { exitGui } from "../helpers/exit.js";
import { usePageLocation } from "hooks";
-import { resetPartitioning } from "../apis/storage.js";
+import {
+ resetPartitioning
+} from "../apis/storage.js";
const _ = cockpit.gettext;
-export const AnacondaWizard = ({ onAddErrorNotification, toggleContextHelp, hideContextHelp, title, conf }) => {
+export const AnacondaWizard = ({ dispatch, storageData, localizationData, onAddErrorNotification, toggleContextHelp, hideContextHelp, title, conf }) => {
const [isFormValid, setIsFormValid] = useState(true);
const [stepNotification, setStepNotification] = useState();
const [isInProgress, setIsInProgress] = useState(false);
@@ -55,6 +57,7 @@ export const AnacondaWizard = ({ onAddErrorNotification, toggleContextHelp, hide
const stepsOrder = [
{
component: InstallationLanguage,
+ data: { dispatch, languages: localizationData.languages, commonLocales: localizationData.commonLocales },
id: "installation-language",
label: _("Welcome"),
},
@@ -64,6 +67,7 @@ export const AnacondaWizard = ({ onAddErrorNotification, toggleContextHelp, hide
label: _("Installation destination"),
steps: [{
component: InstallationDestination,
+ data: { deviceData: storageData.devices, diskSelection: storageData.diskSelection, dispatch },
id: "storage-devices",
label: _("Storage devices")
}, {
@@ -78,6 +82,7 @@ export const AnacondaWizard = ({ onAddErrorNotification, toggleContextHelp, hide
},
{
component: ReviewConfiguration,
+ data: { deviceData: storageData.devices, diskSelection: storageData.diskSelection },
id: "installation-review",
label: _("Review and install"),
},
@@ -144,6 +149,7 @@ export const AnacondaWizard = ({ onAddErrorNotification, toggleContextHelp, hide
window.sessionStorage.setItem("storage-scenario-id", scenarioId);
setStorageScenarioId(scenarioId);
}}
+ {...s.data}
/>
),
});
diff --git a/ui/webui/src/components/app.jsx b/ui/webui/src/components/app.jsx
index 2f5f841511a..b97ba6d1314 100644
--- a/ui/webui/src/components/app.jsx
+++ b/ui/webui/src/components/app.jsx
@@ -32,11 +32,12 @@ import { HelpDrawer } from "./HelpDrawer.jsx";
import { BossClient } from "../apis/boss.js";
import { LocalizationClient } from "../apis/localization.js";
-import { StorageClient } from "../apis/storage.js";
+import { StorageClient, startEventMonitorStorage } from "../apis/storage.js";
import { PayloadsClient } from "../apis/payloads";
import { readBuildstamp, getIsFinal } from "../helpers/betanag.js";
import { readConf } from "../helpers/conf.js";
+import { useReducerWithThunk, reducer, initialState } from "../reducer.js";
export const Application = () => {
const [address, setAddress] = useState();
@@ -47,6 +48,7 @@ export const Application = () => {
const [isHelpExpanded, setIsHelpExpanded] = useState(false);
const [helpContent, setHelpContent] = useState("");
const [prettyName, setPrettyName] = useState("");
+ const [state, dispatch] = useReducerWithThunk(reducer, initialState);
useEffect(() => {
cockpit.file("/run/anaconda/bus.address").watch(address => {
@@ -59,6 +61,8 @@ export const Application = () => {
clients.forEach(c => c.init());
setAddress(address);
+
+ startEventMonitorStorage({ dispatch });
});
readConf().then(
@@ -72,7 +76,7 @@ export const Application = () => {
);
readOsRelease().then(osRelease => setPrettyName(osRelease.PRETTY_NAME));
- }, []);
+ }, [dispatch]);
const onAddNotification = (notificationProps) => {
setNotifications({
@@ -139,6 +143,9 @@ export const Application = () => {
toggleContextHelp={toggleContextHelp}
hideContextHelp={() => setIsHelpExpanded(false)}
title={title}
+ storageData={state.storage}
+ localizationData={state.localization}
+ dispatch={dispatch}
conf={conf}
/>
diff --git a/ui/webui/src/components/localization/InstallationLanguage.jsx b/ui/webui/src/components/localization/InstallationLanguage.jsx
index 149ea5456cf..cee2d4518d3 100644
--- a/ui/webui/src/components/localization/InstallationLanguage.jsx
+++ b/ui/webui/src/components/localization/InstallationLanguage.jsx
@@ -40,12 +40,7 @@ import { setLocale } from "../../apis/boss.js";
import {
getLanguage,
- getLanguages,
- getLanguageData,
- getLocales,
- getLocaleData,
setLanguage,
- getCommonLocales,
} from "../../apis/localization.js";
import {
@@ -54,6 +49,7 @@ import {
setLangCookie
} from "../../helpers/language.js";
import { AnacondaPage } from "../AnacondaPage.jsx";
+import { getLanguagesAction } from "../../actions/localization-actions.js";
import "./InstallationLanguage.scss";
@@ -69,9 +65,6 @@ class LanguageSelector extends React.Component {
constructor (props) {
super(props);
this.state = {
- languages: [],
- locales: [],
- commonLocales: [],
search: "",
lang: "",
};
@@ -93,28 +86,14 @@ class LanguageSelector extends React.Component {
} catch (e) {
this.props.onAddErrorNotification(e);
}
-
- const languageIds = await getLanguages();
- // Create the languages state object
- this.setState({ languages: await Promise.all(languageIds.map(async lang => await getLanguageData({ lang }))) });
- // Create the locales state object
- const localeIds = await Promise.all(languageIds.map(async lang => await getLocales({ lang })));
- const locales = await Promise.all(localeIds.map(async localeId => {
- return await Promise.all(localeId.map(async locale => await getLocaleData({ locale })));
- }));
- this.setState({ locales }, this.updateNativeName);
-
- // Create a list of common locales.
- this.setState({ commonLocales: await getCommonLocales() });
}
- async updateNativeName (localeData) {
- localeData = localeData || await getLocaleData({ locale: this.state.lang });
- this.props.getNativeName(getLocaleNativeName(localeData));
+ async updateNativeName (localeItem) {
+ this.props.setNativeName(getLocaleNativeName(localeItem));
}
renderOptions (filter) {
- const { languages, locales } = this.state;
+ const { languages } = this.props;
const idPrefix = this.props.idPrefix;
const filterLow = filter.toLowerCase();
@@ -124,10 +103,11 @@ class LanguageSelector extends React.Component {
let foundSelected = false;
// Returns a locale with a given code.
const findLocaleWithId = (localeCode) => {
- for (const locale of this.state.locales) {
- for (const subLocale of locale) {
- if (getLocaleId(subLocale) === localeCode) {
- return subLocale;
+ for (const languageId in languages) {
+ const languageItem = languages[languageId];
+ for (const locale of languageItem.locales) {
+ if (getLocaleId(locale) === localeCode) {
+ return locale;
}
}
}
@@ -180,7 +160,7 @@ class LanguageSelector extends React.Component {
key="group-common-languages"
>
{
- this.state.commonLocales
+ this.props.commonLocales
.map(findLocaleWithId)
.filter(locale => locale)
.map(locale => createMenuItem(locale, "option-common-"))
@@ -192,20 +172,20 @@ class LanguageSelector extends React.Component {
}
// List alphabetically.
- for (const langLocales of locales) {
- const currentLang = languages.find(lang => getLanguageId(lang) === getLanguageId(langLocales[0]));
-
- const label = cockpit.format("$0 ($1)", getLanguageNativeName(currentLang), getLanguageEnglishName(currentLang));
+ const languagesIds = Object.keys(languages).sort();
+ for (const languageId of languagesIds) {
+ const languageItem = languages[languageId];
+ const label = cockpit.format("$0 ($1)", getLanguageNativeName(languageItem.languageData), getLanguageEnglishName(languageItem.languageData));
if (!filter || label.toLowerCase().indexOf(filterLow) !== -1) {
filtered.push(
- {langLocales.map(locale => createMenuItem(locale, "option-alpha-"))}
+ {languageItem.locales.map(locale => createMenuItem(locale, "option-alpha-"))}
);
}
@@ -227,11 +207,12 @@ class LanguageSelector extends React.Component {
}
render () {
- const { languages, locales, lang, commonLocales } = this.state;
+ const { lang } = this.state;
+ const { languages, commonLocales } = this.props;
+
const handleOnSelect = (_event, item) => {
- const { locales } = this.state;
- for (const locale of locales) {
- for (const localeItem of locale) {
+ for (const languageItem in languages) {
+ for (const localeItem of languages[languageItem].locales) {
if (getLocaleId(localeItem) === item) {
setLangCookie({ cockpitLang: convertToCockpitLang({ lang: getLocaleId(localeItem) }) });
setLanguage({ lang: getLocaleId(localeItem) })
@@ -261,7 +242,7 @@ class LanguageSelector extends React.Component {
}
};
- const isLoading = languages.length === 0 || languages.length !== locales.length || commonLocales.length === 0;
+ const isLoading = languages.length === 0 || commonLocales.length === 0;
if (isLoading) {
return ;
@@ -315,14 +296,15 @@ class LanguageSelector extends React.Component {
}
LanguageSelector.contextType = AddressContext;
-export const InstallationLanguage = ({ idPrefix, setIsFormValid, onAddErrorNotification }) => {
+export const InstallationLanguage = ({ idPrefix, languages, commonLocales, dispatch, setIsFormValid, onAddErrorNotification }) => {
const [nativeName, setNativeName] = React.useState(false);
const { setLanguage } = React.useContext(LanguageContext);
const [distributionName, setDistributionName] = useState("");
useEffect(() => {
readOsRelease().then(osRelease => setDistributionName(osRelease.NAME));
- }, []);
+ dispatch(getLanguagesAction());
+ }, [dispatch]);
return (
@@ -347,9 +329,11 @@ export const InstallationLanguage = ({ idPrefix, setIsFormValid, onAddErrorNotif
diff --git a/ui/webui/src/components/review/ReviewConfiguration.jsx b/ui/webui/src/components/review/ReviewConfiguration.jsx
index 012da15209b..02b1a4e5883 100644
--- a/ui/webui/src/components/review/ReviewConfiguration.jsx
+++ b/ui/webui/src/components/review/ReviewConfiguration.jsx
@@ -35,8 +35,6 @@ import {
} from "@patternfly/react-core";
import {
- getSelectedDisks,
- getDeviceData,
getAppliedPartitioning,
getPartitioningRequest,
} from "../../apis/storage.js";
@@ -100,9 +98,7 @@ const DeviceRow = ({ name, data }) => {
);
};
-export const ReviewConfiguration = ({ idPrefix, storageScenarioId }) => {
- const [deviceData, setDeviceData] = useState({});
- const [selectedDisks, setSelectedDisks] = useState();
+export const ReviewConfiguration = ({ deviceData, diskSelection, idPrefix, storageScenarioId }) => {
const [systemLanguage, setSystemLanguage] = useState();
const [encrypt, setEncrypt] = useState();
const [showLanguageSection, setShowLanguageSection] = useState(true);
@@ -114,26 +110,17 @@ export const ReviewConfiguration = ({ idPrefix, storageScenarioId }) => {
const langData = await getLanguageData({ lang }).catch(console.error);
setSystemLanguage(langData["native-name"].v);
};
- const initializeDisks = async () => {
- const selDisks = await getSelectedDisks().catch(console.error);
- setSelectedDisks(selDisks);
- for (const disk of selDisks) {
- const devData = await getDeviceData({ disk }).catch(console.error);
- setDeviceData(d => ({ ...d, [disk]: devData[0] }));
- }
- };
const initializeEncrypt = async () => {
const partitioning = await getAppliedPartitioning().catch(console.error);
const request = await getPartitioningRequest({ partitioning }).catch(console.error);
setEncrypt(request.encrypted.v);
};
initializeLanguage();
- initializeDisks();
initializeEncrypt();
}, []);
// handle case of disks not (yet) loaded
- if (!selectedDisks || !systemLanguage) {
+ if (!systemLanguage) {
return null;
}
@@ -191,7 +178,7 @@ export const ReviewConfiguration = ({ idPrefix, storageScenarioId }) => {
{_("Storage devices and configurations")}
diff --git a/ui/webui/src/components/storage/InstallationDestination.jsx b/ui/webui/src/components/storage/InstallationDestination.jsx
index d1c8dda0679..096381a9ec5 100644
--- a/ui/webui/src/components/storage/InstallationDestination.jsx
+++ b/ui/webui/src/components/storage/InstallationDestination.jsx
@@ -15,7 +15,7 @@
* along with This program; If not, see .
*/
import cockpit from "cockpit";
-import React, { useEffect, useState } from "react";
+import React, { useEffect, useRef, useState } from "react";
import {
Alert,
@@ -53,14 +53,7 @@ import { helpStorageOptions } from "./HelpStorageOptions.jsx";
import {
applyPartitioning,
createPartitioning,
- getAllDiskSelection,
- getDevices,
- getDeviceData,
- getDiskFreeSpace,
- getDiskTotalSpace,
- getFormatData,
getRequiredDeviceSize,
- getUsableDisks,
partitioningConfigureWithTask,
resetPartitioning,
runStorageTask,
@@ -75,6 +68,7 @@ import {
import {
getRequiredSpace,
} from "../../apis/payloads";
+import { getDevicesAction, getDiskSelectionAction } from "../../actions/storage-actions.js";
import {
sleep,
@@ -109,14 +103,10 @@ const selectDefaultDisks = ({ ignoredDisks, selectedDisks, usableDisks }) => {
}
};
-const setSelectionForAllDisks = ({ disks, value }) => {
- return (Object.keys(disks).reduce((acc, cur) => ({ ...acc, [cur]: value }), {}));
-};
-
const containEqualDisks = (disks1, disks2) => {
- const disks1Str = Object.keys(disks1).sort()
+ const disks1Str = disks1.sort()
.join();
- const disks2Str = Object.keys(disks2).sort()
+ const disks2Str = disks2.sort()
.join();
return disks1Str === disks2Str;
};
@@ -195,81 +185,54 @@ const DropdownBulkSelect = ({
);
};
-const LocalStandardDisks = ({ idPrefix, setIsFormValid, onAddErrorNotification }) => {
- const [deviceData, setDeviceData] = useState({});
- const [disks, setDisks] = useState({});
- const [refreshCnt, setRefreshCnt] = useState(0);
+const LocalStandardDisks = ({ deviceData, diskSelection, dispatch, idPrefix, setIsFormValid, onAddErrorNotification }) => {
const [isRescanningDisks, setIsRescanningDisks] = useState(false);
- const [lastRescanDisks, setLastRescanDisks] = useState({});
const [equalDisksNotify, setEqualDisksNotify] = useState(false);
+ const refUsableDisks = useRef();
useEffect(() => {
- let usableDisks;
- let devices;
- getDevices()
- .then(ret => {
- devices = ret[0];
- return getUsableDisks();
- })
- .then(res => {
- usableDisks = res[0];
- return getAllDiskSelection();
- })
- .then(props => {
- // Select default disks for the partitioning
- const defaultDisks = selectDefaultDisks({
- ignoredDisks: props[0].IgnoredDisks.v,
- selectedDisks: props[0].SelectedDisks.v,
- usableDisks,
- });
- setDisks(usableDisks.reduce((acc, cur) => ({ ...acc, [cur]: defaultDisks.includes(cur) }), {}));
-
- // Show disks data
- devices.forEach(disk => {
- let deviceData = {};
- const diskNames = [disk];
-
- getDeviceData({ disk })
- .then(res => {
- deviceData = res[0];
- return getDiskFreeSpace({ diskNames });
- }, console.error)
- .then(free => {
- // Since the getDeviceData returns an object with variants as values,
- // extend it with variants to keep the format consistent
- deviceData.free = cockpit.variant(String, free[0]);
- return getDiskTotalSpace({ diskNames });
- }, console.error)
- .then(total => {
- deviceData.total = cockpit.variant(String, total[0]);
- return getFormatData({ diskName: disk });
- }, console.error)
- .then(formatData => {
- deviceData.formatData = formatData[0];
- setDeviceData(d => ({ ...d, [disk]: deviceData }));
- }, console.error);
- });
- }, console.error);
- }, [refreshCnt]);
+ if (isRescanningDisks) {
+ refUsableDisks.current = diskSelection.usableDisks;
+ setEqualDisksNotify(true);
+ }
+ }, [isRescanningDisks, diskSelection.usableDisks]);
+
+ useEffect(() => {
+ // Select default disks for the partitioning on component mount
+ if (refUsableDisks.current !== undefined) {
+ return;
+ }
+
+ const defaultDisks = selectDefaultDisks({
+ ignoredDisks: diskSelection.ignoredDisks,
+ selectedDisks: diskSelection.selectedDisks,
+ usableDisks: diskSelection.usableDisks,
+ });
+
+ if (!containEqualDisks(diskSelection.selectedDisks, defaultDisks)) {
+ refUsableDisks.current = diskSelection.usableDisks;
+ setSelectedDisks({ drives: defaultDisks });
+ }
+ }, [diskSelection]);
+
+ useEffect(() => {
+ dispatch(getDevicesAction());
+ dispatch(getDiskSelectionAction());
+ }, [dispatch]);
- const totalDisksCnt = Object.keys(disks).length;
- const selectedDisksCnt = Object.keys(disks).filter(disk => !!disks[disk]).length;
+ const totalDisksCnt = diskSelection.usableDisks.length;
+ const selectedDisksCnt = diskSelection.selectedDisks.length;
// When the selected disks change in the UI, update in the backend as well
useEffect(() => {
// Do not update on the inital value, wait for initialization by the other effect
- if (Object.keys(disks).length === 0) {
+ if (refUsableDisks.current === undefined) {
return;
}
setIsFormValid(selectedDisksCnt > 0);
+ }, [selectedDisksCnt, setIsFormValid]);
- const selected = Object.keys(disks).filter(disk => disks[disk]);
- console.log("Updating storage backend with selected disks:", selected.join(","));
-
- setSelectedDisks({ drives: selected }).catch(onAddErrorNotification);
- }, [disks, onAddErrorNotification, selectedDisksCnt, setIsFormValid]);
-
- const loading = Object.keys(disks).some(disk => !deviceData[disk]);
+ const loading = !deviceData || diskSelection.usableDisks.some(disk => !deviceData[disk]);
if (loading) {
return ;
}
@@ -297,17 +260,19 @@ const LocalStandardDisks = ({ idPrefix, setIsFormValid, onAddErrorNotification }
variant="secondary"
onClick={() => {
setIsRescanningDisks(true);
- setLastRescanDisks({ ...disks });
- setDisks(setSelectionForAllDisks({ disks, value: false }));
+ setSelectedDisks({ drives: [] });
scanDevicesWithTask()
.then(res => {
runStorageTask({
task: res[0],
- onSuccess: () => resetPartitioning().then(() => setRefreshCnt(refreshCnt + 1), onAddErrorNotification),
+ onSuccess: () => resetPartitioning().then(() => {
+ dispatch(getDevicesAction());
+ dispatch(getDiskSelectionAction());
+ }, onAddErrorNotification),
onFail: onAddErrorNotification
});
})
- .finally(() => { setIsRescanningDisks(false); setEqualDisksNotify(true) });
+ .finally(() => setIsRescanningDisks(false));
}}
>
);
- const localDisksRows = Object.keys(disks).map(disk => {
+ const localDisksRows = diskSelection.usableDisks.map(disk => {
const hasPartitions = deviceData[disk]?.children?.v.length && deviceData[disk]?.children?.v.every(partition => deviceData[partition]);
return ({
- selected: !!disks[disk],
+ selected: !!diskSelection.selectedDisks.includes(disk),
hasPadding: true,
props: { key: disk, id: disk },
columns: [
@@ -395,7 +360,7 @@ const LocalStandardDisks = ({ idPrefix, setIsFormValid, onAddErrorNotification }
]
);
- const rescanningDisksRows = Object.keys(disks).map(disk => (
+ const rescanningDisksRows = diskSelection.usableDisks.map(disk => (
{
columns: rescanningDisksRow
}
@@ -403,9 +368,15 @@ const LocalStandardDisks = ({ idPrefix, setIsFormValid, onAddErrorNotification }
const dropdownBulkSelect = (
setDisks(setSelectionForAllDisks({ disks, value: true }))}
- onSelectNone={() => setDisks(setSelectionForAllDisks({ disks, value: false }))}
- onChange={(checked) => setDisks(setSelectionForAllDisks({ disks, value: checked }))}
+ onSelectAll={() => setSelectedDisks({ drives: diskSelection.usableDisks })}
+ onSelectNone={() => setSelectedDisks({ drives: [] })}
+ onChange={(checked) => {
+ if (checked) {
+ setSelectedDisks({ drives: diskSelection.usableDisks });
+ } else {
+ setSelectedDisks({ drives: [] });
+ }
+ }}
selectedCnt={selectedDisksCnt}
totalCnt={totalDisksCnt}
isDisabled={isRescanningDisks}
@@ -440,7 +411,15 @@ const LocalStandardDisks = ({ idPrefix, setIsFormValid, onAddErrorNotification }
}
onSelect={
!isRescanningDisks
- ? (_, isSelected, diskId) => setDisks({ ...disks, [Object.keys(disks)[diskId]]: isSelected })
+ ? (_, isSelected, diskId) => {
+ const newDisk = diskSelection.usableDisks[diskId];
+
+ if (isSelected) {
+ setSelectedDisks({ drives: [...diskSelection.selectedDisks, newDisk] });
+ } else {
+ setSelectedDisks({ drives: diskSelection.selectedDisks.filter(disk => disk !== newDisk) });
+ }
+ }
: () => {}
}
rows={
@@ -451,6 +430,8 @@ const LocalStandardDisks = ({ idPrefix, setIsFormValid, onAddErrorNotification }
/>
);
+ const equalDisks = refUsableDisks.current && containEqualDisks(refUsableDisks.current, diskSelection.usableDisks);
+
return (