From 9e8cc77e753378c08cc178720ed9c76b4993f33f Mon Sep 17 00:00:00 2001 From: Josef Reidinger Date: Wed, 21 Aug 2024 16:11:26 +0200 Subject: [PATCH 01/54] start with new web API for DASD --- web/src/api/dasd.ts | 30 +++++++++++++++++ web/src/queries/dasd.ts | 71 +++++++++++++++++++++++++++++++++++++++++ web/src/types/dasd.ts | 34 ++++++++++++++++++++ 3 files changed, 135 insertions(+) create mode 100644 web/src/api/dasd.ts create mode 100644 web/src/queries/dasd.ts create mode 100644 web/src/types/dasd.ts diff --git a/web/src/api/dasd.ts b/web/src/api/dasd.ts new file mode 100644 index 0000000000..268c8213ca --- /dev/null +++ b/web/src/api/dasd.ts @@ -0,0 +1,30 @@ +/* + * Copyright (c) [2024] SUSE LLC + * + * All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as published + * by the Free Software Foundation. + * + * 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 General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, contact SUSE LLC. + * + * To contact SUSE LLC about this file by physical or electronic mail, you may + * find current contact information at www.suse.com. + */ + +import { del, get, patch, put } from "~/api/http"; +import { DASDDevice } from "~/types/dasd"; + +/** + * Returns the list of DASD devices + */ +const fetchDASDDevices = (): Promise => get("/api/storage/dasd/devices"); + +export { fetchDASDDevices }; \ No newline at end of file diff --git a/web/src/queries/dasd.ts b/web/src/queries/dasd.ts new file mode 100644 index 0000000000..7a09b68f2a --- /dev/null +++ b/web/src/queries/dasd.ts @@ -0,0 +1,71 @@ +/* + * Copyright (c) [2024] SUSE LLC + * + * All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as published + * by the Free Software Foundation. + * + * 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 General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, contact SUSE LLC. + * + * To contact SUSE LLC about this file by physical or electronic mail, you may + * find current contact information at www.suse.com. + */ + +import { useQueryClient, useSuspenseQuery } from "@tanstack/react-query"; +import { _ } from "~/i18n"; +import { + fetchDASDDevices, +} from "~/api/dasd"; +import { useInstallerClient } from "~/context/installer"; +import React from "react"; + +/** + * Returns a query for retrieving the dasd devices + */ +const DASDDevicesQuery = () => ({ + queryKey: ["dasd", "devices"], + queryFn: fetchDASDDevices, +}); + +/** + * Hook that returns DASD devices. + */ +const useDASDDevices = () => { + const { data: devices } = useSuspenseQuery(DASDDevicesQuery()); + return devices; +}; + +/** + * Listens for DASD devices changes. + */ +const useDASDDevicesChanges = () => { + const client = useInstallerClient(); + const queryClient = useQueryClient(); + + React.useEffect(() => { + if (!client) return; + + return client.ws().onEvent((event) => { + // TODO: for simplicity we now just invalidate query instead of manually adding, removing or changing devices + if ( + event.type === "DASDDeviceAdded" || + event.type === "DASDDeviceRemoved" || + event.type === "DASDDeviceChanged" + ) { + queryClient.invalidateQueries({ queryKey: ["dasd", "devices"] }); + } + }); + }); + const { data: devices } = useSuspenseQuery(DASDDevicesQuery()); + return devices; +}; + +export { useDASDDevices, useDASDDevicesChanges }; \ No newline at end of file diff --git a/web/src/types/dasd.ts b/web/src/types/dasd.ts new file mode 100644 index 0000000000..84558aa3e8 --- /dev/null +++ b/web/src/types/dasd.ts @@ -0,0 +1,34 @@ +/* + * Copyright (c) [2024] SUSE LLC + * + * All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as published + * by the Free Software Foundation. + * + * 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 General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, contact SUSE LLC. + * + * To contact SUSE LLC about this file by physical or electronic mail, you may + * find current contact information at www.suse.com. + */ + +type DASDDevice = { + id: string; + enabled: boolean; + deviceName: string; + formatted: boolean; + diag: boolean; + status: string; // TODO: sync with rust when it switch to enum + deviceType: string; // TODO: sync with rust when it switch to enum + accessType: string; // TODO: sync with rust when it switch to enum + partitionInfo: string; + }; + + export type { DASDDevice }; \ No newline at end of file From daf6124d73d1e07c8e37cd014805dd16b3c81c6c Mon Sep 17 00:00:00 2001 From: Knut Anderssen Date: Thu, 22 Aug 2024 10:15:36 +0100 Subject: [PATCH 02/54] [WIP] Try to add a supported check to DASD API --- rust/agama-lib/src/storage/client/dasd.rs | 7 +++++++ rust/agama-server/src/storage/web/dasd.rs | 14 ++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/rust/agama-lib/src/storage/client/dasd.rs b/rust/agama-lib/src/storage/client/dasd.rs index 17674d64ad..081ed5fa2f 100644 --- a/rust/agama-lib/src/storage/client/dasd.rs +++ b/rust/agama-lib/src/storage/client/dasd.rs @@ -32,6 +32,13 @@ impl<'a> DASDClient<'a> { }) } + pub async fn supported(&self) -> Result<(), ServiceError> { + let introspect = self.manager_proxy.introspect().await?; + let manager = self.object_manager_proxy.get_managed_objects().await?; + dbg!(manager); + Ok(()) + } + pub async fn probe(&self) -> Result<(), ServiceError> { Ok(self.manager_proxy.probe().await?) } diff --git a/rust/agama-server/src/storage/web/dasd.rs b/rust/agama-server/src/storage/web/dasd.rs index f8c3848c69..a25d6aca4a 100644 --- a/rust/agama-server/src/storage/web/dasd.rs +++ b/rust/agama-server/src/storage/web/dasd.rs @@ -49,6 +49,7 @@ pub async fn dasd_service(dbus: &zbus::Connection) -> Result, Servi let client = DASDClient::new(dbus.clone()).await?; let state = DASDState { client }; let router = Router::new() + .route("/supported", get(supported)) .route("/devices", get(devices)) .route("/probe", post(probe)) .route("/format", post(format)) @@ -59,6 +60,19 @@ pub async fn dasd_service(dbus: &zbus::Connection) -> Result, Servi Ok(router) } +/// Returns whether DASD technology is supported or not +#[utoipa::path( + get, + path="/supported", + context_path="/api/storage/dasd", + responses( + (status = OK, description = "Returns whether DASD technology is supported") + ) +)] +async fn supported(State(state): State>) -> Result, Error> { + Ok(Json(state.client.supported().await?)) +} + /// Returns the list of known DASD devices. #[utoipa::path( get, From e05a7cc514f129ef426e95b54b3fa744bef94e9d Mon Sep 17 00:00:00 2001 From: Josef Reidinger Date: Thu, 22 Aug 2024 11:42:18 +0200 Subject: [PATCH 03/54] try to have working dasd#supported --- rust/agama-lib/src/storage/client/dasd.rs | 7 +++---- rust/agama-server/src/storage/web/dasd.rs | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/rust/agama-lib/src/storage/client/dasd.rs b/rust/agama-lib/src/storage/client/dasd.rs index 081ed5fa2f..3e4c6f13b9 100644 --- a/rust/agama-lib/src/storage/client/dasd.rs +++ b/rust/agama-lib/src/storage/client/dasd.rs @@ -32,11 +32,10 @@ impl<'a> DASDClient<'a> { }) } - pub async fn supported(&self) -> Result<(), ServiceError> { + pub async fn supported(&self) -> Result { let introspect = self.manager_proxy.introspect().await?; - let manager = self.object_manager_proxy.get_managed_objects().await?; - dbg!(manager); - Ok(()) + // simply check if introspection contain given interface + Ok(introspect.contains("org.opensuse.Agama.Storage1.DASD.Manager")) } pub async fn probe(&self) -> Result<(), ServiceError> { diff --git a/rust/agama-server/src/storage/web/dasd.rs b/rust/agama-server/src/storage/web/dasd.rs index a25d6aca4a..7acc11f4bc 100644 --- a/rust/agama-server/src/storage/web/dasd.rs +++ b/rust/agama-server/src/storage/web/dasd.rs @@ -69,7 +69,7 @@ pub async fn dasd_service(dbus: &zbus::Connection) -> Result, Servi (status = OK, description = "Returns whether DASD technology is supported") ) )] -async fn supported(State(state): State>) -> Result, Error> { +async fn supported(State(state): State>) -> Result, Error> { Ok(Json(state.client.supported().await?)) } From 5e246d156e8096679efad2e43fb5ddbb05d2cad0 Mon Sep 17 00:00:00 2001 From: Knut Anderssen Date: Thu, 22 Aug 2024 11:14:58 +0100 Subject: [PATCH 04/54] Replaced DASD is supported check --- web/src/api/dasd.ts | 4 +++- web/src/components/storage/DevicesTechMenu.jsx | 3 ++- .../components/storage/DevicesTechMenu.test.jsx | 16 ++++++---------- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/web/src/api/dasd.ts b/web/src/api/dasd.ts index 268c8213ca..93b6a63dc5 100644 --- a/web/src/api/dasd.ts +++ b/web/src/api/dasd.ts @@ -27,4 +27,6 @@ import { DASDDevice } from "~/types/dasd"; */ const fetchDASDDevices = (): Promise => get("/api/storage/dasd/devices"); -export { fetchDASDDevices }; \ No newline at end of file +const DASDSupported = (): Promise => get("/api/storage/dasd/supported"); + +export { fetchDASDDevices, DASDSupported }; diff --git a/web/src/components/storage/DevicesTechMenu.jsx b/web/src/components/storage/DevicesTechMenu.jsx index dc0a47a659..4b000e07fb 100644 --- a/web/src/components/storage/DevicesTechMenu.jsx +++ b/web/src/components/storage/DevicesTechMenu.jsx @@ -26,6 +26,7 @@ import { useHref } from "react-router-dom"; import { MenuToggle, Select, SelectList, SelectOption } from "@patternfly/react-core"; import { _ } from "~/i18n"; import { useInstallerClient } from "~/context/installer"; +import { DASDSupported } from "~/api/dasd"; /** * Internal component for building the link to Storage/DASD page @@ -85,7 +86,7 @@ export default function DevicesTechMenu({ label }) { const { storage: client } = useInstallerClient(); useEffect(() => { - client.dasd.isSupported().then(setShowDasdLink); + DASDSupported().then(setShowDasdLink); client.zfcp.isSupported().then(setShowZFCPLink); }, [client.dasd, client.zfcp]); diff --git a/web/src/components/storage/DevicesTechMenu.test.jsx b/web/src/components/storage/DevicesTechMenu.test.jsx index 6cd7917a1b..b46f4f2be8 100644 --- a/web/src/components/storage/DevicesTechMenu.test.jsx +++ b/web/src/components/storage/DevicesTechMenu.test.jsx @@ -24,14 +24,10 @@ import { screen } from "@testing-library/react"; import { installerRender } from "~/test-utils"; import { createClient } from "~/client"; import DevicesTechMenu from "./DevicesTechMenu"; +import { DASDSupported } from "~/api/dasd"; jest.mock("~/client"); - -const isDASDSupportedFn = jest.fn(); - -const dasd = { - isSupported: isDASDSupportedFn, -}; +jest.mock("~/api/dasd"); const isZFCPSupportedFn = jest.fn(); @@ -40,12 +36,12 @@ const zfcp = { }; beforeEach(() => { - isDASDSupportedFn.mockResolvedValue(false); + DASDSupported.mockResolvedValue(false); isZFCPSupportedFn.mockResolvedValue(false); createClient.mockImplementation(() => { return { - storage: { dasd, zfcp }, + storage: { zfcp }, }; }); }); @@ -59,7 +55,7 @@ it("contains an entry for configuring iSCSI", async () => { }); it("contains an entry for configuring DASD when is supported", async () => { - isDASDSupportedFn.mockResolvedValue(true); + DASDSupported.mockResolvedValue(true); const { user } = installerRender(); const toggler = screen.getByRole("button"); await user.click(toggler); @@ -68,7 +64,7 @@ it("contains an entry for configuring DASD when is supported", async () => { }); it("does not contain an entry for configuring DASD when is NOT supported", async () => { - isDASDSupportedFn.mockResolvedValue(false); + DASDSupported.mockResolvedValue(false); const { user } = installerRender(); const toggler = screen.getByRole("button"); await user.click(toggler); From c772472e1c867d8a422352b96daa9f4516bc7352 Mon Sep 17 00:00:00 2001 From: Knut Anderssen Date: Thu, 22 Aug 2024 11:40:06 +0100 Subject: [PATCH 05/54] [WIP] Start adapting DASDPage --- web/src/components/storage/DASDPage.jsx | 61 ++++++++----------------- web/src/routes/storage.tsx | 8 +++- 2 files changed, 25 insertions(+), 44 deletions(-) diff --git a/web/src/components/storage/DASDPage.jsx b/web/src/components/storage/DASDPage.jsx index 2a11d067ed..448d909a60 100644 --- a/web/src/components/storage/DASDPage.jsx +++ b/web/src/components/storage/DASDPage.jsx @@ -25,6 +25,8 @@ import DASDFormatProgress from "~/components/storage/DASDFormatProgress"; import { _ } from "~/i18n"; import { useCancellablePromise } from "~/utils"; import { useInstallerClient } from "~/context/installer"; +import { Page } from "~/components/core"; +import { useDASDDevices, useDASDDevicesChanges } from "~/queries/dasd"; const reducer = (state, action) => { const { type, payload } = action; @@ -128,18 +130,14 @@ const initialState = { }; export default function DASDPage() { + useDASDDevicesChanges(); + const devices = useDASDDevices(); + initialState.devices = devices; const { storage: client } = useInstallerClient(); const { cancellablePromise } = useCancellablePromise(); const [state, dispatch] = useReducer(reducer, initialState); useEffect(() => { - const loadDevices = async () => { - dispatch({ type: "START_LOADING" }); - const devices = await cancellablePromise(client.dasd.getDevices()); - dispatch({ type: "SET_DEVICES", payload: { devices } }); - dispatch({ type: "STOP_LOADING" }); - }; - const loadJobs = async () => { const jobs = await cancellablePromise(client.dasd.getJobs()); if (jobs.length > 0) { @@ -147,44 +145,21 @@ export default function DASDPage() { } }; - loadDevices().catch(console.error); loadJobs().catch(console.error); - }, [client.dasd, cancellablePromise]); - - useEffect(() => { - const subscriptions = []; - - const subscribe = async () => { - const action = (type, device) => dispatch({ type, payload: { device } }); - - subscriptions.push( - await client.dasd.deviceEventListener("added", (d) => action("ADD_DEVICE", d)), - await client.dasd.deviceEventListener("removed", (d) => action("REMOVE_DEVICE", d)), - await client.dasd.deviceEventListener("changed", (d) => action("UPDATE_DEVICE", d)), - ); - - await client.dasd.onJobAdded((data) => - dispatch({ type: "START_FORMAT_JOB", payload: { data } }), - ); - await client.dasd.onJobChanged((data) => - dispatch({ type: "UPDATE_FORMAT_JOB", payload: { data } }), - ); - }; - - const unsubscribe = () => { - subscriptions.forEach((fn) => fn()); - }; - - subscribe(); - return unsubscribe; - }, [client.dasd]); + }, [client.dasd, cancellablePromise, devices]); return ( - <> - - {state.formatJob.running && ( - - )} - + + +

{_("DASD")}

+
+ + + + {state.formatJob.running && ( + + )} + +
); } diff --git a/web/src/routes/storage.tsx b/web/src/routes/storage.tsx index 4c7fcb5c02..10b32ba217 100644 --- a/web/src/routes/storage.tsx +++ b/web/src/routes/storage.tsx @@ -23,7 +23,7 @@ import React from "react"; import BootSelection from "~/components/storage/BootSelection"; import DeviceSelection from "~/components/storage/DeviceSelection"; import SpacePolicySelection from "~/components/storage/SpacePolicySelection"; -import ISCSIPage from "~/components/storage/ISCSIPage"; +import { DASDPage, ISCSIPage } from "~/components/storage"; import ProposalPage from "~/components/storage/ProposalPage"; import { Route } from "~/types/routes"; import { N_ } from "~/i18n"; @@ -34,6 +34,7 @@ const PATHS = { bootingPartition: "/storage/booting-partition", spacePolicy: "/storage/space-policy", iscsi: "/storage/iscsi", + dasd: "/storage/dasd" }; const routes = (): Route => ({ @@ -61,6 +62,11 @@ const routes = (): Route => ({ element: , handle: { name: N_("iSCSI") }, }, + { + path: PATHS.dasd, + element: , + handle: { name: N_("DASD") }, + }, ], }); From 7e4b34dae1f53bedd889d0fc29ec4e2ff6ddae98 Mon Sep 17 00:00:00 2001 From: Knut Anderssen Date: Thu, 22 Aug 2024 12:28:11 +0100 Subject: [PATCH 06/54] Temporal fix for showing dasds --- web/src/components/storage/DASDPage.jsx | 16 ++- web/src/components/storage/DASDTable.jsx | 128 ++++++++++++----------- 2 files changed, 77 insertions(+), 67 deletions(-) diff --git a/web/src/components/storage/DASDPage.jsx b/web/src/components/storage/DASDPage.jsx index 448d909a60..a3e7f7dc83 100644 --- a/web/src/components/storage/DASDPage.jsx +++ b/web/src/components/storage/DASDPage.jsx @@ -23,7 +23,7 @@ import React, { useEffect, useReducer } from "react"; import DASDTable from "~/components/storage/DASDTable"; import DASDFormatProgress from "~/components/storage/DASDFormatProgress"; import { _ } from "~/i18n"; -import { useCancellablePromise } from "~/utils"; +import { hex, useCancellablePromise } from "~/utils"; import { useInstallerClient } from "~/context/installer"; import { Page } from "~/components/core"; import { useDASDDevices, useDASDDevicesChanges } from "~/queries/dasd"; @@ -125,14 +125,22 @@ const initialState = { selectedDevices: [], minChannel: "", maxChannel: "", - isLoading: true, formatJob: {}, }; +// FIXME +const buildDevice = (device) => { + return { + ...device, + hexId: hex(device.id), + partitionInfo: device.partition_info + } +} + export default function DASDPage() { useDASDDevicesChanges(); const devices = useDASDDevices(); - initialState.devices = devices; + initialState.devices = devices.map(buildDevice); const { storage: client } = useInstallerClient(); const { cancellablePromise } = useCancellablePromise(); const [state, dispatch] = useReducer(reducer, initialState); @@ -146,7 +154,7 @@ export default function DASDPage() { }; loadJobs().catch(console.error); - }, [client.dasd, cancellablePromise, devices]); + }, [client.dasd, cancellablePromise]); return ( diff --git a/web/src/components/storage/DASDTable.jsx b/web/src/components/storage/DASDTable.jsx index 9937b1c4c9..4fb11119b5 100644 --- a/web/src/components/storage/DASDTable.jsx +++ b/web/src/components/storage/DASDTable.jsx @@ -22,6 +22,7 @@ import React, { useState } from "react"; import { Button, + CardBody, Divider, Dropdown, DropdownItem, @@ -37,7 +38,7 @@ import { } from "@patternfly/react-core"; import { Table, Thead, Tr, Th, Tbody, Td } from "@patternfly/react-table"; import { Icon } from "~/components/layout"; -import { SectionSkeleton } from "~/components/core"; +import { CardField } from "~/components/core"; import { _ } from "~/i18n"; import { hex } from "~/utils"; import { sort } from "fast-sort"; @@ -206,9 +207,8 @@ export default function DASDTable({ state, dispatch }) { }; const Content = () => { - if (state.isLoading) return ; - return ( + @@ -249,68 +249,70 @@ export default function DASDTable({ state, dispatch }) { }; return ( - <> - - - - - - - {state.minChannel !== "" && ( - - - - )} - - - - - - {state.maxChannel !== "" && ( - - - - )} - - + + + + + + + + + {state.minChannel !== "" && ( + + + + )} + + + + + + {state.maxChannel !== "" && ( + + + + )} + + - + - - - - - - + + + + + + - - + + + ); } From d866bd81711f4412c5e58983096d3f3ecad001ff Mon Sep 17 00:00:00 2001 From: Josef Reidinger Date: Thu, 22 Aug 2024 16:23:39 +0200 Subject: [PATCH 07/54] adapt ids --- web/src/components/storage/DASDTable.jsx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/web/src/components/storage/DASDTable.jsx b/web/src/components/storage/DASDTable.jsx index 4fb11119b5..bb7d0b337a 100644 --- a/web/src/components/storage/DASDTable.jsx +++ b/web/src/components/storage/DASDTable.jsx @@ -67,16 +67,17 @@ const columnData = (device, column) => { }; const columns = [ - { id: "channelId", sortId: "hexId", label: _("Channel ID") }, + // TODO: fix keys case on rust side and then adapt + { id: "id", sortId: "hexId", label: _("Channel ID") }, { id: "status", label: _("Status") }, - { id: "name", label: _("Device") }, - { id: "type", label: _("Type") }, + { id: "device_name", label: _("Device") }, + { id: "device_type", label: _("Type") }, // TRANSLATORS: table header, the column contains "Yes"/"No" values // for the DIAG access mode (special disk access mode on IBM mainframes), // usually keep untranslated { id: "diag", label: _("DIAG") }, { id: "formatted", label: _("Formatted") }, - { id: "partitionInfo", label: _("Partition Info") }, + { id: "partition_info", label: _("Partition Info") }, ]; const Actions = ({ devices, isDisabled }) => { From 08edd003d4d68bed56d5fc3979835445e93ec8a0 Mon Sep 17 00:00:00 2001 From: Knut Anderssen Date: Thu, 22 Aug 2024 20:48:54 +0100 Subject: [PATCH 08/54] Start moving DASD pages to Typescript --- .../storage/{DASDPage.jsx => DASDPage.tsx} | 14 ++---- .../storage/{DASDTable.jsx => DASDTable.tsx} | 16 +++---- web/src/queries/dasd.ts | 45 ++++++++++--------- web/src/types/dasd.ts | 23 +++++----- web/src/utils.js | 4 ++ 5 files changed, 50 insertions(+), 52 deletions(-) rename web/src/components/storage/{DASDPage.jsx => DASDPage.tsx} (95%) rename web/src/components/storage/{DASDTable.jsx => DASDTable.tsx} (95%) diff --git a/web/src/components/storage/DASDPage.jsx b/web/src/components/storage/DASDPage.tsx similarity index 95% rename from web/src/components/storage/DASDPage.jsx rename to web/src/components/storage/DASDPage.tsx index a3e7f7dc83..dc7240b1c9 100644 --- a/web/src/components/storage/DASDPage.jsx +++ b/web/src/components/storage/DASDPage.tsx @@ -27,6 +27,7 @@ import { hex, useCancellablePromise } from "~/utils"; import { useInstallerClient } from "~/context/installer"; import { Page } from "~/components/core"; import { useDASDDevices, useDASDDevicesChanges } from "~/queries/dasd"; +import { DASDDevice } from "~/types/dasd"; const reducer = (state, action) => { const { type, payload } = action; @@ -128,19 +129,10 @@ const initialState = { formatJob: {}, }; -// FIXME -const buildDevice = (device) => { - return { - ...device, - hexId: hex(device.id), - partitionInfo: device.partition_info - } -} - export default function DASDPage() { useDASDDevicesChanges(); - const devices = useDASDDevices(); - initialState.devices = devices.map(buildDevice); + const devices: DASDDevice[] = useDASDDevices(); + initialState.devices = devices; const { storage: client } = useInstallerClient(); const { cancellablePromise } = useCancellablePromise(); const [state, dispatch] = useReducer(reducer, initialState); diff --git a/web/src/components/storage/DASDTable.jsx b/web/src/components/storage/DASDTable.tsx similarity index 95% rename from web/src/components/storage/DASDTable.jsx rename to web/src/components/storage/DASDTable.tsx index bb7d0b337a..ea7ff97467 100644 --- a/web/src/components/storage/DASDTable.jsx +++ b/web/src/components/storage/DASDTable.tsx @@ -43,10 +43,11 @@ import { _ } from "~/i18n"; import { hex } from "~/utils"; import { sort } from "fast-sort"; import { useInstallerClient } from "~/context/installer"; +import { DASDDevice } from "~/types/dasd"; // FIXME: please, note that this file still requiring refinements until reach a // reasonable stable version -const columnData = (device, column) => { +const columnData = (device: DASDDevice, column: { id: string, sortId: string, label: string }) => { let data = device[column.id]; switch (column.id) { @@ -67,17 +68,16 @@ const columnData = (device, column) => { }; const columns = [ - // TODO: fix keys case on rust side and then adapt { id: "id", sortId: "hexId", label: _("Channel ID") }, { id: "status", label: _("Status") }, - { id: "device_name", label: _("Device") }, - { id: "device_type", label: _("Type") }, + { id: "deviceName", label: _("Device") }, + { id: "deviceType", label: _("Type") }, // TRANSLATORS: table header, the column contains "Yes"/"No" values // for the DIAG access mode (special disk access mode on IBM mainframes), // usually keep untranslated { id: "diag", label: _("DIAG") }, { id: "formatted", label: _("Formatted") }, - { id: "partition_info", label: _("Partition Info") }, + { id: "partitionInfo", label: _("Partition Info") }, ]; const Actions = ({ devices, isDisabled }) => { @@ -146,8 +146,8 @@ const Actions = ({ devices, isDisabled }) => { ); }; -const filterDevices = (devices, from, to) => { - const allChannels = devices.map((d) => d.hexId); +const filterDevices = (devices: DASDDevice[], from: string, to: string): DASDDevice[] => { + const allChannels: number[] = devices.map((d) => d.hexId); const min = hex(from) || Math.min(...allChannels); const max = hex(to) || Math.max(...allChannels); @@ -175,7 +175,7 @@ export default function DASDTable({ state, dispatch }) { // Sorting // See https://github.com/snovakovic/fast-sort - const sortBy = sortingColumn.sortBy || sortingColumn.id; + const sortBy = sortingColumn.sortId || sortingColumn.id; const sortedDevices = sort(filteredDevices)[sortDirection]((d) => d[sortBy]); // FIXME: this can be improved and even extracted to be used with other tables. diff --git a/web/src/queries/dasd.ts b/web/src/queries/dasd.ts index 7a09b68f2a..7127bccc39 100644 --- a/web/src/queries/dasd.ts +++ b/web/src/queries/dasd.ts @@ -22,50 +22,51 @@ import { useQueryClient, useSuspenseQuery } from "@tanstack/react-query"; import { _ } from "~/i18n"; import { - fetchDASDDevices, + fetchDASDDevices, } from "~/api/dasd"; import { useInstallerClient } from "~/context/installer"; import React from "react"; +import { hex } from "~/utils"; /** * Returns a query for retrieving the dasd devices */ const DASDDevicesQuery = () => ({ - queryKey: ["dasd", "devices"], - queryFn: fetchDASDDevices, + queryKey: ["dasd", "devices"], + queryFn: fetchDASDDevices, }); /** * Hook that returns DASD devices. */ const useDASDDevices = () => { - const { data: devices } = useSuspenseQuery(DASDDevicesQuery()); - return devices; + const { data: devices } = useSuspenseQuery(DASDDevicesQuery()); + return devices.map((d) => ({ ...d, hexId: hex(d.id) })); }; /** * Listens for DASD devices changes. */ const useDASDDevicesChanges = () => { - const client = useInstallerClient(); - const queryClient = useQueryClient(); + const client = useInstallerClient(); + const queryClient = useQueryClient(); - React.useEffect(() => { - if (!client) return; + React.useEffect(() => { + if (!client) return; - return client.ws().onEvent((event) => { - // TODO: for simplicity we now just invalidate query instead of manually adding, removing or changing devices - if ( - event.type === "DASDDeviceAdded" || - event.type === "DASDDeviceRemoved" || - event.type === "DASDDeviceChanged" - ) { - queryClient.invalidateQueries({ queryKey: ["dasd", "devices"] }); - } - }); + return client.ws().onEvent((event) => { + // TODO: for simplicity we now just invalidate query instead of manually adding, removing or changing devices + if ( + event.type === "DASDDeviceAdded" || + event.type === "DASDDeviceRemoved" || + event.type === "DASDDeviceChanged" + ) { + queryClient.invalidateQueries({ queryKey: ["dasd", "devices"] }); + } }); - const { data: devices } = useSuspenseQuery(DASDDevicesQuery()); - return devices; + }); + const { data: devices } = useSuspenseQuery(DASDDevicesQuery()); + return devices; }; -export { useDASDDevices, useDASDDevicesChanges }; \ No newline at end of file +export { useDASDDevices, useDASDDevicesChanges }; diff --git a/web/src/types/dasd.ts b/web/src/types/dasd.ts index 84558aa3e8..f4bfbe3647 100644 --- a/web/src/types/dasd.ts +++ b/web/src/types/dasd.ts @@ -20,15 +20,16 @@ */ type DASDDevice = { - id: string; - enabled: boolean; - deviceName: string; - formatted: boolean; - diag: boolean; - status: string; // TODO: sync with rust when it switch to enum - deviceType: string; // TODO: sync with rust when it switch to enum - accessType: string; // TODO: sync with rust when it switch to enum - partitionInfo: string; - }; + id: string; + enabled: boolean; + deviceName: string; + formatted: boolean; + diag: boolean; + status: string; // TODO: sync with rust when it switch to enum + deviceType: string; // TODO: sync with rust when it switch to enum + accessType: string; // TODO: sync with rust when it switch to enum + partitionInfo: string; + hexId: number; +}; - export type { DASDDevice }; \ No newline at end of file +export type { DASDDevice }; diff --git a/web/src/utils.js b/web/src/utils.js index 019ddd69b8..a88cb27c6b 100644 --- a/web/src/utils.js +++ b/web/src/utils.js @@ -287,6 +287,10 @@ const useDebounce = (callback, delay) => { return debouncedCallback; }; +/** + * @param {string} + * @returns {number} + */ const hex = (value) => { const sanitizedValue = value.replaceAll(".", ""); return parseInt(sanitizedValue, 16); From e7962068fd7c59e56b39ea58da14fffdbe450a93 Mon Sep 17 00:00:00 2001 From: Josef Reidinger Date: Thu, 22 Aug 2024 22:16:17 +0200 Subject: [PATCH 09/54] add dasd probe --- web/src/api/dasd.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/web/src/api/dasd.ts b/web/src/api/dasd.ts index 93b6a63dc5..95ecc2cdd0 100644 --- a/web/src/api/dasd.ts +++ b/web/src/api/dasd.ts @@ -19,7 +19,7 @@ * find current contact information at www.suse.com. */ -import { del, get, patch, put } from "~/api/http"; +import { post, get } from "~/api/http"; import { DASDDevice } from "~/types/dasd"; /** @@ -29,4 +29,6 @@ const fetchDASDDevices = (): Promise => get("/api/storage/dasd/dev const DASDSupported = (): Promise => get("/api/storage/dasd/supported"); -export { fetchDASDDevices, DASDSupported }; +const probeDASD = () => post("/api/storage/dasd/probe"); + +export { fetchDASDDevices, DASDSupported, probeDASD }; From 2b40f73b31d60b331f9159687289841b524b4c7d Mon Sep 17 00:00:00 2001 From: Josef Reidinger Date: Thu, 22 Aug 2024 23:03:52 +0200 Subject: [PATCH 10/54] add more api and queries for dasd --- web/src/api/dasd.ts | 35 ++++++++++++++++++++++++++-- web/src/queries/dasd.ts | 51 +++++++++++++++++++++++++++++++++++++++-- 2 files changed, 82 insertions(+), 4 deletions(-) diff --git a/web/src/api/dasd.ts b/web/src/api/dasd.ts index 95ecc2cdd0..8edf53d3b2 100644 --- a/web/src/api/dasd.ts +++ b/web/src/api/dasd.ts @@ -19,7 +19,7 @@ * find current contact information at www.suse.com. */ -import { post, get } from "~/api/http"; +import { post, get, put } from "~/api/http"; import { DASDDevice } from "~/types/dasd"; /** @@ -27,8 +27,39 @@ import { DASDDevice } from "~/types/dasd"; */ const fetchDASDDevices = (): Promise => get("/api/storage/dasd/devices"); +/** + * Returns if DASD is supported at all + */ const DASDSupported = (): Promise => get("/api/storage/dasd/supported"); +/** + * probes DASD devices + */ const probeDASD = () => post("/api/storage/dasd/probe"); -export { fetchDASDDevices, DASDSupported, probeDASD }; +/** + * Enables given list of devices + * @param devicesIDs array of device ids + */ +const DASDEnable = (devicesIDs: string[]) => post("/api/storage/dasd/enable", devicesIDs); + +/** + * Enables given list of devices + * @param devicesIDs array of device ids + */ +const DASDDisable = (devicesIDs: string[]) => post("/api/storage/dasd/disable", devicesIDs); + +/** + * Enables giag on given list of devices + * @param devicesIDs array of device ids + */ +const diagEnable = (devicesIDs: string[]) => put("/api/storage/dasd/diag", { devices: devicesIDs, diag: true }); + +/** + * Disables diag on given list of devices + * @param devicesIDs array of device ids + */ +const diagDisable = (devicesIDs: string[]) => put("/api/storage/dasd/diag", { devices: devicesIDs, diag: false }); + + +export { fetchDASDDevices, DASDSupported, probeDASD, DASDEnable, DASDDisable, diagEnable, diagDisable }; diff --git a/web/src/queries/dasd.ts b/web/src/queries/dasd.ts index 7127bccc39..7c0181888b 100644 --- a/web/src/queries/dasd.ts +++ b/web/src/queries/dasd.ts @@ -19,9 +19,12 @@ * find current contact information at www.suse.com. */ -import { useQueryClient, useSuspenseQuery } from "@tanstack/react-query"; +import { useMutation, useQueryClient, useSuspenseQuery } from "@tanstack/react-query"; import { _ } from "~/i18n"; import { + DASDDisable, + DASDEnable, + diagEnable, fetchDASDDevices, } from "~/api/dasd"; import { useInstallerClient } from "~/context/installer"; @@ -69,4 +72,48 @@ const useDASDDevicesChanges = () => { return devices; }; -export { useDASDDevices, useDASDDevicesChanges }; +const useDASDEnableMutation = () => { + const queryClient = useQueryClient(); + const query = { + mutationFn: DASDEnable, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["dasd", "devices"] }); + }, + }; + return useMutation(query); + }; + + const useDASDDisableMutation = () => { + const queryClient = useQueryClient(); + const query = { + mutationFn: DASDDisable, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["dasd", "devices"] }); + }, + }; + return useMutation(query); + }; + + const useDiagEnableMutation = () => { + const queryClient = useQueryClient(); + const query = { + mutationFn: diagEnable, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["dasd", "devices"] }); + }, + }; + return useMutation(query); + }; + + const useDiagDisableMutation = () => { + const queryClient = useQueryClient(); + const query = { + mutationFn: diagEnable, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["dasd", "devices"] }); + }, + }; + return useMutation(query); + }; + +export { useDASDDevices, useDASDDevicesChanges, useDASDEnableMutation, useDASDDisableMutation, useDiagDisableMutation, useDiagEnableMutation }; From 455fa66c2446f91325e0770617595e764176f76a Mon Sep 17 00:00:00 2001 From: Josef Reidinger Date: Fri, 23 Aug 2024 07:30:33 +0200 Subject: [PATCH 11/54] add format api call --- web/src/api/dasd.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/web/src/api/dasd.ts b/web/src/api/dasd.ts index 8edf53d3b2..0c6eea6288 100644 --- a/web/src/api/dasd.ts +++ b/web/src/api/dasd.ts @@ -37,6 +37,12 @@ const DASDSupported = (): Promise => get("/api/storage/dasd/supported") */ const probeDASD = () => post("/api/storage/dasd/probe"); +/** + * Start format job for given list of devices + * @param devicesIDs array of device ids + */ +const DASDFormat = (devicesIDs: string[]) => post("/api/storage/dasd/format", devicesIDs); + /** * Enables given list of devices * @param devicesIDs array of device ids @@ -62,4 +68,4 @@ const diagEnable = (devicesIDs: string[]) => put("/api/storage/dasd/diag", { dev const diagDisable = (devicesIDs: string[]) => put("/api/storage/dasd/diag", { devices: devicesIDs, diag: false }); -export { fetchDASDDevices, DASDSupported, probeDASD, DASDEnable, DASDDisable, diagEnable, diagDisable }; +export { fetchDASDDevices, DASDSupported, DASDFormat, probeDASD, DASDEnable, DASDDisable, diagEnable, diagDisable }; From fa7ac905aef808f6306d711d8a4f82444545a267 Mon Sep 17 00:00:00 2001 From: Josef Reidinger Date: Fri, 23 Aug 2024 08:14:05 +0200 Subject: [PATCH 12/54] fix sort types --- web/src/components/storage/DASDTable.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/web/src/components/storage/DASDTable.tsx b/web/src/components/storage/DASDTable.tsx index ea7ff97467..e5ca7fde47 100644 --- a/web/src/components/storage/DASDTable.tsx +++ b/web/src/components/storage/DASDTable.tsx @@ -19,7 +19,7 @@ * find current contact information at www.suse.com. */ -import React, { useState } from "react"; +import React, { Dispatch, SetStateAction, useState } from "react"; import { Button, CardBody, @@ -47,7 +47,7 @@ import { DASDDevice } from "~/types/dasd"; // FIXME: please, note that this file still requiring refinements until reach a // reasonable stable version -const columnData = (device: DASDDevice, column: { id: string, sortId: string, label: string }) => { +const columnData = (device: DASDDevice, column: { id: string, sortId?: string, label: string }) => { let data = device[column.id]; switch (column.id) { @@ -156,7 +156,7 @@ const filterDevices = (devices: DASDDevice[], from: string, to: string): DASDDev export default function DASDTable({ state, dispatch }) { const [sortingColumn, setSortingColumn] = useState(columns[0]); - const [sortDirection, setSortDirection] = useState("asc"); + const [sortDirection, setSortDirection] : [ "asc" | "desc", Dispatch>] = useState("asc"); const sortColumnIndex = () => columns.findIndex((c) => c.id === sortingColumn.id); const filteredDevices = filterDevices(state.devices, state.minChannel, state.maxChannel); From b2d7f6cfc8bcf60415920be7d5826821771235fb Mon Sep 17 00:00:00 2001 From: Josef Reidinger Date: Fri, 23 Aug 2024 08:28:47 +0200 Subject: [PATCH 13/54] add backspace icon --- web/src/components/layout/Icon.jsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/web/src/components/layout/Icon.jsx b/web/src/components/layout/Icon.jsx index 47ad45b5ec..85dc96c6a3 100644 --- a/web/src/components/layout/Icon.jsx +++ b/web/src/components/layout/Icon.jsx @@ -26,6 +26,7 @@ import React from "react"; import AddAPhoto from "@icons/add_a_photo.svg?component"; import Apps from "@icons/apps.svg?component"; import Badge from "@icons/badge.svg?component"; +import Backspace from "@icons/backspace.svg?component"; import CheckCircle from "@icons/check_circle.svg?component"; import ChevronRight from "@icons/chevron_right.svg?component"; import CollapseAll from "@icons/collapse_all.svg?component"; @@ -94,6 +95,7 @@ const icons = { add_a_photo: AddAPhoto, apps: Apps, badge: Badge, + backspace: Backspace, check_circle: CheckCircle, chevron_right: ChevronRight, collapse_all: CollapseAll, From 6251f72b5e9b30707d30b065cd740f5885c76a8a Mon Sep 17 00:00:00 2001 From: Knut Anderssen Date: Fri, 23 Aug 2024 08:44:32 +0100 Subject: [PATCH 14/54] Move DASD actions to queries --- web/src/api/dasd.ts | 6 +++--- web/src/api/network.ts | 2 +- web/src/components/storage/DASDTable.tsx | 16 +++++++++------- web/src/routes/storage.tsx | 3 +++ 4 files changed, 16 insertions(+), 11 deletions(-) diff --git a/web/src/api/dasd.ts b/web/src/api/dasd.ts index 0c6eea6288..c042cbabc4 100644 --- a/web/src/api/dasd.ts +++ b/web/src/api/dasd.ts @@ -41,19 +41,19 @@ const probeDASD = () => post("/api/storage/dasd/probe"); * Start format job for given list of devices * @param devicesIDs array of device ids */ -const DASDFormat = (devicesIDs: string[]) => post("/api/storage/dasd/format", devicesIDs); +const DASDFormat = (devicesIDs: string[]) => post("/api/storage/dasd/format", { devices: devicesIDs }); /** * Enables given list of devices * @param devicesIDs array of device ids */ -const DASDEnable = (devicesIDs: string[]) => post("/api/storage/dasd/enable", devicesIDs); +const DASDEnable = (devicesIDs: string[]) => post("/api/storage/dasd/enable", { devices: devicesIDs }); /** * Enables given list of devices * @param devicesIDs array of device ids */ -const DASDDisable = (devicesIDs: string[]) => post("/api/storage/dasd/disable", devicesIDs); +const DASDDisable = (devicesIDs: string[]) => post("/api/storage/dasd/disable", { devices: devicesIDs }); /** * Enables giag on given list of devices diff --git a/web/src/api/network.ts b/web/src/api/network.ts index 749fec8e6d..d623c5fbcd 100644 --- a/web/src/api/network.ts +++ b/web/src/api/network.ts @@ -58,7 +58,7 @@ const addConnection = (connection: APIConnection) => post("/api/network/connecti /** * Updates given connection * - * @param connection - connection to be added + * @param connection - connection to be updated */ const updateConnection = (connection: APIConnection) => put(`/api/network/connections/${connection.id}`, connection); diff --git a/web/src/components/storage/DASDTable.tsx b/web/src/components/storage/DASDTable.tsx index e5ca7fde47..17be4c8f1e 100644 --- a/web/src/components/storage/DASDTable.tsx +++ b/web/src/components/storage/DASDTable.tsx @@ -44,6 +44,7 @@ import { hex } from "~/utils"; import { sort } from "fast-sort"; import { useInstallerClient } from "~/context/installer"; import { DASDDevice } from "~/types/dasd"; +import { DASDDisable, DASDEnable, DASDFormat, diagDisable, diagEnable } from "~/api/dasd"; // FIXME: please, note that this file still requiring refinements until reach a // reasonable stable version @@ -80,17 +81,18 @@ const columns = [ { id: "partitionInfo", label: _("Partition Info") }, ]; -const Actions = ({ devices, isDisabled }) => { +const Actions = ({ devices, isDisabled }: { devices: DASDDevice[], isDisabled: boolean }) => { const { storage: client } = useInstallerClient(); const [isOpen, setIsOpen] = useState(false); const onToggle = () => setIsOpen(!isOpen); const onSelect = () => setIsOpen(false); - const activate = () => client.dasd.enableDevices(devices); - const deactivate = () => client.dasd.disableDevices(devices); - const setDiagOn = () => client.dasd.setDIAG(devices, true); - const setDiagOff = () => client.dasd.setDIAG(devices, false); + const deviceIds = devices.map((d) => d.id); + const activate = () => DASDEnable(deviceIds); + const deactivate = () => DASDDisable(deviceIds); + const setDiagOn = () => diagEnable(deviceIds); + const setDiagOff = () => diagDisable(deviceIds); const format = () => { const offline = devices.filter((d) => !d.enabled); @@ -98,7 +100,7 @@ const Actions = ({ devices, isDisabled }) => { return false; } - return client.dasd.format(devices); + return DASDFormat(devices.map((d) => d.id)); }; const Action = ({ children, ...props }) => ( @@ -156,7 +158,7 @@ const filterDevices = (devices: DASDDevice[], from: string, to: string): DASDDev export default function DASDTable({ state, dispatch }) { const [sortingColumn, setSortingColumn] = useState(columns[0]); - const [sortDirection, setSortDirection] : [ "asc" | "desc", Dispatch>] = useState("asc"); + const [sortDirection, setSortDirection]: ["asc" | "desc", Dispatch>] = useState("asc"); const sortColumnIndex = () => columns.findIndex((c) => c.id === sortingColumn.id); const filteredDevices = filterDevices(state.devices, state.minChannel, state.maxChannel); diff --git a/web/src/routes/storage.tsx b/web/src/routes/storage.tsx index 10b32ba217..a5d33f00ba 100644 --- a/web/src/routes/storage.tsx +++ b/web/src/routes/storage.tsx @@ -27,6 +27,7 @@ import { DASDPage, ISCSIPage } from "~/components/storage"; import ProposalPage from "~/components/storage/ProposalPage"; import { Route } from "~/types/routes"; import { N_ } from "~/i18n"; +import { probeDASD } from "~/api/dasd"; const PATHS = { root: "/storage", @@ -66,6 +67,8 @@ const routes = (): Route => ({ path: PATHS.dasd, element: , handle: { name: N_("DASD") }, + //FIXME: move to the onClick of the DASD SelectOption + loader: async () => probeDASD() }, ], }); From 6e8e581a689931cbc677abc9cabb307468252f28 Mon Sep 17 00:00:00 2001 From: Josef Reidinger Date: Fri, 23 Aug 2024 09:48:54 +0200 Subject: [PATCH 15/54] add jobs api --- web/src/api/storage.ts | 31 +++++++++++++++++++++++++++++++ web/src/types/job.ts | 28 ++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 web/src/api/storage.ts create mode 100644 web/src/types/job.ts diff --git a/web/src/api/storage.ts b/web/src/api/storage.ts new file mode 100644 index 0000000000..dd9bbd0b8f --- /dev/null +++ b/web/src/api/storage.ts @@ -0,0 +1,31 @@ +/* + * Copyright (c) [2024] SUSE LLC + * + * All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as published + * by the Free Software Foundation. + * + * 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 General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, contact SUSE LLC. + * + * To contact SUSE LLC about this file by physical or electronic mail, you may + * find current contact information at www.suse.com. + */ + +import { post, get, put } from "~/api/http"; +import { Job } from "~/types/job"; + +/** + * Returns the installer status information + */ +const fetchStorageJobs = async (): Promise => { + const jobs: Job[] = await get("/api/manager/installer"); + return jobs; + }; diff --git a/web/src/types/job.ts b/web/src/types/job.ts new file mode 100644 index 0000000000..68453f2b9c --- /dev/null +++ b/web/src/types/job.ts @@ -0,0 +1,28 @@ +/* + * Copyright (c) [2024] SUSE LLC + * + * All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as published + * by the Free Software Foundation. + * + * 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 General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, contact SUSE LLC. + * + * To contact SUSE LLC about this file by physical or electronic mail, you may + * find current contact information at www.suse.com. + */ + +type Job = { + id: string; + running: boolean; + exitCode: number; + }; + + export type { Job }; \ No newline at end of file From fc3497fd4b6a5aa247d618c9cd33aedcf2644d24 Mon Sep 17 00:00:00 2001 From: Knut Anderssen Date: Fri, 23 Aug 2024 11:48:18 +0100 Subject: [PATCH 16/54] Moved DASD filters to queries --- web/src/components/storage/DASDTable.tsx | 25 ++--- web/src/queries/dasd.ts | 116 +++++++++++++++-------- 2 files changed, 91 insertions(+), 50 deletions(-) diff --git a/web/src/components/storage/DASDTable.tsx b/web/src/components/storage/DASDTable.tsx index 17be4c8f1e..3cd479ee54 100644 --- a/web/src/components/storage/DASDTable.tsx +++ b/web/src/components/storage/DASDTable.tsx @@ -42,9 +42,9 @@ import { CardField } from "~/components/core"; import { _ } from "~/i18n"; import { hex } from "~/utils"; import { sort } from "fast-sort"; -import { useInstallerClient } from "~/context/installer"; import { DASDDevice } from "~/types/dasd"; import { DASDDisable, DASDEnable, DASDFormat, diagDisable, diagEnable } from "~/api/dasd"; +import { useDASDDevices, useFilterDASD, useFilterDASDChange } from "~/queries/dasd"; // FIXME: please, note that this file still requiring refinements until reach a // reasonable stable version @@ -82,7 +82,6 @@ const columns = [ ]; const Actions = ({ devices, isDisabled }: { devices: DASDDevice[], isDisabled: boolean }) => { - const { storage: client } = useInstallerClient(); const [isOpen, setIsOpen] = useState(false); const onToggle = () => setIsOpen(!isOpen); @@ -157,11 +156,15 @@ const filterDevices = (devices: DASDDevice[], from: string, to: string): DASDDev }; export default function DASDTable({ state, dispatch }) { + const devices = useDASDDevices(); + const { mutate: changeFilter } = useFilterDASDChange(); + const { minChannel, maxChannel } = useFilterDASD(); + const [sortingColumn, setSortingColumn] = useState(columns[0]); const [sortDirection, setSortDirection]: ["asc" | "desc", Dispatch>] = useState("asc"); const sortColumnIndex = () => columns.findIndex((c) => c.id === sortingColumn.id); - const filteredDevices = filterDevices(state.devices, state.minChannel, state.maxChannel); + const filteredDevices = filterDevices(devices, minChannel, maxChannel); const selectedDevicesIds = state.selectedDevices.map((d) => d.id); // Selecting @@ -194,19 +197,19 @@ export default function DASDTable({ state, dispatch }) { // Filtering const onMinChannelFilterChange = (_event, value) => { - dispatch({ type: "SET_MIN_CHANNEL", payload: { minChannel: value } }); + changeFilter({ minChannel: value }); }; const onMaxChannelFilterChange = (_event, value) => { - dispatch({ type: "SET_MAX_CHANNEL", payload: { maxChannel: value } }); + changeFilter({ maxChannel: value }); }; const removeMinChannelFilter = () => { - dispatch({ type: "SET_MIN_CHANNEL", payload: { minChannel: "" } }); + changeFilter({ minChannel: "" }); }; const removeMaxChannelFilter = () => { - dispatch({ type: "SET_MAX_CHANNEL", payload: { maxChannel: "" } }); + changeFilter({ maxChannel: "" }); }; const Content = () => { @@ -260,13 +263,13 @@ export default function DASDTable({ state, dispatch }) { - {state.minChannel !== "" && ( + {minChannel !== "" && (
selectAll(isSelecting), - isSelected: filteredDevices.length === state.selectedDevices.length, + isSelected: filteredDevices.length === selectedDevices.length, }} /> {columns.map((column, index) => ( @@ -309,8 +310,8 @@ export default function DASDTable({ state, dispatch }) { diff --git a/web/src/queries/dasd.ts b/web/src/queries/dasd.ts index af5400f5c8..17c1b716b3 100644 --- a/web/src/queries/dasd.ts +++ b/web/src/queries/dasd.ts @@ -30,8 +30,8 @@ import { import { useInstallerClient } from "~/context/installer"; import React from "react"; import { hex } from "~/utils"; -import { DASDDevice } from "~/types/dasd"; -import { findStorageJob } from "~/api/storage"; +import { DASDDevice, FormatJob } from "~/types/dasd"; +import { fetchStorageJobs, findStorageJob } from "~/api/storage"; /** * Returns a query for retrieving the dasd devices @@ -49,6 +49,22 @@ const useDASDDevices = () => { return devices.map((d) => ({ ...d, hexId: hex(d.id) })); }; +/** + * Returns a query for retrieving DASD format jobs + */ +const DASDFormatJobsQuery = () => ({ + queryKey: ["dasd", "formatJobs"], + queryFn: () => fetchStorageJobs(), +}); + +/** + * Hook that returns DASD format jobs. + */ +const useDASDFormatJobs = () => { + const { data: jobs } = useSuspenseQuery(DASDFormatJobsQuery()); + return jobs; +}; + /** * Returns a query for retrieving the dasd format job */ @@ -58,7 +74,7 @@ const DASDFormatJobQuery = (id: string) => ({ }); /** - * Hook that returns DASD devices. + * Hook that returns and specific DASD format job. */ const useDASDFormatJob = (id: string) => { const { data: job } = useSuspenseQuery(DASDFormatJobQuery(id)); @@ -80,9 +96,10 @@ const useDASDFormatJobChanges = (id: string) => { if ( event.type === "DASDFormatJobChanged" && event.job_id === id ) { - const data = queryClient.getQueryData(["dasd", "formatJob", id]) as object; - const { step, total, done } = event; - queryClient.setQueryData(["dasd", "formatJob", id], { ...data, step, total, done }); + const data = queryClient.getQueryData(["dasd", "formatJob", id]) as FormatJob; + const merged_summary = { ...data.summary, ...event.summary }; + const job = { ...data, summary: merged_summary }; + queryClient.setQueryData(["dasd", "formatJob", id], job); } }); }); @@ -257,6 +274,8 @@ const useDiagDisableMutation = () => { return useMutation(query); }; -export { useDASDDevices, useDASDDevicesChanges, useDASDEnableMutation, useDASDDisableMutation, useDiagDisableMutation, - useDiagEnableMutation, useFilterDASDChange, filterDASDQuery, useFilterDASD, useSelectedDASD, useSelectedDASDChange, selectedDASDQuery, - useDASDFormatJobChanges, useDASDFormatJob }; +export { + useDASDDevices, useDASDDevicesChanges, useDASDEnableMutation, useDASDDisableMutation, useDiagDisableMutation, + useDiagEnableMutation, useFilterDASDChange, filterDASDQuery, useFilterDASD, useSelectedDASD, useSelectedDASDChange, selectedDASDQuery, + useDASDFormatJobChanges, useDASDFormatJob, useDASDFormatJobs +}; diff --git a/web/src/types/dasd.ts b/web/src/types/dasd.ts index c72e30e751..fde2e7b6d3 100644 --- a/web/src/types/dasd.ts +++ b/web/src/types/dasd.ts @@ -32,11 +32,15 @@ type DASDDevice = { hexId: number; }; -type FormatJob = { - job_id: string, +type FormatSummary = { total: number, step: number, done: boolean } +type FormatJob = { + job_id: string, + summary: { [key: string]: FormatSummary } +} + export type { DASDDevice, FormatJob }; From e2da06aab157e353abbe1100aa4269792f93517f Mon Sep 17 00:00:00 2001 From: Knut Anderssen Date: Tue, 27 Aug 2024 11:24:49 +0100 Subject: [PATCH 22/54] Use the DASD id for notifing format job changes --- service/lib/agama/dbus/storage/dasds_format_job.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/service/lib/agama/dbus/storage/dasds_format_job.rb b/service/lib/agama/dbus/storage/dasds_format_job.rb index f08c239a27..a2c89737ec 100644 --- a/service/lib/agama/dbus/storage/dasds_format_job.rb +++ b/service/lib/agama/dbus/storage/dasds_format_job.rb @@ -119,7 +119,7 @@ def initialize(initial, dasds_tree, path, logger: nil) # Current status, in the format described by the D-Bus API def summary result = {} - @infos.each_value { |i| result[i.path] = i.to_dbus if i.path } + @infos.each_value { |i| result[i.id] = i.to_dbus if i.id } result end From 8991d01f56acd996eafd8fa9e554e3216fe13806 Mon Sep 17 00:00:00 2001 From: Knut Anderssen Date: Tue, 27 Aug 2024 11:25:30 +0100 Subject: [PATCH 23/54] Small fixes for the DASD Format progress dialog --- web/src/components/storage/DASDFormatProgress.jsx | 10 +++++----- web/src/queries/dasd.ts | 12 ++++++------ web/src/types/dasd.ts | 2 +- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/web/src/components/storage/DASDFormatProgress.jsx b/web/src/components/storage/DASDFormatProgress.jsx index ca9c5db7b3..913b0a950f 100644 --- a/web/src/components/storage/DASDFormatProgress.jsx +++ b/web/src/components/storage/DASDFormatProgress.jsx @@ -27,21 +27,21 @@ import { useDASDFormatJobChanges } from "~/queries/dasd"; export default function DASDFormatProgress({ job, devices, isOpen = true }) { const formatJob = useDASDFormatJobChanges(job.id); - const progress = formatJob?.summary; + const progress = formatJob?.summary || {}; const ProgressContent = ({ progress }) => { return ( - {Object.entries(progress).map(([path, [total, step, done]]) => { - const device = devices.find((d) => d.id === path.split("/").slice(-1)[0]); + {Object.entries(progress).map(([id, { total, step, done }]) => { + const device = devices.find((d) => d.id === id); return ( diff --git a/web/src/queries/dasd.ts b/web/src/queries/dasd.ts index 17c1b716b3..41adceddba 100644 --- a/web/src/queries/dasd.ts +++ b/web/src/queries/dasd.ts @@ -200,21 +200,21 @@ const useDASDDevicesChanges = () => { return client.ws().onEvent((event) => { if (event.type === "DASDDeviceAdded") { - const device : DASDDevice = event.device; + const device: DASDDevice = event.device; queryClient.setQueryData(["dasd", "devices"], (prev: DASDDevice[]) => { // do not use push here as updater has to be immutable const res = prev.concat([device]); return res; - }); + }); } else if (event.type === "DASDDeviceRemoved") { - const device : DASDDevice = event.device; + const device: DASDDevice = event.device; const { id } = device; queryClient.setQueryData(["dasd", "devices"], (prev: DASDDevice[]) => { const res = prev.filter(dev => dev.id !== id); return res; - }); + }); } else if (event.type === "DASDDeviceChanged") { - const device : DASDDevice = event.device; + const device: DASDDevice = event.device; const { id } = device; queryClient.setQueryData(["dasd", "devices"], (prev: DASDDevice[]) => { // deep copy of original to have it immutable @@ -222,7 +222,7 @@ const useDASDDevicesChanges = () => { const index = res.findIndex(dev => dev.id === id); res[index] = device; return res; - }); + }); } }); }); diff --git a/web/src/types/dasd.ts b/web/src/types/dasd.ts index fde2e7b6d3..dd24a3ef3b 100644 --- a/web/src/types/dasd.ts +++ b/web/src/types/dasd.ts @@ -40,7 +40,7 @@ type FormatSummary = { type FormatJob = { job_id: string, - summary: { [key: string]: FormatSummary } + summary?: { [key: string]: FormatSummary } } export type { DASDDevice, FormatJob }; From d67205df9744d13e8a295181017820b0240cc97a Mon Sep 17 00:00:00 2001 From: Knut Anderssen Date: Tue, 27 Aug 2024 12:22:54 +0100 Subject: [PATCH 24/54] Fix jobs summary test --- service/test/agama/dbus/storage/jobs_tree_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/service/test/agama/dbus/storage/jobs_tree_test.rb b/service/test/agama/dbus/storage/jobs_tree_test.rb index 801455e99d..ab73f9aee2 100644 --- a/service/test/agama/dbus/storage/jobs_tree_test.rb +++ b/service/test/agama/dbus/storage/jobs_tree_test.rb @@ -62,7 +62,7 @@ expect(job.path).to match(/#{described_class::ROOT_PATH}\/[0-9]+/) expect(job.summary).to eq( - { "/path/dasd1" => [1000, 0, false], "/path/dasd2" => [2000, 0, false] } + { "0.0.001" => [1000, 0, false], "0.0.002" => [2000, 0, false] } ) end From 94f92fa39171aa5fc4197cba0cecc92c3991e796 Mon Sep 17 00:00:00 2001 From: Josef Reidinger Date: Wed, 28 Aug 2024 21:16:08 +0200 Subject: [PATCH 25/54] add non-working test for component with mutating queries --- web/src/components/storage/DASDTable.test.tsx | 64 +++++++++++++++++++ web/src/queries/dasd.ts | 9 +-- web/src/types/dasd.ts | 7 +- 3 files changed, 72 insertions(+), 8 deletions(-) create mode 100644 web/src/components/storage/DASDTable.test.tsx diff --git a/web/src/components/storage/DASDTable.test.tsx b/web/src/components/storage/DASDTable.test.tsx new file mode 100644 index 0000000000..f62fb50ec5 --- /dev/null +++ b/web/src/components/storage/DASDTable.test.tsx @@ -0,0 +1,64 @@ +/* + * Copyright (c) [2024] SUSE LLC + * + * All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as published + * by the Free Software Foundation. + * + * 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 General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, contact SUSE LLC. + * + * To contact SUSE LLC about this file by physical or electronic mail, you may + * find current contact information at www.suse.com. + */ + +import React from "react"; +import { act, screen } from "@testing-library/react"; +import { installerRender } from "~/test-utils"; +import DASDTable from "~/components/storage/DASDTable"; +import { DASDDevice, FilterDASD } from "~/types/dasd"; + +let mockDASDDevices: DASDDevice[]; +let mockDASDFilter: { minChannel: string, maxChannel: string }; +let mockSelectedDASD: { minChannel: string, maxChannel: string }; + +jest.mock("~/queries/dasd", () => ({ + useDASDDevices: () => mockDASDDevices, + useFilterDASD: () => mockDASDFilter, + useFilterDASDChange: () => jest.fn().mockImplementation(() => ({ mutate: (data: FilterDASD) => ({data: mockDASDFilter}) })), + useSelectedDASD: () => mockSelectedDASD, + useSelectedDASDChange: () => jest.fn().mockImplementation(()=> ({ data: mockSelectedDASD })), +})); + +describe("DASDTable", () => { + describe("when there is some DASD devices available", () => { + beforeEach(() => { + mockDASDDevices = [ + { + id: "0.0.0200", + enabled: false, + deviceName: "dasda", + deviceType: "eckd", + formatted: false, + diag: false, + status: "active", + accessType: "rw", + partitionInfo: "1", + hexId: 0x200, + } + ]; + }); + + it("renders those devices", () => { + installerRender(); + screen.getByText("active"); + }); + }); +}); \ No newline at end of file diff --git a/web/src/queries/dasd.ts b/web/src/queries/dasd.ts index 41adceddba..93b6d036aa 100644 --- a/web/src/queries/dasd.ts +++ b/web/src/queries/dasd.ts @@ -30,7 +30,7 @@ import { import { useInstallerClient } from "~/context/installer"; import React from "react"; import { hex } from "~/utils"; -import { DASDDevice, FormatJob } from "~/types/dasd"; +import { DASDDevice, FilterDASD, FormatJob } from "~/types/dasd"; import { fetchStorageJobs, findStorageJob } from "~/api/storage"; /** @@ -161,18 +161,13 @@ const filterDASDQuery = () => ({ staleTime: Infinity, }); -const useFilterDASD = (): { minChannel?: string, maxChannel?: string } => { +const useFilterDASD = (): FilterDASD => { const { data } = useQuery(filterDASDQuery()); return data || { minChannel: "", maxChannel: "" }; } const useFilterDASDChange = () => { - type FilterDASD = { - maxChannel?: string; - minChannel?: string; - }; - const queryClient = useQueryClient(); const mutation = useMutation({ diff --git a/web/src/types/dasd.ts b/web/src/types/dasd.ts index dd24a3ef3b..482da1906b 100644 --- a/web/src/types/dasd.ts +++ b/web/src/types/dasd.ts @@ -43,4 +43,9 @@ type FormatJob = { summary?: { [key: string]: FormatSummary } } -export type { DASDDevice, FormatJob }; +type FilterDASD = { + maxChannel?: string; + minChannel?: string; +}; + +export type { DASDDevice, FormatJob, FilterDASD }; From 23405eb606aa158dd34afbc989ea5c17df9e8fdc Mon Sep 17 00:00:00 2001 From: Josef Reidinger Date: Wed, 28 Aug 2024 22:09:11 +0200 Subject: [PATCH 26/54] add test for dasd format progress together with fix it reveals --- .../components/storage/DASDFormatProgress.jsx | 2 +- .../storage/DASDFormatProgress.test.jsx | 95 +++++++++++++++++++ 2 files changed, 96 insertions(+), 1 deletion(-) create mode 100644 web/src/components/storage/DASDFormatProgress.test.jsx diff --git a/web/src/components/storage/DASDFormatProgress.jsx b/web/src/components/storage/DASDFormatProgress.jsx index 913b0a950f..dc706e4361 100644 --- a/web/src/components/storage/DASDFormatProgress.jsx +++ b/web/src/components/storage/DASDFormatProgress.jsx @@ -61,7 +61,7 @@ export default function DASDFormatProgress({ job, devices, isOpen = true }) { return ( - {progress ? : } + {Object.keys(progress).length !== 0 ? : } ); } diff --git a/web/src/components/storage/DASDFormatProgress.test.jsx b/web/src/components/storage/DASDFormatProgress.test.jsx new file mode 100644 index 0000000000..b0b4c35155 --- /dev/null +++ b/web/src/components/storage/DASDFormatProgress.test.jsx @@ -0,0 +1,95 @@ +/* + * Copyright (c) [2024] SUSE LLC + * + * All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as published + * by the Free Software Foundation. + * + * 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 General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, contact SUSE LLC. + * + * To contact SUSE LLC about this file by physical or electronic mail, you may + * find current contact information at www.suse.com. + */ + +import React from "react"; +import { act, screen } from "@testing-library/react"; +import { installerRender } from "~/test-utils"; +import DASDTable from "~/components/storage/DASDTable"; +import DASDFormatProgress from "./DASDFormatProgress"; + +let mockDASDFormatJob; + +jest.mock("~/queries/dasd", () => ({ + useDASDFormatJobChanges: () => mockDASDFormatJob, +})); + +describe("DASDFormatProgress", () => { + describe("when there is already some progress", () => { + beforeEach(() => { + mockDASDFormatJob = + { + jobId: "0.0.0200", + summary: { + "0.0.0200": { + total: 5, + step: 1, + done: false, + }, + }, + } + ; + }); + + it("renders the progress", () => { + const devices = [{ + id: "0.0.0200", + enabled: false, + deviceName: "dasda", + deviceType: "eckd", + formatted: false, + diag: false, + status: "active", + accessType: "rw", + partitionInfo: "1", + hexId: 0x200, + }]; + installerRender(); + screen.getByText("0.0.0200 - dasda"); + }); + }); + + describe("when there is no progress yet", () => { + beforeEach(() => { + mockDASDFormatJob = + { + jobId: "0.0.0200", + } + ; + }); + + it("renders the progress", () => { + const devices = [{ + id: "0.0.0200", + enabled: false, + deviceName: "dasda", + deviceType: "eckd", + formatted: false, + diag: false, + status: "active", + accessType: "rw", + partitionInfo: "1", + hexId: 0x200, + }]; + installerRender(); + screen.getByText("Waiting for progress report"); + }); + }); +}); \ No newline at end of file From 5dba41c4a9adc44dd5cf9656680ccb580f258f0b Mon Sep 17 00:00:00 2001 From: Josef Reidinger Date: Thu, 29 Aug 2024 08:10:43 +0200 Subject: [PATCH 27/54] transform DASDFormatProgress component to typescript --- ...ormatProgress.test.jsx => DASDFormatProgress.test.tsx} | 3 ++- .../{DASDFormatProgress.jsx => DASDFormatProgress.tsx} | 5 +++-- web/src/queries/dasd.ts | 8 ++++---- web/src/types/dasd.ts | 4 ++-- 4 files changed, 11 insertions(+), 9 deletions(-) rename web/src/components/storage/{DASDFormatProgress.test.jsx => DASDFormatProgress.test.tsx} (97%) rename web/src/components/storage/{DASDFormatProgress.jsx => DASDFormatProgress.tsx} (90%) diff --git a/web/src/components/storage/DASDFormatProgress.test.jsx b/web/src/components/storage/DASDFormatProgress.test.tsx similarity index 97% rename from web/src/components/storage/DASDFormatProgress.test.jsx rename to web/src/components/storage/DASDFormatProgress.test.tsx index b0b4c35155..5855c09e36 100644 --- a/web/src/components/storage/DASDFormatProgress.test.jsx +++ b/web/src/components/storage/DASDFormatProgress.test.tsx @@ -24,8 +24,9 @@ import { act, screen } from "@testing-library/react"; import { installerRender } from "~/test-utils"; import DASDTable from "~/components/storage/DASDTable"; import DASDFormatProgress from "./DASDFormatProgress"; +import { FormatJob } from "~/types/dasd"; -let mockDASDFormatJob; +let mockDASDFormatJob : FormatJob; jest.mock("~/queries/dasd", () => ({ useDASDFormatJobChanges: () => mockDASDFormatJob, diff --git a/web/src/components/storage/DASDFormatProgress.jsx b/web/src/components/storage/DASDFormatProgress.tsx similarity index 90% rename from web/src/components/storage/DASDFormatProgress.jsx rename to web/src/components/storage/DASDFormatProgress.tsx index dc706e4361..2bc39633ee 100644 --- a/web/src/components/storage/DASDFormatProgress.jsx +++ b/web/src/components/storage/DASDFormatProgress.tsx @@ -24,12 +24,13 @@ import { Progress, Skeleton, Stack } from "@patternfly/react-core"; import { Popup } from "~/components/core"; import { _ } from "~/i18n"; import { useDASDFormatJobChanges } from "~/queries/dasd"; +import { FormatJob, FormatSummary } from "~/types/dasd"; export default function DASDFormatProgress({ job, devices, isOpen = true }) { - const formatJob = useDASDFormatJobChanges(job.id); + const formatJob: FormatJob = useDASDFormatJobChanges(job.id); const progress = formatJob?.summary || {}; - const ProgressContent = ({ progress }) => { + const ProgressContent = ({ progress } : { progress: { [key: string]: FormatSummary }}) => { return ( {Object.entries(progress).map(([id, { total, step, done }]) => { diff --git a/web/src/queries/dasd.ts b/web/src/queries/dasd.ts index 41adceddba..999647dc94 100644 --- a/web/src/queries/dasd.ts +++ b/web/src/queries/dasd.ts @@ -70,13 +70,13 @@ const useDASDFormatJobs = () => { */ const DASDFormatJobQuery = (id: string) => ({ queryKey: ["dasd", "formatJob", id], - queryFn: () => findStorageJob(id), + queryFn: () => findStorageJob(id).then((sj) => ({jobId: sj.id})), }); /** * Hook that returns and specific DASD format job. */ -const useDASDFormatJob = (id: string) => { +const useDASDFormatJob = (id: string) : FormatJob => { const { data: job } = useSuspenseQuery(DASDFormatJobQuery(id)); return job; }; @@ -84,7 +84,7 @@ const useDASDFormatJob = (id: string) => { /** * Listens for DASD format job changes. */ -const useDASDFormatJobChanges = (id: string) => { +const useDASDFormatJobChanges = (id: string) : FormatJob => { const client = useInstallerClient(); const queryClient = useQueryClient(); @@ -94,7 +94,7 @@ const useDASDFormatJobChanges = (id: string) => { return client.ws().onEvent((event) => { // TODO: for simplicity we now just invalidate query instead of manually adding, removing or changing devices if ( - event.type === "DASDFormatJobChanged" && event.job_id === id + event.type === "DASDFormatJobChanged" && event.jobId === id ) { const data = queryClient.getQueryData(["dasd", "formatJob", id]) as FormatJob; const merged_summary = { ...data.summary, ...event.summary }; diff --git a/web/src/types/dasd.ts b/web/src/types/dasd.ts index dd24a3ef3b..5e7db5161b 100644 --- a/web/src/types/dasd.ts +++ b/web/src/types/dasd.ts @@ -39,8 +39,8 @@ type FormatSummary = { } type FormatJob = { - job_id: string, + jobId: string, summary?: { [key: string]: FormatSummary } } -export type { DASDDevice, FormatJob }; +export type { DASDDevice, FormatJob, FormatSummary }; From 40baceb595922a91fabe17f7808119bff1b9d064 Mon Sep 17 00:00:00 2001 From: Josef Reidinger Date: Thu, 29 Aug 2024 08:13:04 +0200 Subject: [PATCH 28/54] Update web/src/api/dasd.ts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Imobach González Sosa --- web/src/api/dasd.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/api/dasd.ts b/web/src/api/dasd.ts index 46d58c59e9..6044970e54 100644 --- a/web/src/api/dasd.ts +++ b/web/src/api/dasd.ts @@ -57,7 +57,7 @@ const DASDEnable = (devicesIDs: string[]) => post("/api/storage/dasd/enable", { const DASDDisable = (devicesIDs: string[]) => post("/api/storage/dasd/disable", { devices: devicesIDs }); /** - * Enables giag on given list of devices + * Enables diag on given list of devices * @param devicesIDs array of device ids */ const diagEnable = (devicesIDs: string[]) => put("/api/storage/dasd/diag", { devices: devicesIDs, diag: true }); From 4e8e12986e6694cc4a868bdba0215efa7aae1e03 Mon Sep 17 00:00:00 2001 From: Knut Anderssen Date: Thu, 29 Aug 2024 07:26:20 +0100 Subject: [PATCH 29/54] Removing dasd client --- web/src/client/storage.js | 258 +-------------------------------- web/src/client/storage.test.js | 185 +---------------------- 2 files changed, 2 insertions(+), 441 deletions(-) diff --git a/web/src/client/storage.js b/web/src/client/storage.js index 3a14b4e318..b6a875c44a 100644 --- a/web/src/client/storage.js +++ b/web/src/client/storage.js @@ -723,260 +723,6 @@ class ProposalManager { } } -/** - * Class providing an API for managing Direct Access Storage Devices (DASDs) - */ -class DASDManager { - /** - * @param {string} service - D-Bus service name - * @param {string} address - D-Bus address - */ - constructor(service, address) { - this.service = service; - this.address = address; - this.proxies = {}; - } - - /** - * @return {DBusClient} client - */ - client() { - // return this.assigned_client; - if (!this._client) { - this._client = new DBusClient(this.service, this.address); - } - - return this._client; - } - - // FIXME: use info from ObjectManager instead. - // https://github.com/openSUSE/Agama/pull/501#discussion_r1147707515 - async isSupported() { - const proxy = await this.managerProxy(); - - return proxy !== undefined; - } - - /** - * Build a job - * - * @returns {StorageJob} - * - * @typedef {object} StorageJob - * @property {string} path - * @property {boolean} running - * @property {number} exitCode - */ - buildJob(job) { - return { - path: job.path, - running: job.Running, - exitCode: job.ExitCode, - }; - } - - /** - * Triggers a DASD probing - */ - async probe() { - const proxy = await this.managerProxy(); - await proxy?.Probe(); - } - - /** - * Gets the list of DASD devices - * - * @returns {Promise} - */ - async getDevices() { - // FIXME: should we do the probing here? - await this.probe(); - const devices = await this.devicesProxy(); - return Object.values(devices).map(this.buildDevice); - } - - /** - * Requests the format action for given devices - * - * @param {DASDDevice[]} devices - */ - async format(devices) { - const proxy = await this.managerProxy(); - const devicesPath = devices.map((d) => this.devicePath(d)); - proxy.Format(devicesPath); - } - - /** - * Set DIAG for given devices - * - * @param {DASDDevice[]} devices - * @param {boolean} value - */ - async setDIAG(devices, value) { - const proxy = await this.managerProxy(); - const devicesPath = devices.map((d) => this.devicePath(d)); - proxy.SetDiag(devicesPath, value); - } - - /** - * Enables given DASD devices - * - * @param {DASDDevice[]} devices - */ - async enableDevices(devices) { - const proxy = await this.managerProxy(); - const devicesPath = devices.map((d) => this.devicePath(d)); - proxy.Enable(devicesPath); - } - - /** - * Disables given DASD devices - * - * @param {DASDDevice[]} devices - */ - async disableDevices(devices) { - const proxy = await this.managerProxy(); - const devicesPath = devices.map((d) => this.devicePath(d)); - proxy.Disable(devicesPath); - } - - /** - * @private - * Proxy for objects implementing org.opensuse.Agama.Storage1.Job iface - * - * @note The jobs are dynamically exported. - * - * @returns {Promise} - */ - async jobsProxy() { - if (!this.proxies.jobs) - this.proxies.jobs = await this.client().proxies(STORAGE_JOB_IFACE, STORAGE_JOBS_NAMESPACE); - - return this.proxies.jobs; - } - - async getJobs() { - const proxy = await this.jobsProxy(); - return Object.values(proxy) - .filter((p) => p.Running) - .map(this.buildJob); - } - - async onJobAdded(handler) { - const proxy = await this.jobsProxy(); - proxy.addEventListener("added", (_, proxy) => handler(this.buildJob(proxy))); - } - - async onJobChanged(handler) { - const proxy = await this.jobsProxy(); - proxy.addEventListener("changed", (_, proxy) => handler(this.buildJob(proxy))); - } - - /** - * @private - * Proxy for objects implementing org.opensuse.Agama.Storage1.Job iface - * - * @note The jobs are dynamically exported. - * - * @returns {Promise} - */ - async formatProxy(jobPath) { - const proxy = await this.client().proxy(DASD_STATUS_IFACE, jobPath); - return proxy; - } - - async onFormatProgress(jobPath, handler) { - const proxy = await this.formatProxy(jobPath); - proxy.addEventListener("changed", (_, proxy) => { - handler(proxy.Summary); - }); - } - - /** - * @private - * Proxy for objects implementing org.opensuse.Agama.Storage1.DASD.Device iface - * - * @note The DASD devices are dynamically exported. - * - * @returns {Promise} - */ - async devicesProxy() { - if (!this.proxies.devices) - this.proxies.devices = await this.client().proxies(DASD_DEVICE_IFACE, DASD_DEVICES_NAMESPACE); - - return this.proxies.devices; - } - - /** - * @private - * Proxy for org.opensuse.Agama.Storage1.DASD.Manager iface - * - * @returns {Promise} - */ - async managerProxy() { - if (!this.proxies.dasdManager) - this.proxies.dasdManager = await this.client().proxy(DASD_MANAGER_IFACE, STORAGE_OBJECT); - - return this.proxies.dasdManager; - } - - async deviceEventListener(signal, handler) { - const proxy = await this.devicesProxy(); - const action = (_, proxy) => handler(this.buildDevice(proxy)); - - proxy.addEventListener(signal, action); - return () => proxy.removeEventListener(signal, action); - } - - /** - * Build a list of DASD devices - * - * @returns {DASDDevice} - * - * @typedef {object} DASDDevice - * @property {string} id - * @property {number} hexId - * @property {string} accessType - * @property {string} channelId - * @property {boolean} diag - * @property {boolean} enabled - * @property {boolean} formatted - * @property {string} name - * @property {string} partitionInfo - * @property {string} status - * @property {string} type - */ - buildDevice(device) { - const id = device.path.split("/").slice(-1)[0]; - const enabled = device.Enabled; - - return { - id, - accessType: enabled ? device.AccessType : "offline", - channelId: device.Id, - diag: device.Diag, - enabled, - formatted: device.Formatted, - hexId: hex(device.Id), - name: device.DeviceName, - partitionInfo: enabled ? device.PartitionInfo : "", - status: device.Status, - type: device.Type, - }; - } - - /** - * @private - * Builds the D-Bus path for the given DASD device - * - * @param {DASDDevice} device - * @returns {string} - */ - devicePath(device) { - return DASD_DEVICES_NAMESPACE + "/" + device.id; - } -} - /** * Class providing an API for managing zFCP through D-Bus */ @@ -1602,8 +1348,6 @@ class StorageBaseClient { this.proposal = new ProposalManager(this.client, this.system); this.iscsi = new ISCSIManager(this.client); // @ts-ignore - this.dasd = new DASDManager(StorageBaseClient.SERVICE, client); - // @ts-ignore this.zfcp = new ZFCPManager(StorageBaseClient.SERVICE, client); } @@ -1652,6 +1396,6 @@ class StorageBaseClient { /** * Allows interacting with the storage settings */ -class StorageClient extends WithStatus(StorageBaseClient, "/storage/status", SERVICE_NAME) {} +class StorageClient extends WithStatus(StorageBaseClient, "/storage/status", SERVICE_NAME) { } export { StorageClient, EncryptionMethods }; diff --git a/web/src/client/storage.test.js b/web/src/client/storage.test.js index 6b9d214d0a..58fc4b4968 100644 --- a/web/src/client/storage.test.js +++ b/web/src/client/storage.test.js @@ -20,7 +20,7 @@ */ // @ts-check -// cspell:ignore ECKD dasda ddgdcbibhd wwpns +// cspell:ignore ddgdcbibhd wwpns import { HTTPClient } from "./http"; import DBusClient from "./dbus"; @@ -273,31 +273,6 @@ const multipath = { udevPaths: [], }; -/** @type {StorageDevice} */ -const dasd = { - sid: 69, - isDrive: true, - type: "dasd", - vendor: "IBM", - model: "IBM", - driver: [], - bus: "", - busId: "0.0.0150", - transport: "", - dellBOSS: false, - sdCard: false, - active: true, - name: "/dev/dasda", - description: "", - size: 2048, - start: 0, - encrypted: false, - shrinking: { unsupported: ["Resizing is not supported"] }, - systems: [], - udevIds: [], - udevPaths: [], -}; - /** @type {StorageDevice} */ const sdf = { sid: 70, @@ -469,7 +444,6 @@ const systemDevices = { md0, raid, multipath, - dasd, sdf, sdf1, lvmVg, @@ -600,35 +574,6 @@ const contexts = { startup: "onboot", }, ], - withoutDASDDevices: () => { - cockpitProxies.dasdDevices = {}; - }, - withDASDDevices: () => { - cockpitProxies.dasdDevices = { - "/org/opensuse/Agama/Storage1/dasds/8": { - path: "/org/opensuse/Agama/Storage1/dasds/8", - AccessType: "", - DeviceName: "dasd_sample_8", - Diag: false, - Enabled: true, - Formatted: false, - Id: "0.0.019e", - PartitionInfo: "", - Type: "ECKD", - }, - "/org/opensuse/Agama/Storage1/dasds/9": { - path: "/org/opensuse/Agama/Storage1/dasds/9", - AccessType: "rw", - DeviceName: "dasd_sample_9", - Diag: false, - Enabled: true, - Formatted: false, - Id: "0.0.ffff", - PartitionInfo: "/dev/dasd_sample_9", - Type: "FBA", - }, - }; - }, withoutZFCPControllers: () => { cockpitProxies.zfcpControllers = {}; }, @@ -986,11 +931,6 @@ const contexts = { }, }, { - deviceInfo: { - sid: 69, - name: "/dev/dasda", - description: "", - }, blockDevice: { active: true, encrypted: false, @@ -1001,19 +941,6 @@ const contexts = { udevIds: [], udevPaths: [], }, - drive: { - type: "dasd", - vendor: "IBM", - model: "IBM", - driver: [], - bus: "", - busId: "0.0.0150", - transport: "", - info: { - dellBOSS: false, - sdCard: false, - }, - }, }, { deviceInfo: { @@ -1147,8 +1074,6 @@ const mockProxy = (iface, path) => { return cockpitProxies.iscsiInitiator; case "org.opensuse.Agama.Storage1.ISCSI.Node": return cockpitProxies.iscsiNode[path]; - case "org.opensuse.Agama.Storage1.DASD.Manager": - return cockpitProxies.dasdManager; case "org.opensuse.Agama.Storage1.ZFCP.Manager": return cockpitProxies.zfcpManager; case "org.opensuse.Agama.Storage1.ZFCP.Controller": @@ -1160,8 +1085,6 @@ const mockProxies = (iface) => { switch (iface) { case "org.opensuse.Agama.Storage1.ISCSI.Node": return cockpitProxies.iscsiNodes; - case "org.opensuse.Agama.Storage1.DASD.Device": - return cockpitProxies.dasdDevices; case "org.opensuse.Agama.Storage1.ZFCP.Controller": return cockpitProxies.zfcpControllers; case "org.opensuse.Agama.Storage1.ZFCP.Disk": @@ -1192,8 +1115,6 @@ const reset = () => { cockpitProxies.iscsiInitiator = {}; cockpitProxies.iscsiNodes = {}; cockpitProxies.iscsiNode = {}; - cockpitProxies.dasdManager = {}; - cockpitProxies.dasdDevices = {}; cockpitProxies.zfcpManager = {}; cockpitProxies.zfcpControllers = {}; cockpitProxies.zfcpDisks = {}; @@ -1897,110 +1818,6 @@ describe("#proposal", () => { }); }); -describe.skip("#dasd", () => { - const sampleDasdDevice = { - id: "8", - accessType: "", - channelId: "0.0.019e", - diag: false, - enabled: true, - formatted: false, - hexId: 414, - name: "sample_dasd_device", - partitionInfo: "", - type: "ECKD", - }; - - const probeFn = jest.fn(); - const setDiagFn = jest.fn(); - const enableFn = jest.fn(); - const disableFn = jest.fn(); - - beforeEach(() => { - client = new StorageClient(); - cockpitProxies.dasdManager = { - Probe: probeFn, - SetDiag: setDiagFn, - Enable: enableFn, - Disable: disableFn, - }; - contexts.withDASDDevices(); - }); - - describe("#getDevices", () => { - it("triggers probing", async () => { - await client.dasd.getDevices(); - expect(probeFn).toHaveBeenCalled(); - }); - - describe("if there is no exported DASD devices yet", () => { - beforeEach(() => { - contexts.withoutDASDDevices(); - }); - - it("returns an empty list", async () => { - const result = await client.dasd.getDevices(); - expect(result).toStrictEqual([]); - }); - }); - - describe("if there are exported DASD devices", () => { - it("returns a list with the exported DASD devices", async () => { - const result = await client.dasd.getDevices(); - expect(result.length).toEqual(2); - expect(result).toContainEqual({ - id: "8", - accessType: "", - channelId: "0.0.019e", - diag: false, - enabled: true, - formatted: false, - hexId: 414, - name: "dasd_sample_8", - partitionInfo: "", - type: "ECKD", - }); - expect(result).toContainEqual({ - id: "9", - accessType: "rw", - channelId: "0.0.ffff", - diag: false, - enabled: true, - formatted: false, - hexId: 65535, - name: "dasd_sample_9", - partitionInfo: "/dev/dasd_sample_9", - type: "FBA", - }); - }); - }); - }); - - describe("#setDIAG", () => { - it("requests for setting DIAG for given devices", async () => { - await client.dasd.setDIAG([sampleDasdDevice], true); - expect(setDiagFn).toHaveBeenCalledWith(["/org/opensuse/Agama/Storage1/dasds/8"], true); - - await client.dasd.setDIAG([sampleDasdDevice], false); - expect(setDiagFn).toHaveBeenCalledWith(["/org/opensuse/Agama/Storage1/dasds/8"], false); - }); - }); - - describe("#enableDevices", () => { - it("requests for enabling given devices", async () => { - await client.dasd.enableDevices([sampleDasdDevice]); - expect(enableFn).toHaveBeenCalledWith(["/org/opensuse/Agama/Storage1/dasds/8"]); - }); - }); - - describe("#disableDevices", () => { - it("requests for disabling given devices", async () => { - await client.dasd.disableDevices([sampleDasdDevice]); - expect(disableFn).toHaveBeenCalledWith(["/org/opensuse/Agama/Storage1/dasds/8"]); - }); - }); -}); - describe.skip("#zfcp", () => { const probeFn = jest.fn(); let controllersCallbacks; From eeba75ff4b00191e6c1545c08266e8acfe2a87b4 Mon Sep 17 00:00:00 2001 From: Knut Anderssen Date: Thu, 29 Aug 2024 07:27:47 +0100 Subject: [PATCH 30/54] Removing leftovers --- web/src/components/storage/DASDTable.tsx | 1 - web/src/components/storage/DevicesTechMenu.jsx | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/web/src/components/storage/DASDTable.tsx b/web/src/components/storage/DASDTable.tsx index 02b0f76500..0cae638849 100644 --- a/web/src/components/storage/DASDTable.tsx +++ b/web/src/components/storage/DASDTable.tsx @@ -45,7 +45,6 @@ import { sort } from "fast-sort"; import { DASDDevice } from "~/types/dasd"; import { DASDDisable, DASDEnable, DASDFormat, diagDisable, diagEnable } from "~/api/dasd"; import { useDASDDevices, useFilterDASD, useFilterDASDChange, useSelectedDASD, useSelectedDASDChange } from "~/queries/dasd"; -import { useSelectedWifiChange } from "~/queries/network"; // FIXME: please, note that this file still requiring refinements until reach a // reasonable stable version diff --git a/web/src/components/storage/DevicesTechMenu.jsx b/web/src/components/storage/DevicesTechMenu.jsx index 4b000e07fb..9c60917b25 100644 --- a/web/src/components/storage/DevicesTechMenu.jsx +++ b/web/src/components/storage/DevicesTechMenu.jsx @@ -88,7 +88,7 @@ export default function DevicesTechMenu({ label }) { useEffect(() => { DASDSupported().then(setShowDasdLink); client.zfcp.isSupported().then(setShowZFCPLink); - }, [client.dasd, client.zfcp]); + }, [client.zfcp]); const toggle = (toggleRef) => ( setIsOpen(!isOpen)} isExpanded={isOpen}> From ad92d0193a6a1b4149618319d6e03eadda5a9812 Mon Sep 17 00:00:00 2001 From: Josef Reidinger Date: Thu, 29 Aug 2024 08:29:21 +0200 Subject: [PATCH 31/54] use connsistent name in API --- web/src/api/dasd.ts | 12 ++++++------ web/src/components/storage/DASDTable.tsx | 12 ++++++------ web/src/queries/dasd.ts | 15 ++++++++------- 3 files changed, 20 insertions(+), 19 deletions(-) diff --git a/web/src/api/dasd.ts b/web/src/api/dasd.ts index 6044970e54..befac775dc 100644 --- a/web/src/api/dasd.ts +++ b/web/src/api/dasd.ts @@ -42,31 +42,31 @@ const probeDASD = () => post("/api/storage/dasd/probe"); * @param devicesIDs array of device ids * @return id of format job */ -const DASDFormat = (devicesIDs: string[]): Promise => post("/api/storage/dasd/format", { devices: devicesIDs }).then(({ data }) => data); +const formatDASD = (devicesIDs: string[]): Promise => post("/api/storage/dasd/format", { devices: devicesIDs }).then(({ data }) => data); /** * Enables given list of devices * @param devicesIDs array of device ids */ -const DASDEnable = (devicesIDs: string[]) => post("/api/storage/dasd/enable", { devices: devicesIDs }); +const enableDASD = (devicesIDs: string[]) => post("/api/storage/dasd/enable", { devices: devicesIDs }); /** * Enables given list of devices * @param devicesIDs array of device ids */ -const DASDDisable = (devicesIDs: string[]) => post("/api/storage/dasd/disable", { devices: devicesIDs }); +const disableDASD = (devicesIDs: string[]) => post("/api/storage/dasd/disable", { devices: devicesIDs }); /** * Enables diag on given list of devices * @param devicesIDs array of device ids */ -const diagEnable = (devicesIDs: string[]) => put("/api/storage/dasd/diag", { devices: devicesIDs, diag: true }); +const enableDiag = (devicesIDs: string[]) => put("/api/storage/dasd/diag", { devices: devicesIDs, diag: true }); /** * Disables diag on given list of devices * @param devicesIDs array of device ids */ -const diagDisable = (devicesIDs: string[]) => put("/api/storage/dasd/diag", { devices: devicesIDs, diag: false }); +const disableDiag = (devicesIDs: string[]) => put("/api/storage/dasd/diag", { devices: devicesIDs, diag: false }); -export { fetchDASDDevices, DASDSupported, DASDFormat, probeDASD, DASDEnable, DASDDisable, diagEnable, diagDisable }; +export { fetchDASDDevices, DASDSupported, formatDASD, probeDASD, enableDASD, disableDASD, enableDiag, disableDiag }; diff --git a/web/src/components/storage/DASDTable.tsx b/web/src/components/storage/DASDTable.tsx index 0cae638849..f0e3a7150e 100644 --- a/web/src/components/storage/DASDTable.tsx +++ b/web/src/components/storage/DASDTable.tsx @@ -43,7 +43,7 @@ import { _ } from "~/i18n"; import { hex } from "~/utils"; import { sort } from "fast-sort"; import { DASDDevice } from "~/types/dasd"; -import { DASDDisable, DASDEnable, DASDFormat, diagDisable, diagEnable } from "~/api/dasd"; +import { disableDASD, disableDiag, enableDASD, enableDiag } from "~/api/dasd"; import { useDASDDevices, useFilterDASD, useFilterDASDChange, useSelectedDASD, useSelectedDASDChange } from "~/queries/dasd"; // FIXME: please, note that this file still requiring refinements until reach a @@ -88,10 +88,10 @@ const Actions = ({ devices, isDisabled }: { devices: DASDDevice[], isDisabled: b const onSelect = () => setIsOpen(false); const deviceIds = devices.map((d) => d.id); - const activate = () => DASDEnable(deviceIds); - const deactivate = () => DASDDisable(deviceIds); - const setDiagOn = () => diagEnable(deviceIds); - const setDiagOff = () => diagDisable(deviceIds); + const activate = () => enableDASD(deviceIds); + const deactivate = () => disableDASD(deviceIds); + const setDiagOn = () => enableDiag(deviceIds); + const setDiagOff = () => disableDiag(deviceIds); const format = () => { const offline = devices.filter((d) => !d.enabled); @@ -99,7 +99,7 @@ const Actions = ({ devices, isDisabled }: { devices: DASDDevice[], isDisabled: b return false; } - return DASDFormat(devices.map((d) => d.id)); + return formatDASD(devices.map((d) => d.id)); }; const Action = ({ children, ...props }) => ( diff --git a/web/src/queries/dasd.ts b/web/src/queries/dasd.ts index 999647dc94..93cf1f5390 100644 --- a/web/src/queries/dasd.ts +++ b/web/src/queries/dasd.ts @@ -22,9 +22,10 @@ import { useMutation, useQuery, useQueryClient, useSuspenseQuery } from "@tanstack/react-query"; import { _ } from "~/i18n"; import { - DASDDisable, - DASDEnable, - diagEnable, + disableDASD, + disableDiag, + enableDASD, + enableDiag, fetchDASDDevices, } from "~/api/dasd"; import { useInstallerClient } from "~/context/installer"; @@ -233,7 +234,7 @@ const useDASDDevicesChanges = () => { const useDASDEnableMutation = () => { const queryClient = useQueryClient(); const query = { - mutationFn: DASDEnable, + mutationFn: enableDASD, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["dasd", "devices"] }); }, @@ -244,7 +245,7 @@ const useDASDEnableMutation = () => { const useDASDDisableMutation = () => { const queryClient = useQueryClient(); const query = { - mutationFn: DASDDisable, + mutationFn: disableDASD, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["dasd", "devices"] }); }, @@ -255,7 +256,7 @@ const useDASDDisableMutation = () => { const useDiagEnableMutation = () => { const queryClient = useQueryClient(); const query = { - mutationFn: diagEnable, + mutationFn: enableDiag, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["dasd", "devices"] }); }, @@ -266,7 +267,7 @@ const useDiagEnableMutation = () => { const useDiagDisableMutation = () => { const queryClient = useQueryClient(); const query = { - mutationFn: diagEnable, + mutationFn: disableDiag, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["dasd", "devices"] }); }, From 6b97d1d4e9d554b254eef1408bd86609c280f611 Mon Sep 17 00:00:00 2001 From: Knut Anderssen Date: Thu, 29 Aug 2024 09:19:57 +0100 Subject: [PATCH 32/54] Remove not needed code from DASD Page --- web/src/components/storage/DASDPage.tsx | 70 +------------------------ 1 file changed, 2 insertions(+), 68 deletions(-) diff --git a/web/src/components/storage/DASDPage.tsx b/web/src/components/storage/DASDPage.tsx index 34a68ef6d5..5ed2b7dbbb 100644 --- a/web/src/components/storage/DASDPage.tsx +++ b/web/src/components/storage/DASDPage.tsx @@ -19,80 +19,14 @@ * find current contact information at www.suse.com. */ -import React, { useEffect, useReducer } from "react"; +import React from "react"; import DASDTable from "~/components/storage/DASDTable"; import DASDFormatProgress from "~/components/storage/DASDFormatProgress"; import { _ } from "~/i18n"; -import { hex, useCancellablePromise } from "~/utils"; -import { useInstallerClient } from "~/context/installer"; import { Page } from "~/components/core"; -import { useDASDDevices, useDASDDevicesChanges, useDASDFormatJobChanges, useDASDFormatJobs } from "~/queries/dasd"; +import { useDASDDevices, useDASDDevicesChanges, useDASDFormatJobs } from "~/queries/dasd"; import { DASDDevice } from "~/types/dasd"; -const reducer = (state, action) => { - const { type, payload } = action; - - switch (type) { - case "SET_DEVICES": { - return { ...state, devices: payload.devices }; - } - - case "ADD_DEVICE": { - const { device } = payload; - if (state.devices.find((d) => d.id === device.id)) return state; - - return { ...state, devices: [...state.devices, device] }; - } - - case "UPDATE_DEVICE": { - const { device } = payload; - const index = state.devices.findIndex((d) => d.id === device.id); - const devices = [...state.devices]; - index !== -1 ? (devices[index] = device) : devices.push(device); - - const selectedDevicesIds = state.selectedDevices.map((d) => d.id); - const selectedDevices = devices.filter((d) => selectedDevicesIds.includes(d.id)); - - return { ...state, devices, selectedDevices }; - } - - case "REMOVE_DEVICE": { - const { device } = payload; - - return { ...state, devices: state.devices.filter((d) => d.id !== device.id) }; - } - - case "START_FORMAT_JOB": { - const { data: formatJob } = payload; - - if (!formatJob.running) return state; - const newState = { ...state, formatJob }; - - return newState; - } - - case "UPDATE_FORMAT_JOB": { - const { data: formatJob } = payload; - - if (formatJob.path !== state.formatJob.path) return state; - - return { ...state, formatJob }; - } - - case "START_LOADING": { - return { ...state, isLoading: true }; - } - - case "STOP_LOADING": { - return { ...state, isLoading: false }; - } - - default: { - return state; - } - } -}; - export default function DASDPage() { useDASDDevicesChanges(); const jobs = useDASDFormatJobs().filter((j) => j.running); From 1a3d485e80421ec10b96219e761e0889a1d18be9 Mon Sep 17 00:00:00 2001 From: Josef Reidinger Date: Thu, 29 Aug 2024 10:58:58 +0200 Subject: [PATCH 33/54] fix dasd table test --- web/src/components/storage/DASDTable.test.tsx | 6 +++--- web/src/components/storage/DASDTable.tsx | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/web/src/components/storage/DASDTable.test.tsx b/web/src/components/storage/DASDTable.test.tsx index f62fb50ec5..62df1b8888 100644 --- a/web/src/components/storage/DASDTable.test.tsx +++ b/web/src/components/storage/DASDTable.test.tsx @@ -25,9 +25,9 @@ import { installerRender } from "~/test-utils"; import DASDTable from "~/components/storage/DASDTable"; import { DASDDevice, FilterDASD } from "~/types/dasd"; -let mockDASDDevices: DASDDevice[]; -let mockDASDFilter: { minChannel: string, maxChannel: string }; -let mockSelectedDASD: { minChannel: string, maxChannel: string }; +let mockDASDDevices: DASDDevice[] = []; +const mockDASDFilter = { minChannel: "", maxChannel: "" }; +const mockSelectedDASD: string[] = []; jest.mock("~/queries/dasd", () => ({ useDASDDevices: () => mockDASDDevices, diff --git a/web/src/components/storage/DASDTable.tsx b/web/src/components/storage/DASDTable.tsx index f0e3a7150e..8a2a952ce5 100644 --- a/web/src/components/storage/DASDTable.tsx +++ b/web/src/components/storage/DASDTable.tsx @@ -43,7 +43,7 @@ import { _ } from "~/i18n"; import { hex } from "~/utils"; import { sort } from "fast-sort"; import { DASDDevice } from "~/types/dasd"; -import { disableDASD, disableDiag, enableDASD, enableDiag } from "~/api/dasd"; +import { disableDASD, disableDiag, enableDASD, enableDiag, formatDASD } from "~/api/dasd"; import { useDASDDevices, useFilterDASD, useFilterDASDChange, useSelectedDASD, useSelectedDASDChange } from "~/queries/dasd"; // FIXME: please, note that this file still requiring refinements until reach a From a6cb882cc929489ae266a4f5ea8a48acb13c637f Mon Sep 17 00:00:00 2001 From: Knut Anderssen Date: Thu, 29 Aug 2024 13:10:11 +0100 Subject: [PATCH 34/54] Improve DASDFormat progress dialog --- web/src/client/storage.js | 4 - .../components/storage/DASDFormatProgress.tsx | 69 +++++------ web/src/components/storage/DASDPage.tsx | 12 +- web/src/components/storage/DASDTable.tsx | 4 +- web/src/queries/dasd.ts | 115 ++++++------------ 5 files changed, 75 insertions(+), 129 deletions(-) diff --git a/web/src/client/storage.js b/web/src/client/storage.js index b6a875c44a..ed594c118a 100644 --- a/web/src/client/storage.js +++ b/web/src/client/storage.js @@ -31,10 +31,6 @@ const STORAGE_OBJECT = "/org/opensuse/Agama/Storage1"; const STORAGE_JOBS_NAMESPACE = "/org/opensuse/Agama/Storage1/jobs"; const STORAGE_JOB_IFACE = "org.opensuse.Agama.Storage1.Job"; const ISCSI_NODES_NAMESPACE = "/storage/iscsi/nodes"; -const DASD_MANAGER_IFACE = "org.opensuse.Agama.Storage1.DASD.Manager"; -const DASD_DEVICES_NAMESPACE = "/org/opensuse/Agama/Storage1/dasds"; -const DASD_DEVICE_IFACE = "org.opensuse.Agama.Storage1.DASD.Device"; -const DASD_STATUS_IFACE = "org.opensuse.Agama.Storage1.DASD.Format"; const ZFCP_MANAGER_IFACE = "org.opensuse.Agama.Storage1.ZFCP.Manager"; const ZFCP_CONTROLLERS_NAMESPACE = "/org/opensuse/Agama/Storage1/zfcp_controllers"; const ZFCP_CONTROLLER_IFACE = "org.opensuse.Agama.Storage1.ZFCP.Controller"; diff --git a/web/src/components/storage/DASDFormatProgress.tsx b/web/src/components/storage/DASDFormatProgress.tsx index 2bc39633ee..c94609b427 100644 --- a/web/src/components/storage/DASDFormatProgress.tsx +++ b/web/src/components/storage/DASDFormatProgress.tsx @@ -20,49 +20,46 @@ */ import React from "react"; -import { Progress, Skeleton, Stack } from "@patternfly/react-core"; +import { Progress, Stack } from "@patternfly/react-core"; import { Popup } from "~/components/core"; import { _ } from "~/i18n"; -import { useDASDFormatJobChanges } from "~/queries/dasd"; -import { FormatJob, FormatSummary } from "~/types/dasd"; +import { useDASDDevices, useDASDRunningFormatJobs } from "~/queries/dasd"; +import { FormatSummary } from "~/types/dasd"; -export default function DASDFormatProgress({ job, devices, isOpen = true }) { - const formatJob: FormatJob = useDASDFormatJobChanges(job.id); - const progress = formatJob?.summary || {}; +export default function DASDFormatProgress() { + const devices = useDASDDevices(); + const runningJobs = useDASDRunningFormatJobs(); - const ProgressContent = ({ progress } : { progress: { [key: string]: FormatSummary }}) => { - return ( - - {Object.entries(progress).map(([id, { total, step, done }]) => { - const device = devices.find((d) => d.id === id); + const ProgressContent = ({ progress }: { progress: { [key: string]: FormatSummary } }) => ( + Object.entries(progress).map(([id, { total, step, done }]) => { + const device = devices.find((d) => d.id === id); - return ( - - ); - })} - - ); - }; - - const WaitingProgress = () => ( - -
{_("Waiting for progress report")}
- - -
- ); + return ( + + + + ); + })); return ( - - {Object.keys(progress).length !== 0 ? : } + 0} disableFocusTrap> + {runningJobs.map((job) => { + const progress = job.summary || {}; + + if (Object.keys(progress).length === 0) return; + + return ( + + ); + })} ); } diff --git a/web/src/components/storage/DASDPage.tsx b/web/src/components/storage/DASDPage.tsx index 5ed2b7dbbb..28a77e13aa 100644 --- a/web/src/components/storage/DASDPage.tsx +++ b/web/src/components/storage/DASDPage.tsx @@ -24,15 +24,11 @@ import DASDTable from "~/components/storage/DASDTable"; import DASDFormatProgress from "~/components/storage/DASDFormatProgress"; import { _ } from "~/i18n"; import { Page } from "~/components/core"; -import { useDASDDevices, useDASDDevicesChanges, useDASDFormatJobs } from "~/queries/dasd"; -import { DASDDevice } from "~/types/dasd"; +import { useDASDDevicesChanges, useDASDFormatJobChanges } from "~/queries/dasd"; export default function DASDPage() { useDASDDevicesChanges(); - const jobs = useDASDFormatJobs().filter((j) => j.running); - const job = jobs[0]; - - const devices: DASDDevice[] = useDASDDevices(); + useDASDFormatJobChanges(); return ( @@ -42,9 +38,7 @@ export default function DASDPage() { - {job && ( - - )} + ); diff --git a/web/src/components/storage/DASDTable.tsx b/web/src/components/storage/DASDTable.tsx index 8a2a952ce5..3ba6a0be39 100644 --- a/web/src/components/storage/DASDTable.tsx +++ b/web/src/components/storage/DASDTable.tsx @@ -218,7 +218,7 @@ export default function DASDTable() { - {sortedDevices.map((device, rowIndex) => ( -
selectAll(isSelecting), isSelected: filteredDevices.length === selectedDevices.length, @@ -234,7 +234,7 @@ export default function DASDTable() {
selectDevice(device, isSelecting), diff --git a/web/src/queries/dasd.ts b/web/src/queries/dasd.ts index 968de1dda1..213328adc8 100644 --- a/web/src/queries/dasd.ts +++ b/web/src/queries/dasd.ts @@ -51,41 +51,27 @@ const useDASDDevices = () => { }; /** - * Returns a query for retrieving DASD format jobs + * Returns a query for retrieving the running dasd format jobs */ -const DASDFormatJobsQuery = () => ({ - queryKey: ["dasd", "formatJobs"], - queryFn: () => fetchStorageJobs(), -}); - -/** - * Hook that returns DASD format jobs. - */ -const useDASDFormatJobs = () => { - const { data: jobs } = useSuspenseQuery(DASDFormatJobsQuery()); - return jobs; -}; - -/** - * Returns a query for retrieving the dasd format job - */ -const DASDFormatJobQuery = (id: string) => ({ - queryKey: ["dasd", "formatJob", id], - queryFn: () => findStorageJob(id).then((sj) => ({jobId: sj.id})), +const DASDRunningFormatJobsQuery = () => ({ + queryKey: ["dasd", "formatJobs", "running"], + queryFn: () => fetchStorageJobs().then((jobs) => jobs.filter((j) => j.running).map(({ id }) => ({ jobId: id }))), + staleTime: 200 }); /** * Hook that returns and specific DASD format job. */ -const useDASDFormatJob = (id: string) : FormatJob => { - const { data: job } = useSuspenseQuery(DASDFormatJobQuery(id)); - return job; +const useDASDRunningFormatJobs = (): FormatJob[] => { + const { data: jobs } = useSuspenseQuery(DASDRunningFormatJobsQuery()); + + return jobs; }; /** * Listens for DASD format job changes. */ -const useDASDFormatJobChanges = (id: string) : FormatJob => { +const useDASDFormatJobChanges = () => { const client = useInstallerClient(); const queryClient = useQueryClient(); @@ -95,19 +81,36 @@ const useDASDFormatJobChanges = (id: string) : FormatJob => { return client.ws().onEvent((event) => { // TODO: for simplicity we now just invalidate query instead of manually adding, removing or changing devices if ( - event.type === "DASDFormatJobChanged" && event.jobId === id + event.type === "DASDFormatJobChanged" ) { - const data = queryClient.getQueryData(["dasd", "formatJob", id]) as FormatJob; - const merged_summary = { ...data.summary, ...event.summary }; - const job = { ...data, summary: merged_summary }; - queryClient.setQueryData(["dasd", "formatJob", id], job); + const data = queryClient.getQueryData(["dasd", "formatJobs", "running"]) as FormatJob[]; + const nextData = data.map((job) => { + if (job.jobId !== event.jobId) return job; + + return { + ...job, + summary: { ...job?.summary, ...event.summary } + } + }); + queryClient.setQueryData(["dasd", "formatJobs", "running"], nextData); } }); }); - const { data: formatJob } = useSuspenseQuery(DASDFormatJobQuery(id)); - return formatJob; + + const { data: jobs } = useSuspenseQuery(DASDRunningFormatJobsQuery()); + return jobs; }; +const useFormatDASDMutation = () => { + const queryClient = useQueryClient(); + + const mutation = useMutation({ + mutationFn: async (data: FormatJob): Promise => Promise.resolve(data), + onSuccess: (data: FormatJob) => queryClient.setQueryData(["dasd", "formatJob", data.jobId], data) + }); + + return mutation; +}; /** * Returns seleced DASD ids */ @@ -222,56 +225,12 @@ const useDASDDevicesChanges = () => { } }); }); + const { data: devices } = useSuspenseQuery(DASDDevicesQuery()); return devices; }; -const useDASDEnableMutation = () => { - const queryClient = useQueryClient(); - const query = { - mutationFn: enableDASD, - onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ["dasd", "devices"] }); - }, - }; - return useMutation(query); -}; - -const useDASDDisableMutation = () => { - const queryClient = useQueryClient(); - const query = { - mutationFn: disableDASD, - onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ["dasd", "devices"] }); - }, - }; - return useMutation(query); -}; - -const useDiagEnableMutation = () => { - const queryClient = useQueryClient(); - const query = { - mutationFn: enableDiag, - onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ["dasd", "devices"] }); - }, - }; - return useMutation(query); -}; - -const useDiagDisableMutation = () => { - const queryClient = useQueryClient(); - const query = { - mutationFn: disableDiag, - onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ["dasd", "devices"] }); - }, - }; - return useMutation(query); -}; - export { - useDASDDevices, useDASDDevicesChanges, useDASDEnableMutation, useDASDDisableMutation, useDiagDisableMutation, - useDiagEnableMutation, useFilterDASDChange, filterDASDQuery, useFilterDASD, useSelectedDASD, useSelectedDASDChange, selectedDASDQuery, - useDASDFormatJobChanges, useDASDFormatJob, useDASDFormatJobs + useDASDDevices, useDASDDevicesChanges, useFilterDASDChange, filterDASDQuery, useFilterDASD, useSelectedDASD, useSelectedDASDChange, selectedDASDQuery, + useDASDFormatJobChanges, useDASDRunningFormatJobs, useFormatDASDMutation }; From df7ca948de466023cc01db015290330741da5ccd Mon Sep 17 00:00:00 2001 From: Knut Anderssen Date: Thu, 29 Aug 2024 13:56:51 +0100 Subject: [PATCH 35/54] Notify id in Jobs events --- rust/agama-server/src/web/common/jobs.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/rust/agama-server/src/web/common/jobs.rs b/rust/agama-server/src/web/common/jobs.rs index 1e7484ebf2..8dbbd9df20 100644 --- a/rust/agama-server/src/web/common/jobs.rs +++ b/rust/agama-server/src/web/common/jobs.rs @@ -121,6 +121,7 @@ impl JobsStream { values: &HashMap, ) -> Result<&'a Job, ServiceError> { let job = cache.find_or_create(path); + job.id = path.to_string(); property_from_dbus!(job, running, "Running", values, bool); property_from_dbus!(job, exit_code, "ExitCode", values, u32); Ok(job) From 7ddbba59a45318f0003fe403b068b425d3827c11 Mon Sep 17 00:00:00 2001 From: Knut Anderssen Date: Thu, 29 Aug 2024 14:10:39 +0100 Subject: [PATCH 36/54] Handle JobAdded event --- web/src/queries/dasd.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/web/src/queries/dasd.ts b/web/src/queries/dasd.ts index 213328adc8..207e007247 100644 --- a/web/src/queries/dasd.ts +++ b/web/src/queries/dasd.ts @@ -94,6 +94,15 @@ const useDASDFormatJobChanges = () => { }); queryClient.setQueryData(["dasd", "formatJobs", "running"], nextData); } + if ( + event.type === "JobAdded" + ) { + const formatJob: FormatJob = { jobId: event.job.id } + let data = queryClient.getQueryData(["dasd", "formatJobs", "running"]) as FormatJob[]; + data.push(formatJob); + + queryClient.setQueryData(["dasd", "formatJobs", "running"], data); + } }); }); From f585806720fdd17184a0ba0b576dd04403427b70 Mon Sep 17 00:00:00 2001 From: Knut Anderssen Date: Thu, 29 Aug 2024 14:58:57 +0100 Subject: [PATCH 37/54] Remove running jobs when finish --- web/src/queries/dasd.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/web/src/queries/dasd.ts b/web/src/queries/dasd.ts index 207e007247..b2841c74db 100644 --- a/web/src/queries/dasd.ts +++ b/web/src/queries/dasd.ts @@ -103,6 +103,17 @@ const useDASDFormatJobChanges = () => { queryClient.setQueryData(["dasd", "formatJobs", "running"], data); } + if ( + event.type === "JobChanged" + ) { + const { id, running } = event.job; + if (running) return; + let data = queryClient.getQueryData(["dasd", "formatJobs", "running"]) as FormatJob[]; + const nextData = data.filter((j) => j.jobId !== id); + if (data.length !== nextData.length) { + queryClient.setQueryData(["dasd", "formatJobs", "running"], nextData); + } + } }); }); From b028a3bee71c962cd7733fdff9ea73045e2295e5 Mon Sep 17 00:00:00 2001 From: Knut Anderssen Date: Mon, 2 Sep 2024 10:44:30 +0100 Subject: [PATCH 38/54] Fix DASDFormatProgress unit test --- .../storage/DASDFormatProgress.test.tsx | 66 +++++++++---------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/web/src/components/storage/DASDFormatProgress.test.tsx b/web/src/components/storage/DASDFormatProgress.test.tsx index 5855c09e36..9d31836dde 100644 --- a/web/src/components/storage/DASDFormatProgress.test.tsx +++ b/web/src/components/storage/DASDFormatProgress.test.tsx @@ -20,37 +20,35 @@ */ import React from "react"; -import { act, screen } from "@testing-library/react"; -import { installerRender } from "~/test-utils"; -import DASDTable from "~/components/storage/DASDTable"; +import { screen } from "@testing-library/react"; +import { installerRender, plainRender } from "~/test-utils"; import DASDFormatProgress from "./DASDFormatProgress"; -import { FormatJob } from "~/types/dasd"; +import { DASDDevice, FormatJob } from "~/types/dasd"; -let mockDASDFormatJob : FormatJob; +let mockDASDFormatJobs: FormatJob[]; +let mockDASDDevices: DASDDevice[]; jest.mock("~/queries/dasd", () => ({ - useDASDFormatJobChanges: () => mockDASDFormatJob, + useDASDRunningFormatJobs: () => mockDASDFormatJobs, + useDASDDevices: () => mockDASDDevices, })); describe("DASDFormatProgress", () => { describe("when there is already some progress", () => { beforeEach(() => { - mockDASDFormatJob = - { - jobId: "0.0.0200", - summary: { - "0.0.0200": { - total: 5, - step: 1, - done: false, - }, + mockDASDFormatJobs = + [{ + jobId: "0.0.0200", + summary: { + "0.0.0200": { + total: 5, + step: 1, + done: false, }, - } - ; - }); + }, + }]; - it("renders the progress", () => { - const devices = [{ + mockDASDDevices = [{ id: "0.0.0200", enabled: false, deviceName: "dasda", @@ -62,22 +60,21 @@ describe("DASDFormatProgress", () => { partitionInfo: "1", hexId: 0x200, }]; - installerRender(); + ; + }); + + it("renders the progress", () => { + installerRender(); + expect(screen.queryByRole("progressbar")).toBeInTheDocument(); screen.getByText("0.0.0200 - dasda"); }); }); - describe("when there is no progress yet", () => { + describe("when there are no running jobs", () => { beforeEach(() => { - mockDASDFormatJob = - { - jobId: "0.0.0200", - } - ; - }); + mockDASDFormatJobs = []; - it("renders the progress", () => { - const devices = [{ + mockDASDDevices = [{ id: "0.0.0200", enabled: false, deviceName: "dasda", @@ -89,8 +86,11 @@ describe("DASDFormatProgress", () => { partitionInfo: "1", hexId: 0x200, }]; - installerRender(); - screen.getByText("Waiting for progress report"); + }); + + it("does not render any progress", () => { + installerRender(); + expect(screen.queryByRole("progressbar")).not.toBeInTheDocument(); }); }); -}); \ No newline at end of file +}); From 9fd45440a78090a7a2e59c7f287da4791abdd23a Mon Sep 17 00:00:00 2001 From: Knut Anderssen Date: Mon, 2 Sep 2024 11:51:49 +0100 Subject: [PATCH 39/54] Removed leftover Deviceinfo --- web/src/client/storage.test.js | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/web/src/client/storage.test.js b/web/src/client/storage.test.js index 58fc4b4968..813c3a54ba 100644 --- a/web/src/client/storage.test.js +++ b/web/src/client/storage.test.js @@ -930,18 +930,6 @@ const contexts = { wires: ["/dev/sdd", "/dev/sde"], }, }, - { - blockDevice: { - active: true, - encrypted: false, - size: 2048, - start: 0, - shrinking: { unsupported: ["Resizing is not supported"] }, - systems: [], - udevIds: [], - udevPaths: [], - }, - }, { deviceInfo: { sid: 70, From dbdf72274f14e3da0c6e1756377634e342488cbf Mon Sep 17 00:00:00 2001 From: Knut Anderssen Date: Mon, 2 Sep 2024 15:59:07 +0100 Subject: [PATCH 40/54] Changes based on CR --- web/src/api/dasd.ts | 51 +++++++++++++++++++++----------- web/src/queries/dasd.ts | 65 +++++++++++++++++++++++------------------ 2 files changed, 70 insertions(+), 46 deletions(-) diff --git a/web/src/api/dasd.ts b/web/src/api/dasd.ts index befac775dc..4178b3fb18 100644 --- a/web/src/api/dasd.ts +++ b/web/src/api/dasd.ts @@ -38,35 +38,52 @@ const DASDSupported = (): Promise => get("/api/storage/dasd/supported") const probeDASD = () => post("/api/storage/dasd/probe"); /** - * Start format job for given list of devices - * @param devicesIDs array of device ids + * Start format job for given list of DASD devices + * @param devicesIDs - array of DASD device ids * @return id of format job */ -const formatDASD = (devicesIDs: string[]): Promise => post("/api/storage/dasd/format", { devices: devicesIDs }).then(({ data }) => data); +const formatDASD = (devicesIDs: string[]): Promise => + post("/api/storage/dasd/format", { devices: devicesIDs }).then(({ data }) => data); /** - * Enables given list of devices - * @param devicesIDs array of device ids + * Enable given list of DASD devices + * + * @param devicesIDs - array of DASD device ids */ -const enableDASD = (devicesIDs: string[]) => post("/api/storage/dasd/enable", { devices: devicesIDs }); +const enableDASD = (devicesIDs: string[]) => + post("/api/storage/dasd/enable", { devices: devicesIDs }); /** - * Enables given list of devices - * @param devicesIDs array of device ids + * Disable given list of DASD devices + * + * @param devicesIDs - array of DASD device ids */ -const disableDASD = (devicesIDs: string[]) => post("/api/storage/dasd/disable", { devices: devicesIDs }); +const disableDASD = (devicesIDs: string[]) => + post("/api/storage/dasd/disable", { devices: devicesIDs }); /** - * Enables diag on given list of devices - * @param devicesIDs array of device ids + * Enables diag on given list of DASD devices + * + * @param devicesIDs - array of DASD device ids */ -const enableDiag = (devicesIDs: string[]) => put("/api/storage/dasd/diag", { devices: devicesIDs, diag: true }); +const enableDiag = (devicesIDs: string[]) => + put("/api/storage/dasd/diag", { devices: devicesIDs, diag: true }); /** - * Disables diag on given list of devices - * @param devicesIDs array of device ids + * Disables diag on given list of DASD devices + * + * @param devicesIDs - array of DASD device ids */ -const disableDiag = (devicesIDs: string[]) => put("/api/storage/dasd/diag", { devices: devicesIDs, diag: false }); - +const disableDiag = (devicesIDs: string[]) => + put("/api/storage/dasd/diag", { devices: devicesIDs, diag: false }); -export { fetchDASDDevices, DASDSupported, formatDASD, probeDASD, enableDASD, disableDASD, enableDiag, disableDiag }; +export { + fetchDASDDevices, + DASDSupported, + formatDASD, + probeDASD, + enableDASD, + disableDASD, + enableDiag, + disableDiag, +}; diff --git a/web/src/queries/dasd.ts b/web/src/queries/dasd.ts index b2841c74db..38d5587119 100644 --- a/web/src/queries/dasd.ts +++ b/web/src/queries/dasd.ts @@ -125,7 +125,7 @@ const useFormatDASDMutation = () => { const queryClient = useQueryClient(); const mutation = useMutation({ - mutationFn: async (data: FormatJob): Promise => Promise.resolve(data), + mutationFn: (data: FormatJob): Promise => Promise.resolve(data), onSuccess: (data: FormatJob) => queryClient.setQueryData(["dasd", "formatJob", data.jobId], data) }); @@ -136,7 +136,7 @@ const useFormatDASDMutation = () => { */ const selectedDASDQuery = () => ({ queryKey: ["dasd", "selected"], - queryFn: async () => { + queryFn: () => { return Promise.resolve([]); }, staleTime: Infinity, @@ -158,7 +158,7 @@ const useSelectedDASDChange = () => { const queryClient = useQueryClient(); const mutation = useMutation({ - mutationFn: async (data: SelectDASD): Promise => Promise.resolve(data), + mutationFn: (data: SelectDASD): Promise => Promise.resolve(data), onSuccess: (data: SelectDASD) => queryClient.setQueryData(["dasd", "selected"], (prev: DASDDevice[]) => { if (data.unselect) { if (data.device) return prev.filter((d) => d.id !== data.device.id); @@ -179,7 +179,7 @@ const useSelectedDASDChange = () => { */ const filterDASDQuery = () => ({ queryKey: ["dasd", "filter"], - queryFn: async () => { + queryFn: () => { return Promise.resolve({ minChannel: "", maxChannel: "" }); }, staleTime: Infinity, @@ -195,7 +195,7 @@ const useFilterDASDChange = () => { const queryClient = useQueryClient(); const mutation = useMutation({ - mutationFn: async (data: FilterDASD): Promise => Promise.resolve(data), + mutationFn: (data: FilterDASD): Promise => Promise.resolve(data), onSuccess: (data: FilterDASD) => { queryClient.setQueryData(["dasd", "filter"], (prev: FilterDASD) => ({ ...prev, @@ -218,30 +218,37 @@ const useDASDDevicesChanges = () => { if (!client) return; return client.ws().onEvent((event) => { - if (event.type === "DASDDeviceAdded") { - const device: DASDDevice = event.device; - queryClient.setQueryData(["dasd", "devices"], (prev: DASDDevice[]) => { - // do not use push here as updater has to be immutable - const res = prev.concat([device]); - return res; - }); - } else if (event.type === "DASDDeviceRemoved") { - const device: DASDDevice = event.device; - const { id } = device; - queryClient.setQueryData(["dasd", "devices"], (prev: DASDDevice[]) => { - const res = prev.filter(dev => dev.id !== id); - return res; - }); - } else if (event.type === "DASDDeviceChanged") { - const device: DASDDevice = event.device; - const { id } = device; - queryClient.setQueryData(["dasd", "devices"], (prev: DASDDevice[]) => { - // deep copy of original to have it immutable - const res = [...prev]; - const index = res.findIndex(dev => dev.id === id); - res[index] = device; - return res; - }); + switch (event.type) { + case "DASDDeviceAdded": { + const device: DASDDevice = event.device; + queryClient.setQueryData(["dasd", "devices"], (prev: DASDDevice[]) => { + // do not use push here as updater has to be immutable + const res = prev.concat([device]); + return res; + }); + break; + } + case "DASDDeviceRemoved": { + const device: DASDDevice = event.device; + const { id } = device; + queryClient.setQueryData(["dasd", "devices"], (prev: DASDDevice[]) => { + const res = prev.filter(dev => dev.id !== id); + return res; + }); + break; + } + case "DASDDeviceChanged": { + const device: DASDDevice = event.device; + const { id } = device; + queryClient.setQueryData(["dasd", "devices"], (prev: DASDDevice[]) => { + // deep copy of original to have it immutable + const res = [...prev]; + const index = res.findIndex(dev => dev.id === id); + res[index] = device; + return res; + }); + break; + } } }); }); From 8bbf57bdd2ccfe80c8427d991bccd44b37e9cf85 Mon Sep 17 00:00:00 2001 From: Knut Anderssen Date: Tue, 3 Sep 2024 11:03:20 +0100 Subject: [PATCH 41/54] Changes based on code review --- web/src/api/storage.ts | 10 +-- .../storage/DASDFormatProgress.test.tsx | 60 +++++++++-------- .../components/storage/DASDFormatProgress.tsx | 40 ++++++------ web/src/components/storage/DASDTable.test.tsx | 33 +++++----- web/src/components/storage/DASDTable.tsx | 38 ++++++----- web/src/queries/dasd.ts | 65 +++++++++---------- 6 files changed, 126 insertions(+), 120 deletions(-) diff --git a/web/src/api/storage.ts b/web/src/api/storage.ts index a8efb84896..47c6cad15b 100644 --- a/web/src/api/storage.ts +++ b/web/src/api/storage.ts @@ -25,16 +25,12 @@ import { Job } from "~/types/job"; /** * Returns the list of jobs */ -const fetchStorageJobs = async (): Promise => { - const jobs: Job[] = await get("/api/storage/jobs"); - return jobs; -}; +const fetchStorageJobs = (): Promise => get("/api/storage/jobs"); /** * Returns the job with given id or undefined */ -const findStorageJob = async (id: string): Promise => { - return fetchStorageJobs().then((jobs: Job[]) => jobs.find((value, _i, _o) => value.id === id)); -}; +const findStorageJob = (id: string): Promise => + fetchStorageJobs().then((jobs: Job[]) => jobs.find((value) => value.id === id)); export { fetchStorageJobs, findStorageJob }; diff --git a/web/src/components/storage/DASDFormatProgress.test.tsx b/web/src/components/storage/DASDFormatProgress.test.tsx index 9d31836dde..730665d934 100644 --- a/web/src/components/storage/DASDFormatProgress.test.tsx +++ b/web/src/components/storage/DASDFormatProgress.test.tsx @@ -36,8 +36,8 @@ jest.mock("~/queries/dasd", () => ({ describe("DASDFormatProgress", () => { describe("when there is already some progress", () => { beforeEach(() => { - mockDASDFormatJobs = - [{ + mockDASDFormatJobs = [ + { jobId: "0.0.0200", summary: { "0.0.0200": { @@ -46,21 +46,23 @@ describe("DASDFormatProgress", () => { done: false, }, }, - }]; + }, + ]; - mockDASDDevices = [{ - id: "0.0.0200", - enabled: false, - deviceName: "dasda", - deviceType: "eckd", - formatted: false, - diag: false, - status: "active", - accessType: "rw", - partitionInfo: "1", - hexId: 0x200, - }]; - ; + mockDASDDevices = [ + { + id: "0.0.0200", + enabled: false, + deviceName: "dasda", + deviceType: "eckd", + formatted: false, + diag: false, + status: "active", + accessType: "rw", + partitionInfo: "1", + hexId: 0x200, + }, + ]; }); it("renders the progress", () => { @@ -74,18 +76,20 @@ describe("DASDFormatProgress", () => { beforeEach(() => { mockDASDFormatJobs = []; - mockDASDDevices = [{ - id: "0.0.0200", - enabled: false, - deviceName: "dasda", - deviceType: "eckd", - formatted: false, - diag: false, - status: "active", - accessType: "rw", - partitionInfo: "1", - hexId: 0x200, - }]; + mockDASDDevices = [ + { + id: "0.0.0200", + enabled: false, + deviceName: "dasda", + deviceType: "eckd", + formatted: false, + diag: false, + status: "active", + accessType: "rw", + partitionInfo: "1", + hexId: 0x200, + }, + ]; }); it("does not render any progress", () => { diff --git a/web/src/components/storage/DASDFormatProgress.tsx b/web/src/components/storage/DASDFormatProgress.tsx index c94609b427..61f2b4e6da 100644 --- a/web/src/components/storage/DASDFormatProgress.tsx +++ b/web/src/components/storage/DASDFormatProgress.tsx @@ -28,36 +28,38 @@ import { FormatSummary } from "~/types/dasd"; export default function DASDFormatProgress() { const devices = useDASDDevices(); - const runningJobs = useDASDRunningFormatJobs(); + const runningJobs = useDASDRunningFormatJobs().filter( + (job) => Object.keys(job.summary || {}).length > 0, + ); - const ProgressContent = ({ progress }: { progress: { [key: string]: FormatSummary } }) => ( + const ProgressContent = ({ progress }: { progress: { [key: string]: FormatSummary } }) => Object.entries(progress).map(([id, { total, step, done }]) => { const device = devices.find((d) => d.id === id); return ( - - - + ); - })); + }); return ( 0} disableFocusTrap> {runningJobs.map((job) => { - const progress = job.summary || {}; - - if (Object.keys(progress).length === 0) return; - return ( - + + + ); })} diff --git a/web/src/components/storage/DASDTable.test.tsx b/web/src/components/storage/DASDTable.test.tsx index 62df1b8888..2e2c7f8676 100644 --- a/web/src/components/storage/DASDTable.test.tsx +++ b/web/src/components/storage/DASDTable.test.tsx @@ -32,27 +32,30 @@ const mockSelectedDASD: string[] = []; jest.mock("~/queries/dasd", () => ({ useDASDDevices: () => mockDASDDevices, useFilterDASD: () => mockDASDFilter, - useFilterDASDChange: () => jest.fn().mockImplementation(() => ({ mutate: (data: FilterDASD) => ({data: mockDASDFilter}) })), + useFilterDASDChange: () => + jest + .fn() + .mockImplementation(() => ({ mutate: (data: FilterDASD) => ({ data: mockDASDFilter }) })), useSelectedDASD: () => mockSelectedDASD, - useSelectedDASDChange: () => jest.fn().mockImplementation(()=> ({ data: mockSelectedDASD })), + useSelectedDASDChange: () => jest.fn().mockImplementation(() => ({ data: mockSelectedDASD })), })); describe("DASDTable", () => { describe("when there is some DASD devices available", () => { beforeEach(() => { mockDASDDevices = [ - { - id: "0.0.0200", - enabled: false, - deviceName: "dasda", - deviceType: "eckd", - formatted: false, - diag: false, - status: "active", - accessType: "rw", - partitionInfo: "1", - hexId: 0x200, - } + { + id: "0.0.0200", + enabled: false, + deviceName: "dasda", + deviceType: "eckd", + formatted: false, + diag: false, + status: "active", + accessType: "rw", + partitionInfo: "1", + hexId: 0x200, + }, ]; }); @@ -61,4 +64,4 @@ describe("DASDTable", () => { screen.getByText("active"); }); }); -}); \ No newline at end of file +}); diff --git a/web/src/components/storage/DASDTable.tsx b/web/src/components/storage/DASDTable.tsx index 3ba6a0be39..80beae417e 100644 --- a/web/src/components/storage/DASDTable.tsx +++ b/web/src/components/storage/DASDTable.tsx @@ -44,11 +44,17 @@ import { hex } from "~/utils"; import { sort } from "fast-sort"; import { DASDDevice } from "~/types/dasd"; import { disableDASD, disableDiag, enableDASD, enableDiag, formatDASD } from "~/api/dasd"; -import { useDASDDevices, useFilterDASD, useFilterDASDChange, useSelectedDASD, useSelectedDASDChange } from "~/queries/dasd"; +import { + useDASDDevices, + useFilterDASD, + useFilterDASDMutation, + useSelectedDASD, + useSelectedDASDChange, +} from "~/queries/dasd"; // FIXME: please, note that this file still requiring refinements until reach a // reasonable stable version -const columnData = (device: DASDDevice, column: { id: string, sortId?: string, label: string }) => { +const columnData = (device: DASDDevice, column: { id: string; sortId?: string; label: string }) => { let data = device[column.id]; switch (column.id) { @@ -81,7 +87,7 @@ const columns = [ { id: "partitionInfo", label: _("Partition Info") }, ]; -const Actions = ({ devices, isDisabled }: { devices: DASDDevice[], isDisabled: boolean }) => { +const Actions = ({ devices, isDisabled }: { devices: DASDDevice[]; isDisabled: boolean }) => { const [isOpen, setIsOpen] = useState(false); const onToggle = () => setIsOpen(!isOpen); @@ -148,7 +154,7 @@ const Actions = ({ devices, isDisabled }: { devices: DASDDevice[], isDisabled: b }; const filterDevices = (devices: DASDDevice[], from: string, to: string): DASDDevice[] => { - const allChannels: number[] = devices.map((d) => d.hexId); + const allChannels = devices.map((d) => d.hexId); const min = hex(from) || Math.min(...allChannels); const max = hex(to) || Math.max(...allChannels); @@ -157,13 +163,13 @@ const filterDevices = (devices: DASDDevice[], from: string, to: string): DASDDev export default function DASDTable() { const devices = useDASDDevices(); - const { mutate: changeFilter } = useFilterDASDChange(); + const { mutate: changeFilter } = useFilterDASDMutation(); const { mutate: changeSelected } = useSelectedDASDChange(); const { minChannel, maxChannel } = useFilterDASD(); const selectedDevices = useSelectedDASD(); const [sortingColumn, setSortingColumn] = useState(columns[0]); - const [sortDirection, setSortDirection]: ["asc" | "desc", Dispatch>] = useState("asc"); + const [sortDirection, setSortDirection] = useState<"asc" | "desc">("asc"); const sortColumnIndex = () => columns.findIndex((c) => c.id === sortingColumn.id); const filteredDevices = filterDevices(devices, minChannel, maxChannel); @@ -171,11 +177,11 @@ export default function DASDTable() { // Selecting const selectAll = (isSelecting = true) => { - changeSelected({ unselect: !isSelecting, devices: filteredDevices }) + changeSelected({ unselect: !isSelecting, devices: filteredDevices }); }; const selectDevice = (device, isSelecting = true) => { - changeSelected({ unselect: !isSelecting, device }) + changeSelected({ unselect: !isSelecting, device }); }; // Sorting @@ -214,11 +220,11 @@ export default function DASDTable() { const Content = () => { return ( - - {sortedDevices.map((device, rowIndex) => ( -
selectAll(isSelecting), isSelected: filteredDevices.length === selectedDevices.length, @@ -234,7 +240,8 @@ export default function DASDTable() {
selectDevice(device, isSelecting), @@ -308,17 +315,14 @@ export default function DASDTable() { - + - - + + ); } diff --git a/web/src/queries/dasd.ts b/web/src/queries/dasd.ts index 38d5587119..f629fdc22f 100644 --- a/web/src/queries/dasd.ts +++ b/web/src/queries/dasd.ts @@ -64,7 +64,6 @@ const DASDRunningFormatJobsQuery = () => ({ */ const useDASDRunningFormatJobs = (): FormatJob[] => { const { data: jobs } = useSuspenseQuery(DASDRunningFormatJobsQuery()); - return jobs; }; @@ -80,38 +79,37 @@ const useDASDFormatJobChanges = () => { return client.ws().onEvent((event) => { // TODO: for simplicity we now just invalidate query instead of manually adding, removing or changing devices - if ( - event.type === "DASDFormatJobChanged" - ) { - const data = queryClient.getQueryData(["dasd", "formatJobs", "running"]) as FormatJob[]; - const nextData = data.map((job) => { - if (job.jobId !== event.jobId) return job; + switch (event.type) { + case ("DASDFormatJobChanged"): { + const data = queryClient.getQueryData(["dasd", "formatJobs", "running"]) as FormatJob[]; + const nextData = data.map((job) => { + if (job.jobId !== event.jobId) return job; + + return { + ...job, + summary: { ...job?.summary, ...event.summary } + } + }); + queryClient.setQueryData(["dasd", "formatJobs", "running"], nextData); + break; + } + case ("JobAdded"): { + const formatJob: FormatJob = { jobId: event.job.id } + const data = queryClient.getQueryData(["dasd", "formatJobs", "running"]) as FormatJob[]; + data.push(formatJob); - return { - ...job, - summary: { ...job?.summary, ...event.summary } + queryClient.setQueryData(["dasd", "formatJobs", "running"], data); + break; + } + case "JobChanged": { + const { id, running } = event.job; + if (running) return; + const data = queryClient.getQueryData(["dasd", "formatJobs", "running"]) as FormatJob[]; + const nextData = data.filter((j) => j.jobId !== id); + if (data.length !== nextData.length) { + queryClient.setQueryData(["dasd", "formatJobs", "running"], nextData); } - }); - queryClient.setQueryData(["dasd", "formatJobs", "running"], nextData); - } - if ( - event.type === "JobAdded" - ) { - const formatJob: FormatJob = { jobId: event.job.id } - let data = queryClient.getQueryData(["dasd", "formatJobs", "running"]) as FormatJob[]; - data.push(formatJob); - - queryClient.setQueryData(["dasd", "formatJobs", "running"], data); - } - if ( - event.type === "JobChanged" - ) { - const { id, running } = event.job; - if (running) return; - let data = queryClient.getQueryData(["dasd", "formatJobs", "running"]) as FormatJob[]; - const nextData = data.filter((j) => j.jobId !== id); - if (data.length !== nextData.length) { - queryClient.setQueryData(["dasd", "formatJobs", "running"], nextData); + break; } } }); @@ -156,7 +154,6 @@ const useSelectedDASDChange = () => { } const queryClient = useQueryClient(); - const mutation = useMutation({ mutationFn: (data: SelectDASD): Promise => Promise.resolve(data), onSuccess: (data: SelectDASD) => queryClient.setQueryData(["dasd", "selected"], (prev: DASDDevice[]) => { @@ -191,7 +188,7 @@ const useFilterDASD = (): FilterDASD => { return data || { minChannel: "", maxChannel: "" }; } -const useFilterDASDChange = () => { +const useFilterDASDMutation = () => { const queryClient = useQueryClient(); const mutation = useMutation({ @@ -258,6 +255,6 @@ const useDASDDevicesChanges = () => { }; export { - useDASDDevices, useDASDDevicesChanges, useFilterDASDChange, filterDASDQuery, useFilterDASD, useSelectedDASD, useSelectedDASDChange, selectedDASDQuery, + useDASDDevices, useDASDDevicesChanges, useFilterDASDMutation, filterDASDQuery, useFilterDASD, useSelectedDASD, useSelectedDASDChange, selectedDASDQuery, useDASDFormatJobChanges, useDASDRunningFormatJobs, useFormatDASDMutation }; From 918a5e3b2715143290cab12ddd4fd51abf17f66f Mon Sep 17 00:00:00 2001 From: Knut Anderssen Date: Tue, 3 Sep 2024 16:53:41 +0100 Subject: [PATCH 42/54] Use mutations instead of API direct calls --- web/src/components/storage/DASDTable.tsx | 11 ++- web/src/queries/dasd.ts | 102 ++++++++++++++++++++--- 2 files changed, 101 insertions(+), 12 deletions(-) diff --git a/web/src/components/storage/DASDTable.tsx b/web/src/components/storage/DASDTable.tsx index 80beae417e..4d8a36d66e 100644 --- a/web/src/components/storage/DASDTable.tsx +++ b/web/src/components/storage/DASDTable.tsx @@ -43,11 +43,15 @@ import { _ } from "~/i18n"; import { hex } from "~/utils"; import { sort } from "fast-sort"; import { DASDDevice } from "~/types/dasd"; -import { disableDASD, disableDiag, enableDASD, enableDiag, formatDASD } from "~/api/dasd"; import { useDASDDevices, + useEnableDASDMutation, + useDisableDASDMutation, + useEnableDiagMutation, + useDisableDiagMutation, useFilterDASD, useFilterDASDMutation, + useFormatDASDMutation, useSelectedDASD, useSelectedDASDChange, } from "~/queries/dasd"; @@ -88,6 +92,11 @@ const columns = [ ]; const Actions = ({ devices, isDisabled }: { devices: DASDDevice[]; isDisabled: boolean }) => { + const { mutate: enableDASD } = useEnableDASDMutation(); + const { mutate: disableDASD } = useDisableDASDMutation(); + const { mutate: enableDiag } = useEnableDiagMutation(); + const { mutate: disableDiag } = useDisableDiagMutation(); + const { mutate: formatDASD } = useFormatDASDMutation(); const [isOpen, setIsOpen] = useState(false); const onToggle = () => setIsOpen(!isOpen); diff --git a/web/src/queries/dasd.ts b/web/src/queries/dasd.ts index f629fdc22f..fa158566b8 100644 --- a/web/src/queries/dasd.ts +++ b/web/src/queries/dasd.ts @@ -27,6 +27,7 @@ import { enableDASD, enableDiag, fetchDASDDevices, + formatDASD, } from "~/api/dasd"; import { useInstallerClient } from "~/context/installer"; import React from "react"; @@ -119,16 +120,6 @@ const useDASDFormatJobChanges = () => { return jobs; }; -const useFormatDASDMutation = () => { - const queryClient = useQueryClient(); - - const mutation = useMutation({ - mutationFn: (data: FormatJob): Promise => Promise.resolve(data), - onSuccess: (data: FormatJob) => queryClient.setQueryData(["dasd", "formatJob", data.jobId], data) - }); - - return mutation; -}; /** * Returns seleced DASD ids */ @@ -254,7 +245,96 @@ const useDASDDevicesChanges = () => { return devices; }; +const useEnableDASDMutation = () => { + const queryClient = useQueryClient(); + const query = { + mutationFn: enableDASD, + onSuccess: (_: object, deviceIds: string[]) => { + queryClient.setQueryData(["dasd", "devices"], (prev: DASDDevice[]) => { + const nextData = prev.map((dev) => { + if (deviceIds.includes(dev.id)) dev.enabled = true; + return dev; + }); + return nextData; + }); + }, + }; + return useMutation(query); +}; + +const useDisableDASDMutation = () => { + const queryClient = useQueryClient(); + const query = { + mutationFn: disableDASD, + onSuccess: (_: object, deviceIds: string[]) => { + queryClient.setQueryData(["dasd", "devices"], (prev: DASDDevice[]) => { + const nextData = prev.map((dev) => { + if (deviceIds.includes(dev.id)) dev.enabled = false; + return dev; + }); + return nextData; + }); + }, + }; + return useMutation(query); +}; + +const useEnableDiagMutation = () => { + const queryClient = useQueryClient(); + const query = { + mutationFn: enableDiag, + onSuccess: (_: object, deviceIds: string[]) => { + queryClient.setQueryData(["dasd", "devices"], (prev: DASDDevice[]) => { + const nextData = prev.map((dev) => { + if (deviceIds.includes(dev.id)) dev.diag = true; + return dev; + }); + return nextData; + }); + }, + }; + return useMutation(query); +}; + +const useDisableDiagMutation = () => { + const queryClient = useQueryClient(); + const query = { + mutationFn: disableDiag, + onSuccess: (_: object, deviceIds: string[]) => { + queryClient.setQueryData(["dasd", "devices"], (prev: DASDDevice[]) => { + const nextData = prev.map((dev) => { + if (deviceIds.includes(dev.id)) dev.diag = false; + return dev; + }); + return nextData; + }); + }, + }; + return useMutation(query); +}; + +const useFormatDASDMutation = () => { + const queryClient = useQueryClient(); + const query = { + mutationFn: formatDASD, + onSuccess: (data: string, deviceIds: string[]) => { + queryClient.setQueryData(["dasd", "formatJob", data], { jobId: data }); + queryClient.setQueryData(["dasd", "devices"], (prev: DASDDevice[]) => { + const nextData = prev.map((dev) => { + if (deviceIds.includes(dev.id)) dev.formatted = false; + return dev; + }); + return nextData; + }); + }, + }; + + return useMutation(query); +}; + + export { useDASDDevices, useDASDDevicesChanges, useFilterDASDMutation, filterDASDQuery, useFilterDASD, useSelectedDASD, useSelectedDASDChange, selectedDASDQuery, - useDASDFormatJobChanges, useDASDRunningFormatJobs, useFormatDASDMutation + useDASDFormatJobChanges, useDASDRunningFormatJobs, useEnableDASDMutation, useDisableDASDMutation, useEnableDiagMutation, useDisableDiagMutation, + useFormatDASDMutation }; From 4405715e35820132ef30cfbd727403c461c218d8 Mon Sep 17 00:00:00 2001 From: Knut Anderssen Date: Wed, 4 Sep 2024 07:25:56 +0100 Subject: [PATCH 43/54] More changes based on code review --- .../components/storage/DASDFormatProgress.tsx | 52 ++++++++----------- web/src/components/storage/DASDTable.test.tsx | 7 ++- 2 files changed, 29 insertions(+), 30 deletions(-) diff --git a/web/src/components/storage/DASDFormatProgress.tsx b/web/src/components/storage/DASDFormatProgress.tsx index 61f2b4e6da..835bd56eaa 100644 --- a/web/src/components/storage/DASDFormatProgress.tsx +++ b/web/src/components/storage/DASDFormatProgress.tsx @@ -24,7 +24,19 @@ import { Progress, Stack } from "@patternfly/react-core"; import { Popup } from "~/components/core"; import { _ } from "~/i18n"; import { useDASDDevices, useDASDRunningFormatJobs } from "~/queries/dasd"; -import { FormatSummary } from "~/types/dasd"; +import { DASDDevice, FormatSummary } from "~/types/dasd"; + +const DeviceProgress = ({ device, progress }: { device: DASDDevice; progress: FormatSummary }) => ( + +); export default function DASDFormatProgress() { const devices = useDASDDevices(); @@ -32,36 +44,18 @@ export default function DASDFormatProgress() { (job) => Object.keys(job.summary || {}).length > 0, ); - const ProgressContent = ({ progress }: { progress: { [key: string]: FormatSummary } }) => - Object.entries(progress).map(([id, { total, step, done }]) => { - const device = devices.find((d) => d.id === id); - - return ( - - ); - }); - return ( 0} disableFocusTrap> - {runningJobs.map((job) => { - return ( - - - - ); - })} + + {runningJobs.map((job) => + Object.entries(job.summary).map(([id, progress]) => { + const device = devices.find((d) => d.id === id); + return ( + + ); + }), + )} + ); } diff --git a/web/src/components/storage/DASDTable.test.tsx b/web/src/components/storage/DASDTable.test.tsx index 2e2c7f8676..a30a568a31 100644 --- a/web/src/components/storage/DASDTable.test.tsx +++ b/web/src/components/storage/DASDTable.test.tsx @@ -32,12 +32,17 @@ const mockSelectedDASD: string[] = []; jest.mock("~/queries/dasd", () => ({ useDASDDevices: () => mockDASDDevices, useFilterDASD: () => mockDASDFilter, - useFilterDASDChange: () => + useFilterDASDMutation: () => jest .fn() .mockImplementation(() => ({ mutate: (data: FilterDASD) => ({ data: mockDASDFilter }) })), useSelectedDASD: () => mockSelectedDASD, useSelectedDASDChange: () => jest.fn().mockImplementation(() => ({ data: mockSelectedDASD })), + useEnableDASDMutation: () => jest.fn(), + useDisableDASDMutation: () => jest.fn(), + useEnableDiagMutation: () => jest.fn(), + useDisableDiagMutation: () => jest.fn(), + useFormatDASDMutation: () => jest.fn(), })); describe("DASDTable", () => { From 6d140be54c8901383d1079b792f630751805013a Mon Sep 17 00:00:00 2001 From: Knut Anderssen Date: Wed, 4 Sep 2024 09:43:59 +0100 Subject: [PATCH 44/54] Replace dasd filter queries by component state --- web/src/components/storage/DASDTable.test.tsx | 10 +---- web/src/components/storage/DASDTable.tsx | 40 ++++++++----------- 2 files changed, 18 insertions(+), 32 deletions(-) diff --git a/web/src/components/storage/DASDTable.test.tsx b/web/src/components/storage/DASDTable.test.tsx index a30a568a31..160af08d24 100644 --- a/web/src/components/storage/DASDTable.test.tsx +++ b/web/src/components/storage/DASDTable.test.tsx @@ -20,22 +20,16 @@ */ import React from "react"; -import { act, screen } from "@testing-library/react"; +import { screen } from "@testing-library/react"; import { installerRender } from "~/test-utils"; import DASDTable from "~/components/storage/DASDTable"; -import { DASDDevice, FilterDASD } from "~/types/dasd"; +import { DASDDevice } from "~/types/dasd"; let mockDASDDevices: DASDDevice[] = []; -const mockDASDFilter = { minChannel: "", maxChannel: "" }; const mockSelectedDASD: string[] = []; jest.mock("~/queries/dasd", () => ({ useDASDDevices: () => mockDASDDevices, - useFilterDASD: () => mockDASDFilter, - useFilterDASDMutation: () => - jest - .fn() - .mockImplementation(() => ({ mutate: (data: FilterDASD) => ({ data: mockDASDFilter }) })), useSelectedDASD: () => mockSelectedDASD, useSelectedDASDChange: () => jest.fn().mockImplementation(() => ({ data: mockSelectedDASD })), useEnableDASDMutation: () => jest.fn(), diff --git a/web/src/components/storage/DASDTable.tsx b/web/src/components/storage/DASDTable.tsx index 4d8a36d66e..6f0d7795c8 100644 --- a/web/src/components/storage/DASDTable.tsx +++ b/web/src/components/storage/DASDTable.tsx @@ -19,7 +19,7 @@ * find current contact information at www.suse.com. */ -import React, { Dispatch, SetStateAction, useState } from "react"; +import React, { useState } from "react"; import { Button, CardBody, @@ -49,8 +49,6 @@ import { useDisableDASDMutation, useEnableDiagMutation, useDisableDiagMutation, - useFilterDASD, - useFilterDASDMutation, useFormatDASDMutation, useSelectedDASD, useSelectedDASDChange, @@ -170,12 +168,19 @@ const filterDevices = (devices: DASDDevice[], from: string, to: string): DASDDev return devices.filter((d) => d.hexId >= min && d.hexId <= max); }; +type FilterOptions = { + minChannel?: string; + maxChannel?: string; +}; + export default function DASDTable() { const devices = useDASDDevices(); - const { mutate: changeFilter } = useFilterDASDMutation(); const { mutate: changeSelected } = useSelectedDASDChange(); - const { minChannel, maxChannel } = useFilterDASD(); const selectedDevices = useSelectedDASD(); + const [{ minChannel, maxChannel }, setFilters] = useState({ + minChannel: "", + maxChannel: "", + }); const [sortingColumn, setSortingColumn] = useState(columns[0]); const [sortDirection, setSortDirection] = useState<"asc" | "desc">("asc"); @@ -210,21 +215,8 @@ export default function DASDTable() { }; }; - // Filtering - const onMinChannelFilterChange = (_event, value) => { - changeFilter({ minChannel: value }); - }; - - const onMaxChannelFilterChange = (_event, value) => { - changeFilter({ maxChannel: value }); - }; - - const removeMinChannelFilter = () => { - changeFilter({ minChannel: "" }); - }; - - const removeMaxChannelFilter = () => { - changeFilter({ maxChannel: "" }); + const updateFilter = (newFilters: FilterOptions) => { + setFilters((currentFilters) => ({ ...currentFilters, ...newFilters })); }; const Content = () => { @@ -283,14 +275,14 @@ export default function DASDTable() { type="text" aria-label={_("Filter by min channel")} placeholder={_("Filter by min channel")} - onChange={onMinChannelFilterChange} + onChange={(_, minChannel) => updateFilter({ minChannel })} /> {minChannel !== "" && ( @@ -305,14 +297,14 @@ export default function DASDTable() { type="text" aria-label={_("Filter by max channel")} placeholder={_("Filter by max channel")} - onChange={onMaxChannelFilterChange} + onChange={(_, maxChannel) => updateFilter({ maxChannel })} /> {maxChannel !== "" && ( From 8fc01b9f9679dca3786ad07d3d56021fca24f680 Mon Sep 17 00:00:00 2001 From: Knut Anderssen Date: Wed, 4 Sep 2024 10:00:55 +0100 Subject: [PATCH 45/54] Replace dasd selection queries by component state --- web/src/components/storage/DASDTable.tsx | 29 +++++-- web/src/queries/dasd.ts | 100 +++++++++-------------- 2 files changed, 61 insertions(+), 68 deletions(-) diff --git a/web/src/components/storage/DASDTable.tsx b/web/src/components/storage/DASDTable.tsx index 6f0d7795c8..b7eccec7aa 100644 --- a/web/src/components/storage/DASDTable.tsx +++ b/web/src/components/storage/DASDTable.tsx @@ -50,8 +50,6 @@ import { useEnableDiagMutation, useDisableDiagMutation, useFormatDASDMutation, - useSelectedDASD, - useSelectedDASDChange, } from "~/queries/dasd"; // FIXME: please, note that this file still requiring refinements until reach a @@ -172,11 +170,15 @@ type FilterOptions = { minChannel?: string; maxChannel?: string; }; +type SelectionOptions = { + unselect?: boolean; + device?: DASDDevice; + devices?: DASDDevice[]; +}; export default function DASDTable() { const devices = useDASDDevices(); - const { mutate: changeSelected } = useSelectedDASDChange(); - const selectedDevices = useSelectedDASD(); + const [selectedDASD, setSelectedDASD] = useState([]); const [{ minChannel, maxChannel }, setFilters] = useState({ minChannel: "", maxChannel: "", @@ -187,7 +189,7 @@ export default function DASDTable() { const sortColumnIndex = () => columns.findIndex((c) => c.id === sortingColumn.id); const filteredDevices = filterDevices(devices, minChannel, maxChannel); - const selectedDevicesIds = selectedDevices.map((d) => d.id); + const selectedDevicesIds = selectedDASD.map((d) => d.id); // Selecting const selectAll = (isSelecting = true) => { @@ -219,6 +221,19 @@ export default function DASDTable() { setFilters((currentFilters) => ({ ...currentFilters, ...newFilters })); }; + const changeSelected = (newSelection: SelectionOptions) => { + setSelectedDASD((prevSelection) => { + if (newSelection.unselect) { + if (newSelection.device) + return prevSelection.filter((d) => d.id !== newSelection.device.id); + if (newSelection.devices) return []; + } else { + if (newSelection.device) return [...prevSelection, newSelection.device]; + if (newSelection.devices) return newSelection.devices; + } + }); + }; + const Content = () => { return ( @@ -228,7 +243,7 @@ export default function DASDTable() { aria-label="dasd-select" select={{ onSelect: (_event, isSelecting) => selectAll(isSelecting), - isSelected: filteredDevices.length === selectedDevices.length, + isSelected: filteredDevices.length === selectedDASD.length, }} /> {columns.map((column, index) => ( @@ -316,7 +331,7 @@ export default function DASDTable() { - + diff --git a/web/src/queries/dasd.ts b/web/src/queries/dasd.ts index fa158566b8..d6bbb99b0e 100644 --- a/web/src/queries/dasd.ts +++ b/web/src/queries/dasd.ts @@ -56,8 +56,11 @@ const useDASDDevices = () => { */ const DASDRunningFormatJobsQuery = () => ({ queryKey: ["dasd", "formatJobs", "running"], - queryFn: () => fetchStorageJobs().then((jobs) => jobs.filter((j) => j.running).map(({ id }) => ({ jobId: id }))), - staleTime: 200 + queryFn: () => + fetchStorageJobs().then((jobs) => + jobs.filter((j) => j.running).map(({ id }) => ({ jobId: id })), + ), + staleTime: 200, }); /** @@ -81,21 +84,21 @@ const useDASDFormatJobChanges = () => { return client.ws().onEvent((event) => { // TODO: for simplicity we now just invalidate query instead of manually adding, removing or changing devices switch (event.type) { - case ("DASDFormatJobChanged"): { + case "DASDFormatJobChanged": { const data = queryClient.getQueryData(["dasd", "formatJobs", "running"]) as FormatJob[]; const nextData = data.map((job) => { if (job.jobId !== event.jobId) return job; return { ...job, - summary: { ...job?.summary, ...event.summary } - } + summary: { ...job?.summary, ...event.summary }, + }; }); queryClient.setQueryData(["dasd", "formatJobs", "running"], nextData); break; } - case ("JobAdded"): { - const formatJob: FormatJob = { jobId: event.job.id } + case "JobAdded": { + const formatJob: FormatJob = { jobId: event.job.id }; const data = queryClient.getQueryData(["dasd", "formatJobs", "running"]) as FormatJob[]; data.push(formatJob); @@ -135,61 +138,28 @@ const useSelectedDASD = (): DASDDevice[] => { const { data } = useQuery(selectedDASDQuery()); return data || []; -} +}; const useSelectedDASDChange = () => { type SelectDASD = { - unselect?: boolean, - device?: DASDDevice, - devices?: DASDDevice[] - } + unselect?: boolean; + device?: DASDDevice; + devices?: DASDDevice[]; + }; const queryClient = useQueryClient(); const mutation = useMutation({ mutationFn: (data: SelectDASD): Promise => Promise.resolve(data), - onSuccess: (data: SelectDASD) => queryClient.setQueryData(["dasd", "selected"], (prev: DASDDevice[]) => { - if (data.unselect) { - if (data.device) return prev.filter((d) => d.id !== data.device.id); - if (data.devices) return []; - - } else { - if (data.device) return [...prev, data.device]; - if (data.devices) return data.devices; - } - }), - }); - - return mutation; -}; - -/** - * Returns DASD filters - */ -const filterDASDQuery = () => ({ - queryKey: ["dasd", "filter"], - queryFn: () => { - return Promise.resolve({ minChannel: "", maxChannel: "" }); - }, - staleTime: Infinity, -}); - -const useFilterDASD = (): FilterDASD => { - const { data } = useQuery(filterDASDQuery()); - - return data || { minChannel: "", maxChannel: "" }; -} - -const useFilterDASDMutation = () => { - const queryClient = useQueryClient(); - - const mutation = useMutation({ - mutationFn: (data: FilterDASD): Promise => Promise.resolve(data), - onSuccess: (data: FilterDASD) => { - queryClient.setQueryData(["dasd", "filter"], (prev: FilterDASD) => ({ - ...prev, - ...data - })); - }, + onSuccess: (data: SelectDASD) => + queryClient.setQueryData(["dasd", "selected"], (prev: DASDDevice[]) => { + if (data.unselect) { + if (data.device) return prev.filter((d) => d.id !== data.device.id); + if (data.devices) return []; + } else { + if (data.device) return [...prev, data.device]; + if (data.devices) return data.devices; + } + }), }); return mutation; @@ -220,7 +190,7 @@ const useDASDDevicesChanges = () => { const device: DASDDevice = event.device; const { id } = device; queryClient.setQueryData(["dasd", "devices"], (prev: DASDDevice[]) => { - const res = prev.filter(dev => dev.id !== id); + const res = prev.filter((dev) => dev.id !== id); return res; }); break; @@ -231,7 +201,7 @@ const useDASDDevicesChanges = () => { queryClient.setQueryData(["dasd", "devices"], (prev: DASDDevice[]) => { // deep copy of original to have it immutable const res = [...prev]; - const index = res.findIndex(dev => dev.id === id); + const index = res.findIndex((dev) => dev.id === id); res[index] = device; return res; }); @@ -332,9 +302,17 @@ const useFormatDASDMutation = () => { return useMutation(query); }; - export { - useDASDDevices, useDASDDevicesChanges, useFilterDASDMutation, filterDASDQuery, useFilterDASD, useSelectedDASD, useSelectedDASDChange, selectedDASDQuery, - useDASDFormatJobChanges, useDASDRunningFormatJobs, useEnableDASDMutation, useDisableDASDMutation, useEnableDiagMutation, useDisableDiagMutation, - useFormatDASDMutation + useDASDDevices, + useDASDDevicesChanges, + useSelectedDASD, + useSelectedDASDChange, + selectedDASDQuery, + useDASDFormatJobChanges, + useDASDRunningFormatJobs, + useEnableDASDMutation, + useDisableDASDMutation, + useEnableDiagMutation, + useDisableDiagMutation, + useFormatDASDMutation, }; From 50e76ad6dd950adce4a662cb8b880bf27337a346 Mon Sep 17 00:00:00 2001 From: Knut Anderssen Date: Wed, 4 Sep 2024 10:06:17 +0100 Subject: [PATCH 46/54] Do not set any info about formatting. --- web/src/queries/dasd.ts | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/web/src/queries/dasd.ts b/web/src/queries/dasd.ts index d6bbb99b0e..81c1d1d1a8 100644 --- a/web/src/queries/dasd.ts +++ b/web/src/queries/dasd.ts @@ -287,15 +287,8 @@ const useFormatDASDMutation = () => { const queryClient = useQueryClient(); const query = { mutationFn: formatDASD, - onSuccess: (data: string, deviceIds: string[]) => { + onSuccess: (data: string) => { queryClient.setQueryData(["dasd", "formatJob", data], { jobId: data }); - queryClient.setQueryData(["dasd", "devices"], (prev: DASDDevice[]) => { - const nextData = prev.map((dev) => { - if (deviceIds.includes(dev.id)) dev.formatted = false; - return dev; - }); - return nextData; - }); }, }; From a75354f1ddb8f9eff6a54689e75f69d15d9860fa Mon Sep 17 00:00:00 2001 From: Knut Anderssen Date: Wed, 4 Sep 2024 12:03:18 +0100 Subject: [PATCH 47/54] Added DASD route only if supported --- .../components/storage/DevicesTechMenu.jsx | 2 +- web/src/routes/storage.tsx | 78 ++++++++++--------- 2 files changed, 43 insertions(+), 37 deletions(-) diff --git a/web/src/components/storage/DevicesTechMenu.jsx b/web/src/components/storage/DevicesTechMenu.jsx index 9c60917b25..12449889b5 100644 --- a/web/src/components/storage/DevicesTechMenu.jsx +++ b/web/src/components/storage/DevicesTechMenu.jsx @@ -96,7 +96,7 @@ export default function DevicesTechMenu({ label }) { ); - const onSelect = (_event, value) => { + const onSelect = () => { setIsOpen(false); }; diff --git a/web/src/routes/storage.tsx b/web/src/routes/storage.tsx index a5d33f00ba..37d224efcd 100644 --- a/web/src/routes/storage.tsx +++ b/web/src/routes/storage.tsx @@ -27,7 +27,7 @@ import { DASDPage, ISCSIPage } from "~/components/storage"; import ProposalPage from "~/components/storage/ProposalPage"; import { Route } from "~/types/routes"; import { N_ } from "~/i18n"; -import { probeDASD } from "~/api/dasd"; +import { DASDSupported, probeDASD } from "~/api/dasd"; const PATHS = { root: "/storage", @@ -35,43 +35,49 @@ const PATHS = { bootingPartition: "/storage/booting-partition", spacePolicy: "/storage/space-policy", iscsi: "/storage/iscsi", - dasd: "/storage/dasd" + dasd: "/storage/dasd", }; -const routes = (): Route => ({ - path: PATHS.root, - handle: { name: N_("Storage"), icon: "hard_drive" }, - children: [ - { - index: true, - element: , - }, - { - path: PATHS.targetDevice, - element: , - }, - { - path: PATHS.bootingPartition, - element: , - }, - { - path: PATHS.spacePolicy, - element: , - }, - { - path: PATHS.iscsi, - element: , - handle: { name: N_("iSCSI") }, - }, - { - path: PATHS.dasd, - element: , - handle: { name: N_("DASD") }, - //FIXME: move to the onClick of the DASD SelectOption - loader: async () => probeDASD() - }, - ], -}); +const routes = (): Route => { + const dasdRoute = { + path: PATHS.dasd, + element: , + handle: { name: N_("DASD") }, + loader: async () => probeDASD(), + }; + + const routes = { + path: PATHS.root, + handle: { name: N_("Storage"), icon: "hard_drive" }, + children: [ + { + index: true, + element: , + }, + { + path: PATHS.targetDevice, + element: , + }, + { + path: PATHS.bootingPartition, + element: , + }, + { + path: PATHS.spacePolicy, + element: , + }, + { + path: PATHS.iscsi, + element: , + handle: { name: N_("iSCSI") }, + }, + ], + }; + + if (DASDSupported()) routes.children.push(dasdRoute); + + return routes; +}; export default routes; export { PATHS }; From 669f1027df292f8ee15484241f85978a4f02edd5 Mon Sep 17 00:00:00 2001 From: Knut Anderssen Date: Wed, 4 Sep 2024 12:08:07 +0100 Subject: [PATCH 48/54] Removed selected DASD leftover queries --- web/src/queries/dasd.ts | 45 ----------------------------------------- 1 file changed, 45 deletions(-) diff --git a/web/src/queries/dasd.ts b/web/src/queries/dasd.ts index 81c1d1d1a8..f57d445412 100644 --- a/web/src/queries/dasd.ts +++ b/web/src/queries/dasd.ts @@ -123,48 +123,6 @@ const useDASDFormatJobChanges = () => { return jobs; }; -/** - * Returns seleced DASD ids - */ -const selectedDASDQuery = () => ({ - queryKey: ["dasd", "selected"], - queryFn: () => { - return Promise.resolve([]); - }, - staleTime: Infinity, -}); - -const useSelectedDASD = (): DASDDevice[] => { - const { data } = useQuery(selectedDASDQuery()); - - return data || []; -}; - -const useSelectedDASDChange = () => { - type SelectDASD = { - unselect?: boolean; - device?: DASDDevice; - devices?: DASDDevice[]; - }; - - const queryClient = useQueryClient(); - const mutation = useMutation({ - mutationFn: (data: SelectDASD): Promise => Promise.resolve(data), - onSuccess: (data: SelectDASD) => - queryClient.setQueryData(["dasd", "selected"], (prev: DASDDevice[]) => { - if (data.unselect) { - if (data.device) return prev.filter((d) => d.id !== data.device.id); - if (data.devices) return []; - } else { - if (data.device) return [...prev, data.device]; - if (data.devices) return data.devices; - } - }), - }); - - return mutation; -}; - /** * Listens for DASD devices changes. */ @@ -298,9 +256,6 @@ const useFormatDASDMutation = () => { export { useDASDDevices, useDASDDevicesChanges, - useSelectedDASD, - useSelectedDASDChange, - selectedDASDQuery, useDASDFormatJobChanges, useDASDRunningFormatJobs, useEnableDASDMutation, From 76d295b30278f0f724e0d1a5d23b23a0b3e4e90b Mon Sep 17 00:00:00 2001 From: Knut Anderssen Date: Wed, 4 Sep 2024 15:27:11 +0100 Subject: [PATCH 49/54] Use only one mutation for modifying DASD state --- web/src/components/storage/DASDTable.test.tsx | 8 +- web/src/components/storage/DASDTable.tsx | 22 ++--- web/src/queries/dasd.ts | 95 ++++++++----------- 3 files changed, 46 insertions(+), 79 deletions(-) diff --git a/web/src/components/storage/DASDTable.test.tsx b/web/src/components/storage/DASDTable.test.tsx index 160af08d24..6643e41029 100644 --- a/web/src/components/storage/DASDTable.test.tsx +++ b/web/src/components/storage/DASDTable.test.tsx @@ -26,16 +26,10 @@ import DASDTable from "~/components/storage/DASDTable"; import { DASDDevice } from "~/types/dasd"; let mockDASDDevices: DASDDevice[] = []; -const mockSelectedDASD: string[] = []; jest.mock("~/queries/dasd", () => ({ useDASDDevices: () => mockDASDDevices, - useSelectedDASD: () => mockSelectedDASD, - useSelectedDASDChange: () => jest.fn().mockImplementation(() => ({ data: mockSelectedDASD })), - useEnableDASDMutation: () => jest.fn(), - useDisableDASDMutation: () => jest.fn(), - useEnableDiagMutation: () => jest.fn(), - useDisableDiagMutation: () => jest.fn(), + useDASDMutation: () => jest.fn(), useFormatDASDMutation: () => jest.fn(), })); diff --git a/web/src/components/storage/DASDTable.tsx b/web/src/components/storage/DASDTable.tsx index b7eccec7aa..54fd22fec8 100644 --- a/web/src/components/storage/DASDTable.tsx +++ b/web/src/components/storage/DASDTable.tsx @@ -43,14 +43,7 @@ import { _ } from "~/i18n"; import { hex } from "~/utils"; import { sort } from "fast-sort"; import { DASDDevice } from "~/types/dasd"; -import { - useDASDDevices, - useEnableDASDMutation, - useDisableDASDMutation, - useEnableDiagMutation, - useDisableDiagMutation, - useFormatDASDMutation, -} from "~/queries/dasd"; +import { useDASDDevices, useDASDMutation, useFormatDASDMutation } from "~/queries/dasd"; // FIXME: please, note that this file still requiring refinements until reach a // reasonable stable version @@ -88,10 +81,7 @@ const columns = [ ]; const Actions = ({ devices, isDisabled }: { devices: DASDDevice[]; isDisabled: boolean }) => { - const { mutate: enableDASD } = useEnableDASDMutation(); - const { mutate: disableDASD } = useDisableDASDMutation(); - const { mutate: enableDiag } = useEnableDiagMutation(); - const { mutate: disableDiag } = useDisableDiagMutation(); + const { mutate: updateDASD } = useDASDMutation(); const { mutate: formatDASD } = useFormatDASDMutation(); const [isOpen, setIsOpen] = useState(false); @@ -99,10 +89,10 @@ const Actions = ({ devices, isDisabled }: { devices: DASDDevice[]; isDisabled: b const onSelect = () => setIsOpen(false); const deviceIds = devices.map((d) => d.id); - const activate = () => enableDASD(deviceIds); - const deactivate = () => disableDASD(deviceIds); - const setDiagOn = () => enableDiag(deviceIds); - const setDiagOff = () => disableDiag(deviceIds); + const activate = () => updateDASD({ action: "enable", devices: deviceIds }); + const deactivate = () => updateDASD({ action: "disable", devices: deviceIds }); + const setDiagOn = () => updateDASD({ action: "diagOn", devices: deviceIds }); + const setDiagOff = () => updateDASD({ action: "diagOff", devices: deviceIds }); const format = () => { const offline = devices.filter((d) => !d.enabled); diff --git a/web/src/queries/dasd.ts b/web/src/queries/dasd.ts index f57d445412..c7c4eb22b5 100644 --- a/web/src/queries/dasd.ts +++ b/web/src/queries/dasd.ts @@ -173,71 +173,57 @@ const useDASDDevicesChanges = () => { return devices; }; -const useEnableDASDMutation = () => { +const useDASDMutation = () => { const queryClient = useQueryClient(); const query = { - mutationFn: enableDASD, - onSuccess: (_: object, deviceIds: string[]) => { - queryClient.setQueryData(["dasd", "devices"], (prev: DASDDevice[]) => { - const nextData = prev.map((dev) => { - if (deviceIds.includes(dev.id)) dev.enabled = true; - return dev; - }); - return nextData; - }); + mutationFn: ({ action, devices }: { action: string; devices: string[] }) => { + switch (action) { + case "enable": { + return enableDASD(devices); + } + case "disable": { + return disableDASD(devices); + } + case "diagOn": { + return enableDiag(devices); + } + case "diagOff": { + return disableDiag(devices); + } + } }, - }; - return useMutation(query); -}; - -const useDisableDASDMutation = () => { - const queryClient = useQueryClient(); - const query = { - mutationFn: disableDASD, - onSuccess: (_: object, deviceIds: string[]) => { + onSuccess: (_: object, { action, devices }: { action: string; devices: string[] }) => { queryClient.setQueryData(["dasd", "devices"], (prev: DASDDevice[]) => { const nextData = prev.map((dev) => { - if (deviceIds.includes(dev.id)) dev.enabled = false; - return dev; - }); - return nextData; - }); - }, - }; - return useMutation(query); -}; + if (devices.includes(dev.id)) { + switch (action) { + case "enable": { + dev.enabled = true; + break; + } + case "disable": { + dev.enabled = false; + break; + } + case "diagOn": { + dev.diag = true; + break; + } + case "diagOff": { + dev.diag = false; + break; + } + } + } -const useEnableDiagMutation = () => { - const queryClient = useQueryClient(); - const query = { - mutationFn: enableDiag, - onSuccess: (_: object, deviceIds: string[]) => { - queryClient.setQueryData(["dasd", "devices"], (prev: DASDDevice[]) => { - const nextData = prev.map((dev) => { - if (deviceIds.includes(dev.id)) dev.diag = true; return dev; }); - return nextData; - }); - }, - }; - return useMutation(query); -}; -const useDisableDiagMutation = () => { - const queryClient = useQueryClient(); - const query = { - mutationFn: disableDiag, - onSuccess: (_: object, deviceIds: string[]) => { - queryClient.setQueryData(["dasd", "devices"], (prev: DASDDevice[]) => { - const nextData = prev.map((dev) => { - if (deviceIds.includes(dev.id)) dev.diag = false; - return dev; - }); return nextData; }); }, }; + return useMutation(query); }; @@ -258,9 +244,6 @@ export { useDASDDevicesChanges, useDASDFormatJobChanges, useDASDRunningFormatJobs, - useEnableDASDMutation, - useDisableDASDMutation, - useEnableDiagMutation, - useDisableDiagMutation, useFormatDASDMutation, + useDASDMutation, }; From a705ac61a26631fa0bcf5341c897637056caa972 Mon Sep 17 00:00:00 2001 From: Knut Anderssen Date: Wed, 4 Sep 2024 16:43:11 +0100 Subject: [PATCH 50/54] Do not modify existent cache but return new object --- web/src/queries/dasd.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/web/src/queries/dasd.ts b/web/src/queries/dasd.ts index c7c4eb22b5..917dfc61a1 100644 --- a/web/src/queries/dasd.ts +++ b/web/src/queries/dasd.ts @@ -194,7 +194,8 @@ const useDASDMutation = () => { }, onSuccess: (_: object, { action, devices }: { action: string; devices: string[] }) => { queryClient.setQueryData(["dasd", "devices"], (prev: DASDDevice[]) => { - const nextData = prev.map((dev) => { + const nextData = prev.map((prevDev) => { + const dev = { ...prevDev }; if (devices.includes(dev.id)) { switch (action) { case "enable": { From d0bf495be7018811c46b85e3c7de746e4da9f797 Mon Sep 17 00:00:00 2001 From: Knut Anderssen Date: Wed, 4 Sep 2024 16:52:37 +0100 Subject: [PATCH 51/54] Another inmutability fix --- web/src/queries/dasd.ts | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/web/src/queries/dasd.ts b/web/src/queries/dasd.ts index 917dfc61a1..68c09fe5a4 100644 --- a/web/src/queries/dasd.ts +++ b/web/src/queries/dasd.ts @@ -32,8 +32,8 @@ import { import { useInstallerClient } from "~/context/installer"; import React from "react"; import { hex } from "~/utils"; -import { DASDDevice, FilterDASD, FormatJob } from "~/types/dasd"; -import { fetchStorageJobs, findStorageJob } from "~/api/storage"; +import { DASDDevice, FormatJob } from "~/types/dasd"; +import { fetchStorageJobs } from "~/api/storage"; /** * Returns a query for retrieving the dasd devices @@ -100,9 +100,8 @@ const useDASDFormatJobChanges = () => { case "JobAdded": { const formatJob: FormatJob = { jobId: event.job.id }; const data = queryClient.getQueryData(["dasd", "formatJobs", "running"]) as FormatJob[]; - data.push(formatJob); - queryClient.setQueryData(["dasd", "formatJobs", "running"], data); + queryClient.setQueryData(["dasd", "formatJobs", "running"], [...data, formatJob]); break; } case "JobChanged": { @@ -138,9 +137,7 @@ const useDASDDevicesChanges = () => { case "DASDDeviceAdded": { const device: DASDDevice = event.device; queryClient.setQueryData(["dasd", "devices"], (prev: DASDDevice[]) => { - // do not use push here as updater has to be immutable - const res = prev.concat([device]); - return res; + return [...prev, device]; }); break; } From 1c9dfdd5067883393d44c89fff3fe02a6fa9e611 Mon Sep 17 00:00:00 2001 From: Knut Anderssen Date: Wed, 4 Sep 2024 22:02:11 +0100 Subject: [PATCH 52/54] Added changelog --- web/package/agama-web-ui.changes | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/web/package/agama-web-ui.changes b/web/package/agama-web-ui.changes index 3f0b2a424d..6a2ccdcfcd 100644 --- a/web/package/agama-web-ui.changes +++ b/web/package/agama-web-ui.changes @@ -1,3 +1,8 @@ +------------------------------------------------------------------- +Wed Sep 4 21:00:34 UTC 2024 - Knut Anderssen + +- Bring back DASD management support (gh#openSUSE/1549). + ------------------------------------------------------------------- Tue Aug 13 14:57:21 UTC 2024 - David Diaz From 9b96a932ee9e7d49c3224209ddd8c0e297ae1411 Mon Sep 17 00:00:00 2001 From: Knut Anderssen Date: Thu, 5 Sep 2024 07:19:23 +0100 Subject: [PATCH 53/54] Apply suggestions from CR --- web/package/agama-web-ui.changes | 2 +- web/src/types/dasd.ts | 7 +------ 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/web/package/agama-web-ui.changes b/web/package/agama-web-ui.changes index 6a2ccdcfcd..bfd54e86d1 100644 --- a/web/package/agama-web-ui.changes +++ b/web/package/agama-web-ui.changes @@ -1,7 +1,7 @@ ------------------------------------------------------------------- Wed Sep 4 21:00:34 UTC 2024 - Knut Anderssen -- Bring back DASD management support (gh#openSUSE/1549). +- Bring back DASD management support (gh#openSUSE/agama#1549). ------------------------------------------------------------------- Tue Aug 13 14:57:21 UTC 2024 - David Diaz diff --git a/web/src/types/dasd.ts b/web/src/types/dasd.ts index 3e11d3e3b5..0acfad270a 100644 --- a/web/src/types/dasd.ts +++ b/web/src/types/dasd.ts @@ -43,9 +43,4 @@ type FormatJob = { summary?: { [key: string]: FormatSummary } } -type FilterDASD = { - maxChannel?: string; - minChannel?: string; -}; - -export type { DASDDevice, FormatSummary, FormatJob, FilterDASD }; +export type { DASDDevice, FormatSummary, FormatJob }; From e8960850475815865187d85b833800f23261b30b Mon Sep 17 00:00:00 2001 From: Knut Anderssen Date: Thu, 5 Sep 2024 10:28:15 +0100 Subject: [PATCH 54/54] Redirect to storage page in case DASD is not supported --- web/src/routes/storage.tsx | 75 ++++++++++++++++++-------------------- 1 file changed, 36 insertions(+), 39 deletions(-) diff --git a/web/src/routes/storage.tsx b/web/src/routes/storage.tsx index 37d224efcd..0df131b543 100644 --- a/web/src/routes/storage.tsx +++ b/web/src/routes/storage.tsx @@ -28,6 +28,7 @@ import ProposalPage from "~/components/storage/ProposalPage"; import { Route } from "~/types/routes"; import { N_ } from "~/i18n"; import { DASDSupported, probeDASD } from "~/api/dasd"; +import { redirect } from "react-router-dom"; const PATHS = { root: "/storage", @@ -38,46 +39,42 @@ const PATHS = { dasd: "/storage/dasd", }; -const routes = (): Route => { - const dasdRoute = { - path: PATHS.dasd, - element: , - handle: { name: N_("DASD") }, - loader: async () => probeDASD(), - }; - - const routes = { - path: PATHS.root, - handle: { name: N_("Storage"), icon: "hard_drive" }, - children: [ - { - index: true, - element: , - }, - { - path: PATHS.targetDevice, - element: , - }, - { - path: PATHS.bootingPartition, - element: , - }, - { - path: PATHS.spacePolicy, - element: , +const routes = (): Route => ({ + path: PATHS.root, + handle: { name: N_("Storage"), icon: "hard_drive" }, + children: [ + { + index: true, + element: , + }, + { + path: PATHS.targetDevice, + element: , + }, + { + path: PATHS.bootingPartition, + element: , + }, + { + path: PATHS.spacePolicy, + element: , + }, + { + path: PATHS.iscsi, + element: , + handle: { name: N_("iSCSI") }, + }, + { + path: PATHS.dasd, + element: , + handle: { name: N_("DASD") }, + loader: async () => { + if (!DASDSupported()) return redirect(PATHS.root); + return probeDASD(); }, - { - path: PATHS.iscsi, - element: , - handle: { name: N_("iSCSI") }, - }, - ], - }; - - if (DASDSupported()) routes.children.push(dasdRoute); - - return routes; -}; + }, + ], +}); export default routes; export { PATHS };