diff --git a/web/src/api/storage/proposal.ts b/web/src/api/storage/proposal.ts deleted file mode 100644 index 333f3ae633..0000000000 --- a/web/src/api/storage/proposal.ts +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (c) [2024-2025] SUSE LLC - * - * All Rights Reserved. - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation; either version 2 of the License, or (at your option) - * any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU 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 { put } from "../http"; -import { ProposalSettingsPatch } from "~/api/storage/types"; - -const calculate = (settings: ProposalSettingsPatch) => - put("/api/storage/proposal/settings", settings); - -export { calculate }; diff --git a/web/src/api/storage/types/config-model.ts b/web/src/api/storage/types/config-model.ts index 079d6bb8f4..2095531c99 100644 --- a/web/src/api/storage/types/config-model.ts +++ b/web/src/api/storage/types/config-model.ts @@ -1,4 +1,3 @@ -/* eslint-disable */ /** * This file was automatically generated by json-schema-to-typescript. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, diff --git a/web/src/api/storage/types/config.ts b/web/src/api/storage/types/config.ts index 7a7864a01d..dbaaa13af1 100644 --- a/web/src/api/storage/types/config.ts +++ b/web/src/api/storage/types/config.ts @@ -1,4 +1,3 @@ -/* eslint-disable */ /** * This file was automatically generated by json-schema-to-typescript. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, diff --git a/web/src/components/core/ExpandableSelector.test.tsx b/web/src/components/core/ExpandableSelector.test.tsx deleted file mode 100644 index 9f1f9e6815..0000000000 --- a/web/src/components/core/ExpandableSelector.test.tsx +++ /dev/null @@ -1,454 +0,0 @@ -/* - * 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 the GNU General Public License as published by the Free - * Software Foundation; either version 2 of the License, or (at your option) - * any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU 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 { screen, within } from "@testing-library/react"; -import { plainRender } from "~/test-utils"; -import { ExpandableSelector } from "~/components/core"; -import { ExpandableSelectorColumn } from "./ExpandableSelector"; - -let consoleErrorSpy: jest.SpyInstance; - -/* eslint-disable @typescript-eslint/no-explicit-any */ - -const sda: any = { - sid: "59", - isDrive: true, - type: "disk", - vendor: "Micron", - model: "Micron 1100 SATA", - driver: ["ahci", "mmcblk"], - bus: "IDE", - busId: "", - transport: "usb", - dellBOSS: false, - sdCard: true, - active: true, - name: "/dev/sda", - size: 1024, - shrinking: { unsupported: ["Resizing is not supported"] }, - systems: [], - udevIds: ["ata-Micron_1100_SATA_512GB_12563", "scsi-0ATA_Micron_1100_SATA_512GB"], - udevPaths: ["pci-0000:00-12", "pci-0000:00-12-ata"], -}; - -const sda1 = { - sid: "60", - isDrive: false, - type: "", - active: true, - name: "/dev/sda1", - size: 512, - shrinking: { supported: 128 }, - systems: [], - udevIds: [], - udevPaths: [], -}; - -const sda2 = { - sid: "61", - isDrive: false, - type: "", - active: true, - name: "/dev/sda2", - size: 512, - shrinking: { unsupported: ["Resizing is not supported"] }, - systems: [], - udevIds: [], - udevPaths: [], -}; - -sda.partitionTable = { - type: "gpt", - partitions: [sda1, sda2], - unpartitionedSize: 512, -}; - -const sdb = { - sid: "62", - isDrive: true, - type: "disk", - vendor: "Samsung", - model: "Samsung Evo 8 Pro", - driver: ["ahci"], - bus: "IDE", - busId: "", - transport: "", - dellBOSS: false, - sdCard: false, - active: true, - name: "/dev/sdb", - size: 2048, - shrinking: { unsupported: ["Resizing is not supported"] }, - systems: [], - udevIds: [], - udevPaths: ["pci-0000:00-19"], -}; - -const lv1 = { - sid: "163", - name: "/dev/system/vg/lv1", - content: "Personal Data", -}; - -const vg = { - sid: "162", - type: "vg", - name: "/dev/system/vg", - lvs: [lv1], -}; - -const columns: ExpandableSelectorColumn[] = [ - // FIXME: do not use any but the right types once storage part is rewritten. - // Or even better, write a test not coupled to storage - { name: "Device", value: (item: any) => item.name }, - { - name: "Content", - value: (item: any) => { - if (item.isDrive) return item.systems.map((s, i) =>

{s}

); - if (item.type === "vg") return `${item.lvs.length} logical volume(s)`; - - return item.content; - }, - }, - { name: "Size", value: (item: any) => item.size }, -]; - -const onChangeFn = jest.fn(); - -let props; -const commonProps = { - columns, - items: [sda, sdb, vg], - itemIdKey: "sid", - initialExpandedKeys: [sda.sid, vg.sid], - itemChildren: (item) => (item.isDrive ? item.partitionTable?.partitions : item.lvs), - onSelectionChange: onChangeFn, - "aria-label": "Device selector", -}; - -describe("ExpandableSelector", () => { - beforeAll(() => { - consoleErrorSpy = jest.spyOn(console, "error"); - consoleErrorSpy.mockImplementation(); - }); - - afterAll(() => { - consoleErrorSpy.mockRestore(); - }); - - beforeEach(() => { - props = { ...commonProps }; - }); - - it("renders a table with given name", () => { - plainRender(); - screen.getByRole("grid", { name: "Device selector" }); - }); - - it("renders the table headers", () => { - plainRender(); - const table = screen.getByRole("grid"); - within(table).getByRole("columnheader", { name: "Device" }); - within(table).getByRole("columnheader", { name: "Content" }); - within(table).getByRole("columnheader", { name: "Size" }); - }); - - it("renders a rowgroup per parent item", () => { - plainRender(); - const groups = screen.getAllByRole("rowgroup"); - // NOTE: since has also the rowgroup role, we expect to found 4 in - // this example: 1 thead + 3 tbody (sda, sdb, vg) - expect(groups.length).toEqual(4); - }); - - it("renders a row per given item and found children", () => { - plainRender(); - const table = screen.getByRole("grid"); - within(table).getByRole("row", { name: /dev\/sda 1024/ }); - within(table).getByRole("row", { name: /dev\/sdb 2048/ }); - within(table).getByRole("row", { name: /dev\/system\/vg 1 logical/ }); - within(table).getByRole("row", { name: /dev\/sda1 512/ }); - within(table).getByRole("row", { name: /dev\/sda2 512/ }); - within(table).getByRole("row", { name: /Personal Data/ }); - }); - - it("renders a expand toggler in items with children", () => { - plainRender(); - const table = screen.getByRole("grid"); - const sdaRow = within(table).getByRole("row", { name: /dev\/sda 1024/ }); - const sdbRow = within(table).getByRole("row", { name: /dev\/sdb 2048/ }); - const lvRow = within(table).getByRole("row", { name: /dev\/system\/vg 1 logical/ }); - - within(sdaRow).getByRole("button", { name: "Details" }); - within(lvRow).getByRole("button", { name: "Details" }); - // `/dev/sdb` does not have children, toggler must not be there - const sdbChildrenToggler = within(sdbRow).queryByRole("button", { name: "Details" }); - expect(sdbChildrenToggler).toBeNull(); - }); - - it("renders as expanded items which value for `itemIdKey` is included in `initialExpandedKeys` prop", () => { - plainRender( - , - ); - const table = screen.getByRole("grid"); - within(table).getByRole("row", { name: /dev\/sda1 512/ }); - within(table).getByRole("row", { name: /dev\/sda2 512/ }); - }); - - it("keeps track of expanded items", async () => { - const { user } = plainRender( - , - ); - const table = screen.getByRole("grid"); - const sdaRow = within(table).getByRole("row", { name: /sda 1024/ }); - const sdaToggler = within(sdaRow).getByRole("button", { name: "Details" }); - const vgRow = within(table).getByRole("row", { name: /vg 1 logical/ }); - const vgToggler = within(vgRow).getByRole("button", { name: "Details" }); - - within(table).getByRole("row", { name: /dev\/sda1 512/ }); - within(table).getByRole("row", { name: /dev\/sda2 512/ }); - - await user.click(vgToggler); - - within(table).getByRole("row", { name: /Personal Data/ }); - - await user.click(sdaToggler); - const sdaPartitionsRows = within(table).queryAllByRole("row", { name: /sda[d] 512/ }); - expect(sdaPartitionsRows.length).toEqual(0); - }); - - it("uses 'id' as key when `itemIdKey` prop is not given", () => { - plainRender(); - - const table = screen.getByRole("grid"); - // Since itemIdKey does not match the id used for the item, they are - // collapsed by default and their children are not visible - const sdaChild = within(table).queryByRole("row", { name: /dev\/sda1 512/ }); - expect(sdaChild).toBeNull(); - }); - - it("uses given `itemIdKey` as key", () => { - plainRender( - , - ); - - const table = screen.getByRole("grid"); - // Since itemIdKey === "name", "/dev/sda" is properly mounted as expanded. Its - // children must be visible - const sdaChild = within(table).queryByRole("row", { name: /dev\/sda1 512/ }); - expect(sdaChild).not.toBeNull(); - }); - - describe("when `itemsSelected` is given", () => { - it("renders nothing as checked if value is an empty array", () => { - plainRender(); - const table = screen.getByRole("grid"); - const selection = within(table).queryAllByRole("radio", { checked: true }); - expect(selection.length).toEqual(0); - }); - - describe("but it isn't an array", () => { - it("outputs to console.error", () => { - plainRender(); - expect(console.error).toHaveBeenCalledWith( - expect.stringContaining("prop must be an array"), - "Whatever", - ); - }); - - it("renders nothing as selected", () => { - plainRender(); - const table = screen.getByRole("grid"); - const selection = within(table).queryAllByRole("radio", { checked: true }); - expect(selection.length).toEqual(0); - }); - }); - }); - - describe("when mounted as single selector", () => { - describe.each([undefined, null, false])("because isMultiple={%s}", (isMultiple) => { - beforeEach(() => { - props = { ...props, isMultiple }; - }); - - it("renders a radio per item row", () => { - plainRender(); - const table = screen.getByRole("grid"); - const radios = within(table).getAllByRole("radio"); - expect(radios.length).toEqual(6); - }); - - describe("but `itemSelectable` is given", () => { - it("renders a radio only for items for which it returns true", () => { - const itemSelectable = (item) => item.isDrive || item.type === "vg"; - plainRender(); - const table = screen.getByRole("grid"); - const radios = within(table).getAllByRole("radio"); - - // Expected only three radios - expect(radios.length).toEqual(3); - - // Not in below items - const sda1Row = within(table).getByRole("row", { name: /dev\/sda1/ }); - const sda2Row = within(table).getByRole("row", { name: /dev\/sda2/ }); - const lv1Row = within(table).getByRole("row", { name: /lv1/ }); - expect(within(sda1Row).queryAllByRole("radio")).toEqual([]); - expect(within(sda2Row).queryAllByRole("radio")).toEqual([]); - expect(within(lv1Row).queryAllByRole("radio")).toEqual([]); - }); - }); - - describe("and `itemsSelected` is given", () => { - describe("and it is an array with just one item", () => { - it("renders it as checked", async () => { - plainRender(); - const table = screen.getByRole("grid"); - const sda1Row = within(table).getByRole("row", { name: /dev\/sda1/ }); - const selection = screen.getAllByRole("radio", { checked: true }); - expect(selection.length).toEqual(1); - within(sda1Row).getByRole("radio", { checked: true }); - }); - }); - - describe("but it is an array with more than one item", () => { - it("outputs to console.error", () => { - plainRender(); - expect(console.error).toHaveBeenCalledWith( - expect.stringContaining("Using only the first element"), - ); - }); - - it("renders the first one as checked", async () => { - plainRender(); - const table = screen.getByRole("grid"); - const selection = screen.getAllByRole("radio", { checked: true }); - const sda1Row = within(table).getByRole("row", { name: /dev\/sda1/ }); - const lv1Row = within(table).getByRole("row", { name: /Personal Data/ }); - const lv1Radio = within(lv1Row).getByRole("radio"); - within(sda1Row).getByRole("radio", { checked: true }); - expect(lv1Radio).not.toHaveAttribute("checked", true); - expect(selection.length).toEqual(1); - }); - }); - }); - - describe("and user selects an already selected item", () => { - it("does not trigger the `onSelectionChange` callback", async () => { - const { user } = plainRender(); - const sda1row = screen.getByRole("row", { name: /dev\/sda1/ }); - const sda1radio = within(sda1row).getByRole("radio"); - await user.click(sda1radio); - expect(onChangeFn).not.toHaveBeenCalled(); - }); - }); - - describe("and user selects a not selected item", () => { - it("calls the `onSelectionChange` callback with a collection holding only selected item", async () => { - const { user } = plainRender(); - const sda2row = screen.getByRole("row", { name: /dev\/sda2/ }); - const sda2radio = within(sda2row).getByRole("radio"); - await user.click(sda2radio); - expect(onChangeFn).toHaveBeenCalledWith([sda2]); - }); - }); - }); - }); - - describe("when mounted as multiple selector", () => { - beforeEach(() => { - props = { ...props, isMultiple: true }; - }); - - it("renders a checkbox per item row", () => { - plainRender(); - const table = screen.getByRole("grid"); - const checkboxes = within(table).getAllByRole("checkbox"); - expect(checkboxes.length).toEqual(6); - }); - - describe("but `itemSelectable` is given", () => { - it("renders a checkbox only for items for which it returns true", () => { - const itemSelectable = (item) => item.isDrive || item.type === "vg"; - plainRender(); - const table = screen.getByRole("grid"); - const checkboxes = within(table).getAllByRole("checkbox"); - - // Expected only three checkboxes - expect(checkboxes.length).toEqual(3); - - // Not in below items - const sda1Row = within(table).getByRole("row", { name: /dev\/sda1/ }); - const sda2Row = within(table).getByRole("row", { name: /dev\/sda2/ }); - const lv1Row = within(table).getByRole("row", { name: /lv1/ }); - expect(within(sda1Row).queryAllByRole("checkbox")).toEqual([]); - expect(within(sda2Row).queryAllByRole("checkbox")).toEqual([]); - expect(within(lv1Row).queryAllByRole("checkbox")).toEqual([]); - }); - }); - - describe("and `itemsSelected` is given", () => { - it("renders given items as checked", async () => { - plainRender(); - const table = screen.getByRole("grid"); - const selection = screen.getAllByRole("checkbox", { checked: true }); - const sda1Row = within(table).getByRole("row", { name: /dev\/sda1/ }); - const lv1Row = within(table).getByRole("row", { name: /Personal Data/ }); - within(sda1Row).getByRole("checkbox", { checked: true }); - within(lv1Row).getByRole("checkbox", { checked: true }); - expect(selection.length).toEqual(2); - }); - }); - - it("renders initially selected items given via `itemsSelected` prop", async () => { - plainRender(); - const table = screen.getByRole("grid"); - const sda1Row = within(table).getByRole("row", { name: /dev\/sda1/ }); - const lv1Row = within(table).getByRole("row", { name: /Personal Data/ }); - const selection = screen.getAllByRole("checkbox", { checked: true }); - expect(selection.length).toEqual(2); - [sda1Row, lv1Row].forEach((row) => within(row).getByRole("checkbox", { checked: true })); - }); - - describe("and user selects an already selected item", () => { - it("triggers the `onSelectionChange` callback with a collection not including the item", async () => { - const { user } = plainRender( - , - ); - const sda1row = screen.getByRole("row", { name: /dev\/sda1/ }); - const sda1radio = within(sda1row).getByRole("checkbox"); - await user.click(sda1radio); - expect(onChangeFn).toHaveBeenCalledWith([sda2]); - }); - }); - - describe("and user selects a not selected item", () => { - it("calls the `onSelectionChange` callback with a collection including the item", async () => { - const { user } = plainRender(); - const sda2row = screen.getByRole("row", { name: /dev\/sda2/ }); - const sda2checkbox = within(sda2row).getByRole("checkbox"); - await user.click(sda2checkbox); - expect(onChangeFn).toHaveBeenCalledWith([sda1, sda2]); - }); - }); - }); -}); diff --git a/web/src/components/core/ExpandableSelector.tsx b/web/src/components/core/ExpandableSelector.tsx deleted file mode 100644 index b8c04f58fa..0000000000 --- a/web/src/components/core/ExpandableSelector.tsx +++ /dev/null @@ -1,274 +0,0 @@ -/* - * 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 the GNU General Public License as published by the Free - * Software Foundation; either version 2 of the License, or (at your option) - * any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU 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, { useState } from "react"; -import { - Table, - TableProps, - Thead, - Tr, - Th, - Tbody, - Td, - ExpandableRowContent, - RowSelectVariant, -} from "@patternfly/react-table"; - -/* eslint-disable @typescript-eslint/no-explicit-any */ - -/** - * An object for sharing data across nested maps - * - * Since function arguments are always passed by value, an object passed by - * sharing is needed for sharing data that might be mutated from different - * places, as it is the case of the rowIndex prop here. - * - * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions#passing_arguments - */ - -type SharedData = { - rowIndex: number; -}; - -export type ExpandableSelectorColumn = { - /** The column header text */ - name: string; - /** A function receiving the item to work with and returns the column value */ - value: (item: object) => React.ReactNode; - /** Space-separated list of additional CSS class names */ - classNames?: string; -}; - -export type ExpandableSelectorProps = { - /** Collection of objects defining columns. */ - columns?: ExpandableSelectorColumn[]; - /** Whether multiple selection is allowed. */ - isMultiple?: boolean; - /** Collection of items to be rendered. */ - items?: object[]; - /** The key for retrieving the item id. */ - itemIdKey?: string; - /** Lookup method to retrieve children from given item. */ - itemChildren?: (item: object) => object[]; - /** Whether an item will be selectable or not. */ - itemSelectable?: (item: object) => boolean; - /** Callback to add additional CSS class names to item row. */ - itemClassNames?: (item: object) => string | undefined; - /** Collection of selected items. */ - itemsSelected?: object[]; - /** Ids of initially expanded items. */ - initialExpandedKeys?: any[]; - /** Callback to be triggered when selection changes. */ - onSelectionChange?: (selection: object[]) => void; -} & TableProps; - -/** - * Internal component for building the table header - */ -const TableHeader = ({ columns }: { columns: ExpandableSelectorColumn[] }) => ( - - - - - {columns?.map((c, i) => ( - - {c.name} - - ))} - - -); - -/** - * Helper function for ensuring a good value for ExpandableSelector#itemsSelected prop - * - * It logs information to console.error if given value does not match - * expectations. - * - * @param selection - The value to check. - * @param allowMultiple - Whether the returned collection can have - * more than one item - * @return Empty array if given value is not valid. The first element if - * it is a collection with more than one but selector does not allow multiple. - * The original value otherwise. - */ -const sanitizeSelection = (selection: any[], allowMultiple: boolean): any[] => { - if (!Array.isArray(selection)) { - console.error("`itemSelected` prop must be an array. Ignoring given value", selection); - return []; - } - - if (!allowMultiple && selection.length > 1) { - console.error( - "`itemsSelected` prop can only have more than one item when selector `isMultiple`. " + - "Using only the first element", - ); - - return [selection[0]]; - } - - return selection; -}; - -/** - * Build a expandable table with selectable items. - * @component - * - * @note It only accepts one nesting level. - * - * @param {ExpandableSelectorProps} props - */ -export default function ExpandableSelector({ - columns = [], - isMultiple = false, - items = [], - itemIdKey = "id", - itemChildren = () => [], - itemSelectable = () => true, - itemClassNames = () => "", - itemsSelected = [], - initialExpandedKeys = [], - onSelectionChange, - ...tableProps -}: ExpandableSelectorProps) { - const [expandedItemsKeys, setExpandedItemsKeys] = useState(initialExpandedKeys); - const selection = sanitizeSelection(itemsSelected, isMultiple); - const isItemSelected = (item: object) => { - const selected = selection.find((selectionItem) => { - return ( - Object.hasOwn(selectionItem, itemIdKey) && selectionItem[itemIdKey] === item[itemIdKey] - ); - }); - - return selected !== undefined || selection.includes(item); - }; - const isItemExpanded = (key: string | number) => expandedItemsKeys.includes(key); - const toggleExpanded = (key: string | number) => { - if (isItemExpanded(key)) { - setExpandedItemsKeys(expandedItemsKeys.filter((k) => k !== key)); - } else { - setExpandedItemsKeys([...expandedItemsKeys, key]); - } - }; - - const updateSelection = (item: object) => { - if (!isMultiple) { - onSelectionChange([item]); - return; - } - - if (isItemSelected(item)) { - onSelectionChange(selection.filter((i) => i !== item)); - } else { - onSelectionChange([...selection, item]); - } - }; - - /** - * Render method for building the markup for an item child - * - * @param item - The child to be rendered - * @param isExpanded - Whether the child should be shown or not - * @param sharedData - An object holding shared data - */ - const renderItemChild = (item: object, isExpanded: boolean, sharedData: SharedData) => { - const rowIndex = sharedData.rowIndex++; - - const selectProps = { - rowIndex, - onSelect: () => updateSelection(item), - isSelected: isItemSelected(item), - variant: isMultiple ? RowSelectVariant.checkbox : RowSelectVariant.radio, - }; - - return ( - - - - {columns?.map((column, index) => ( - - {column.value(item)} - - ))} - - ); - }; - - /** - * Render method for building the markup for item - * - * @param item - The item to be rendered - * @param sharedData - An object holding shared data - */ - const renderItem = (item: object, sharedData: SharedData) => { - const itemKey = item[itemIdKey]; - const rowIndex = sharedData.rowIndex++; - const children = itemChildren(item); - const validChildren = Array.isArray(children) && children.length > 0; - const expandProps = validChildren && { - rowIndex, - isExpanded: isItemExpanded(itemKey), - onToggle: () => toggleExpanded(itemKey), - }; - - const selectProps = { - rowIndex, - onSelect: () => updateSelection(item), - isSelected: isItemSelected(item), - variant: isMultiple ? RowSelectVariant.checkbox : RowSelectVariant.radio, - }; - - const renderChildren = () => { - if (!validChildren) return; - - return children.map((item) => renderItemChild(item, isItemExpanded(itemKey), sharedData)); - }; - - // TODO: Add label to Tbody? - return ( - - - - - {columns?.map((column, index) => ( - - {column.value(item)} - - ))} - - {renderChildren()} - - ); - }; - - // @see SharedData - const sharedData = { rowIndex: 0 }; - - const TableBody = () => items?.map((item) => renderItem(item, sharedData)); - - return ( - - - -
- ); -} diff --git a/web/src/components/core/index.ts b/web/src/components/core/index.ts index e77cbce6a3..d2e7babb03 100644 --- a/web/src/components/core/index.ts +++ b/web/src/components/core/index.ts @@ -40,7 +40,6 @@ export { default as ProgressReport } from "./ProgressReport"; export { default as ProgressText } from "./ProgressText"; export { default as PasswordInput } from "./PasswordInput"; export { default as ServerError } from "./ServerError"; -export { default as ExpandableSelector } from "./ExpandableSelector"; export { default as TreeTable } from "./TreeTable"; export { default as Link } from "./Link"; export { default as EmptyState } from "./EmptyState"; diff --git a/web/src/components/storage/DeviceSelection.tsx b/web/src/components/storage/DeviceSelection.tsx deleted file mode 100644 index 5fc87aae9e..0000000000 --- a/web/src/components/storage/DeviceSelection.tsx +++ /dev/null @@ -1,215 +0,0 @@ -/* - * 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 the GNU General Public License as published by the Free - * Software Foundation; either version 2 of the License, or (at your option) - * any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU 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, { useEffect, useState } from "react"; -import { useNavigate } from "react-router-dom"; -import { Flex, Form, FormGroup, Radio, Stack } from "@patternfly/react-core"; -import { Page } from "~/components/core"; -import { DeviceSelectorTable } from "~/components/storage"; -import DevicesTechMenu from "./DevicesTechMenu"; -import { ProposalTarget, StorageDevice } from "~/types/storage"; -import { useAvailableDevices, useProposalMutation, useProposalResult } from "~/queries/storage"; -import { deviceChildren } from "~/components/storage/utils"; -import { compact } from "~/utils"; -import a11y from "@patternfly/react-styles/css/utilities/Accessibility/accessibility"; -import { _ } from "~/i18n"; - -const SELECT_DISK_ID = "select-disk"; -const CREATE_LVM_ID = "create-lvm"; -const SELECT_DISK_PANEL_ID = "panel-for-disk-selection"; -const CREATE_LVM_PANEL_ID = "panel-for-lvm-creation"; - -type DeviceSelectionState = { - target?: ProposalTarget; - targetDevice?: StorageDevice; - targetPVDevices?: StorageDevice[]; -}; - -/** - * Allows the user to select a target device for installation. - * @component - */ -export default function DeviceSelection() { - const proposal = useProposalResult(); - const availableDevices = useAvailableDevices(); - const updateProposal = useProposalMutation(); - const navigate = useNavigate(); - const [state, setState] = useState({}); - - const isTargetDisk = state.target === ProposalTarget.DISK; - const isTargetNewLvmVg = state.target === ProposalTarget.NEW_LVM_VG; - - useEffect(() => { - if (state.target !== undefined) return; - - // FIXME: move to a state/reducer - setState({ - target: proposal.settings.target, - targetDevice: availableDevices.find((d) => d.name === proposal.settings.targetDevice), - targetPVDevices: availableDevices.filter((d) => - proposal.settings.targetPVDevices?.includes(d.name), - ), - }); - }, [proposal, availableDevices, state.target]); - - const selectTargetDisk = () => setState({ ...state, target: ProposalTarget.DISK }); - const selectTargetNewLvmVG = () => setState({ ...state, target: ProposalTarget.NEW_LVM_VG }); - - const selectTargetDevice = (devices: StorageDevice[]) => - setState({ ...state, targetDevice: devices[0] }); - const selectTargetPVDevices = (devices: StorageDevice[]) => { - setState({ ...state, targetPVDevices: devices }); - }; - - const onSubmit = async (e) => { - e.preventDefault(); - const newSettings = { - target: state.target, - targetDevice: isTargetDisk ? state.targetDevice?.name : "", - targetPVDevices: isTargetNewLvmVg ? state.targetPVDevices.map((d) => d.name) : [], - }; - - updateProposal.mutateAsync({ ...proposal.settings, ...newSettings }); - navigate(".."); - }; - - const isAcceptDisabled = () => { - if (isTargetDisk) return state.targetDevice === undefined; - if (isTargetNewLvmVg) return state.targetPVDevices?.length === 0; - - return true; - }; - - const isDeviceSelectable = (device: StorageDevice) => device.isDrive || device.type === "md"; - - // TRANSLATORS: description for using plain partitions for installing the - // system, the text in the square brackets [] is displayed in bold, use only - // one pair in the translation - const [msgStart1, msgBold1, msgEnd1] = _( - "The file systems will be allocated \ -by default as [new partitions in the selected device].", - ).split(/[[\]]/); - // TRANSLATORS: description for using logical volumes for installing the - // system, the text in the square brackets [] is displayed in bold, use only - // one pair in the translation - const [msgStart2, msgBold2, msgEnd2] = _( - "The file systems will be allocated \ -by default as [logical volumes of a new LVM Volume Group]. The corresponding \ -physical volumes will be created on demand as new partitions at the selected \ -devices.", - ).split(/[[\]]/); - - return ( - - -

{_("Select installation device")}

-
- - -
- - - - - - - - - -
- {msgStart1} - {msgBold1} - {msgEnd1} -
- - -
- - -
- {msgStart2} - {msgBold2} - {msgEnd2} -
- -
- -
-
- - - {_("Prepare more devices by configuring advanced")} - - -
-
-
-
- - - - - -
- ); -} diff --git a/web/src/components/storage/DeviceSelectorTable.tsx b/web/src/components/storage/DeviceSelectorTable.tsx deleted file mode 100644 index 1372dc2a8d..0000000000 --- a/web/src/components/storage/DeviceSelectorTable.tsx +++ /dev/null @@ -1,183 +0,0 @@ -/* - * 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 the GNU General Public License as published by the Free - * Software Foundation; either version 2 of the License, or (at your option) - * any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU 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 { - DeviceName, - DeviceDetails, - DeviceSize, - FilesystemLabel, - toStorageDevice, -} from "~/components/storage/device-utils"; -import { ExpandableSelector } from "~/components/core"; -import { Icon } from "~/components/layout"; -import { _ } from "~/i18n"; -import { sprintf } from "sprintf-js"; -import { deviceBaseName } from "~/components/storage/utils"; -import { PartitionSlot, StorageDevice } from "~/types/storage"; -import { ExpandableSelectorColumn, ExpandableSelectorProps } from "../core/ExpandableSelector"; -import { typeDescription, contentDescription } from "./utils/device"; -import { DeviceInfo as DeviceInfoType } from "~/api/storage/types"; - -/** - * @component - */ -const DeviceInfo = ({ item }: { item: PartitionSlot | StorageDevice }) => { - const device = toStorageDevice(item); - if (!device) return null; - - const DeviceType = () => { - const type = typeDescription(device); - - return type &&
{type}
; - }; - - const DeviceModel = () => { - if (!device.model || device.model === "") return null; - - return
{device.model}
; - }; - - const MDInfo = () => { - if (device.type !== "md" || !device.devices) return null; - - const members = device.devices.map(deviceBaseName); - - // TRANSLATORS: RAID details, %s is replaced by list of devices used by the array - return
{sprintf(_("Members: %s"), members.sort().join(", "))}
; - }; - - const RAIDInfo = () => { - if (device.type !== "raid") return null; - - const devices = device.devices.map(deviceBaseName); - - // TRANSLATORS: RAID details, %s is replaced by list of devices used by the array - return
{sprintf(_("Devices: %s"), devices.sort().join(", "))}
; - }; - - const MultipathInfo = () => { - if (device.type !== "multipath") return null; - - const wires = device.wires.map(deviceBaseName); - - // TRANSLATORS: multipath details, %s is replaced by list of connections used by the device - return
{sprintf(_("Wires: %s"), wires.sort().join(", "))}
; - }; - - return ( -
- - - - - - -
- ); -}; - -/** - * @component - */ -const DeviceExtendedDetails = ({ item }: { item: PartitionSlot | StorageDevice }) => { - const device = toStorageDevice(item); - - if (!device || ["partition", "lvmLv"].includes(device.type)) return ; - - const Description = () => { - return ( -
- {contentDescription(device)} -
- ); - }; - - const Systems = () => { - if (!device.systems || device.systems.length === 0) return null; - - const System = ({ system }) => { - const isWindows = /windows/i.test(system); - - if (isWindows) return
{system}
; - - return ( -
- {system} -
- ); - }; - - return device.systems.map((s, i) => ); - }; - - return ( -
- - -
- ); -}; - -const columns: ExpandableSelectorColumn[] = [ - { name: _("Device"), value: (item: PartitionSlot | StorageDevice) => }, - { - name: _("Details"), - value: (item: PartitionSlot | StorageDevice) => , - }, - { - name: _("Size"), - value: (item: PartitionSlot | StorageDevice) => , - classNames: "sizes-column", - }, -]; - -type DeviceSelectorTableProps = { - devices: StorageDevice[]; - selectedDevices: StorageDevice[]; -} & ExpandableSelectorProps; - -/** - * Table for selecting the installation device. - * @component - */ -export default function DeviceSelectorTable({ - devices, - selectedDevices, - ...props -}: DeviceSelectorTableProps) { - return ( - { - if (!device.sid) { - return "dimmed-row"; - } - }} - itemsSelected={selectedDevices} - className="devices-table" - {...props} - /> - ); -} diff --git a/web/src/components/storage/DevicesTechMenu.test.tsx b/web/src/components/storage/DevicesTechMenu.test.tsx deleted file mode 100644 index 5b8bf07358..0000000000 --- a/web/src/components/storage/DevicesTechMenu.test.tsx +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (c) [2023-2025] SUSE LLC - * - * All Rights Reserved. - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation; either version 2 of the License, or (at your option) - * any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU 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 { screen } from "@testing-library/react"; -import { installerRender } from "~/test-utils"; -import DevicesTechMenu from "./DevicesTechMenu"; -import { supportedDASD } from "~/api/storage/dasd"; -import { supportedZFCP } from "~/api/storage/zfcp"; - -jest.mock("~/api/storage/dasd"); -jest.mock("~/api/storage/zfcp"); - -beforeEach(() => { - (supportedDASD as jest.Mock).mockResolvedValue(false); - (supportedZFCP as jest.Mock).mockResolvedValue(false); -}); - -it("contains an entry for configuring iSCSI", async () => { - const { user } = installerRender(); - const toggler = screen.getByRole("button"); - await user.click(toggler); - const link = screen.getByRole("option", { name: /iSCSI/ }); - expect(link).toHaveAttribute("href", "/storage/iscsi"); -}); - -it("does not contain an entry for configuring DASD when is NOT supported", async () => { - const { user } = installerRender(); - const toggler = screen.getByRole("button"); - await user.click(toggler); - expect(screen.queryByRole("option", { name: /DASD/ })).toBeNull(); -}); - -it("contains an entry for configuring DASD when is supported", async () => { - (supportedDASD as jest.Mock).mockResolvedValue(true); - const { user } = installerRender(); - const toggler = screen.getByRole("button"); - await user.click(toggler); - const link = screen.getByRole("option", { name: /DASD/ }); - expect(link).toHaveAttribute("href", "/storage/dasd"); -}); - -it("does not contain an entry for configuring zFCP when is NOT supported", async () => { - const { user } = installerRender(); - const toggler = screen.getByRole("button"); - await user.click(toggler); - expect(screen.queryByRole("option", { name: /DASD/ })).toBeNull(); -}); - -it("contains an entry for configuring zFCP when is supported", async () => { - (supportedZFCP as jest.Mock).mockResolvedValue(true); - const { user } = installerRender(); - const toggler = screen.getByRole("button"); - await user.click(toggler); - const link = screen.getByRole("option", { name: /zFCP/ }); - expect(link).toHaveAttribute("href", "/storage/zfcp"); -}); diff --git a/web/src/components/storage/DevicesTechMenu.tsx b/web/src/components/storage/DevicesTechMenu.tsx deleted file mode 100644 index 20cf3adc56..0000000000 --- a/web/src/components/storage/DevicesTechMenu.tsx +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright (c) [2023-2024] SUSE LLC - * - * All Rights Reserved. - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation; either version 2 of the License, or (at your option) - * any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU 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, { useEffect, useState } from "react"; -import { useHref } from "react-router-dom"; -import { - MenuToggle, - MenuToggleElement, - Select, - SelectList, - SelectOption, -} from "@patternfly/react-core"; -import { _ } from "~/i18n"; -import { supportedDASD } from "~/api/storage/dasd"; -import { supportedZFCP } from "~/api/storage/zfcp"; - -/** - * Internal component for building the link to Storage/DASD page - */ -const DASDLink = () => { - const href = useHref("/storage/dasd"); - - return ( - - DASD - - ); -}; - -/** - * Internal component for building the link to Storage/zFCP page - */ -const ZFCPLink = () => { - const href = useHref("/storage/zfcp"); - - return ( - - {_("zFCP")} - - ); -}; - -/** - * Internal component for building the link to Storage/iSCSI page - * @component - */ -const ISCSILink = () => { - const href = useHref("/storage/iscsi"); - - return ( - - {_("iSCSI")} - - ); -}; - -type ProposalMenuProps = { - label: string; -}; - -/** - * Component for rendering the options available from Storage/ProposalPage - */ -export default function DevicesTechMenu({ label }: ProposalMenuProps) { - const [isOpen, setIsOpen] = useState(false); - const [showDasdLink, setShowDasdLink] = useState(false); - const [showZFCPLink, setShowZFCPLink] = useState(false); - - useEffect(() => { - supportedDASD().then(setShowDasdLink); - supportedZFCP().then(setShowZFCPLink); - }, []); - const toggle = (toggleRef: React.Ref) => ( - setIsOpen(!isOpen)} isExpanded={isOpen}> - {label} - - ); - - const onSelect = () => { - setIsOpen(false); - }; - - return ( - - ); -} diff --git a/web/src/components/storage/ProposalPage.test.tsx b/web/src/components/storage/ProposalPage.test.tsx index bd1cc78388..c4e24e7f47 100644 --- a/web/src/components/storage/ProposalPage.test.tsx +++ b/web/src/components/storage/ProposalPage.test.tsx @@ -29,13 +29,7 @@ import React from "react"; import { screen } from "@testing-library/react"; import { installerRender } from "~/test-utils"; import { ProposalPage } from "~/components/storage"; -import { - ProposalResult, - ProposalTarget, - StorageDevice, - Volume, - VolumeTarget, -} from "~/types/storage"; +import { Action, StorageDevice, Volume, VolumeTarget } from "~/types/storage"; jest.mock("~/queries/issues", () => ({ ...jest.requireActual("~/queries/issues"), @@ -105,22 +99,7 @@ const volume = (mountPath: string): Volume => { }; }; -const mockProposalResult: ProposalResult = { - settings: { - target: ProposalTarget.DISK, - targetPVDevices: [], - configureBoot: false, - bootDevice: "", - defaultBootDevice: "", - encryptionPassword: "", - encryptionMethod: "", - spacePolicy: "", - spaceActions: [], - volumes: [], - installationDevices: [], - }, - actions: [], -}; +const mockActions: Action[] = []; jest.mock("~/queries/storage", () => ({ ...jest.requireActual("~/queries/storage"), @@ -132,7 +111,7 @@ jest.mock("~/queries/storage", () => ({ encryptionMethods: [], mountPoints: ["/", "swap"], }), - useProposalResult: () => mockProposalResult, + useActions: () => mockActions, useDeprecated: () => false, useDeprecatedChanges: jest.fn(), useProposalMutation: jest.fn(), diff --git a/web/src/components/storage/ProposalPage.tsx b/web/src/components/storage/ProposalPage.tsx index 9f8347be87..8ceb3dfd33 100644 --- a/web/src/components/storage/ProposalPage.tsx +++ b/web/src/components/storage/ProposalPage.tsx @@ -36,7 +36,7 @@ import { useDevices, useDeprecated, useDeprecatedChanges, - useProposalResult, + useActions, useReprobeMutation, } from "~/queries/storage"; import { _ } from "~/i18n"; @@ -69,7 +69,7 @@ export default function ProposalPage() { const stagingDevices = useDevices("result"); const isDeprecated = useDeprecated(); const { mutateAsync: reprobe } = useReprobeMutation(); - const { actions } = useProposalResult(); + const actions = useActions(); useDeprecatedChanges(); diff --git a/web/src/components/storage/VolumeLocationSelectorTable.tsx b/web/src/components/storage/VolumeLocationSelectorTable.tsx deleted file mode 100644 index 764a9c3e38..0000000000 --- a/web/src/components/storage/VolumeLocationSelectorTable.tsx +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright (c) [2024-2025] SUSE LLC - * - * All Rights Reserved. - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation; either version 2 of the License, or (at your option) - * any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU 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 { Content, Split } from "@patternfly/react-core"; -import { _ } from "~/i18n"; -import { - DeviceName, - DeviceDetails, - DeviceSize, - toStorageDevice, -} from "~/components/storage/device-utils"; -import { ExpandableSelector } from "~/components/core"; -import { - ExpandableSelectorColumn, - ExpandableSelectorProps, -} from "~/components/core/ExpandableSelector"; -import { PartitionSlot, StorageDevice, Volume } from "~/types/storage"; -import { DeviceInfo } from "~/api/storage/types"; - -/** - * Returns what (volumes, installation device) is using a device. - */ -const deviceUsers = ( - item: PartitionSlot | StorageDevice, - targetDevices: StorageDevice[], - volumes: Volume[], -): string[] => { - const device = toStorageDevice(item); - if (!device) return []; - - const isTargetDevice = !!targetDevices.find((d) => d.name === device.name); - const volumeUsers = volumes.filter((v) => v.targetDevice?.name === device.name); - - const users = []; - if (isTargetDevice) users.push(_("Installation device")); - - return users.concat(volumeUsers.map((v) => v.mountPath)); -}; - -/** - * @component - */ -const DeviceUsage = ({ users }: { users: string[] }) => { - return ( - - {users.map((user, index) => ( - {user} - ))} - - ); -}; - -type VolumeLocationSelectorTableBaseProps = { - devices: StorageDevice[]; - selectedDevices: StorageDevice[]; - targetDevices: StorageDevice[]; - volumes: Volume[]; -}; - -export type VolumeLocationSelectorTableProps = VolumeLocationSelectorTableBaseProps & - ExpandableSelectorProps; - -/** - * Table for selecting the location for a volume. - * @component - */ -export default function VolumeLocationSelectorTable({ - devices, - selectedDevices, - targetDevices, - volumes, - ...props -}: VolumeLocationSelectorTableProps) { - const columns: ExpandableSelectorColumn[] = [ - { - name: _("Device"), - value: (item: PartitionSlot | StorageDevice) => , - }, - { - name: _("Details"), - value: (item: PartitionSlot | StorageDevice) => , - }, - { - name: _("Usage"), - value: (item: PartitionSlot | StorageDevice) => ( - - ), - }, - { - name: _("Size"), - value: (item: PartitionSlot | StorageDevice) => , - classNames: "sizes-column", - }, - ]; - - return ( - { - if (!device.sid) { - return "dimmed-row"; - } - }} - itemsSelected={selectedDevices} - className="devices-table" - {...props} - /> - ); -} diff --git a/web/src/components/storage/index.ts b/web/src/components/storage/index.ts index 2e1a4044c4..8cb45ed580 100644 --- a/web/src/components/storage/index.ts +++ b/web/src/components/storage/index.ts @@ -26,7 +26,5 @@ export { default as ProposalActionsDialog } from "./ProposalActionsDialog"; export { default as ProposalResultSection } from "./ProposalResultSection"; export { default as ISCSIPage } from "./ISCSIPage"; export { default as BootSelection } from "./BootSelection"; -export { default as DeviceSelectorTable } from "./DeviceSelectorTable"; export { default as DevicesFormSelect } from "./DevicesFormSelect"; export { default as SpaceActionsTable } from "./SpaceActionsTable"; -export { default as DeviceSelection } from "./DeviceSelection"; diff --git a/web/src/queries/storage.ts b/web/src/queries/storage.ts index cd2b7c4f71..47d916c022 100644 --- a/web/src/queries/storage.ts +++ b/web/src/queries/storage.ts @@ -37,22 +37,10 @@ import { fetchUsableDevices, reprobe, } from "~/api/storage"; -import { calculate } from "~/api/storage/proposal"; import { fetchDevices, fetchDevicesDirty } from "~/api/storage/devices"; import { useInstallerClient } from "~/context/installer"; -import { - config, - ProductParams, - Volume as APIVolume, - ProposalSettingsPatch, -} from "~/api/storage/types"; -import { - ProposalSettings, - ProposalResult, - StorageDevice, - Volume, - VolumeTarget, -} from "~/types/storage"; +import { config, ProductParams, Volume as APIVolume } from "~/api/storage/types"; +import { Action, StorageDevice, Volume, VolumeTarget } from "~/types/storage"; import { QueryHookOptions } from "~/types/queries"; @@ -234,55 +222,11 @@ const proposalActionsQuery = { }; /** - * Hook that returns the current proposal (settings and actions). + * Hook that returns the actions to perform in the storage devices. */ -const useProposalResult = (): ProposalResult | undefined => { - const { data: actions } = useSuspenseQuery(proposalActionsQuery); - - return { actions }; -}; - -const useProposalMutation = () => { - const queryClient = useQueryClient(); - const query = { - mutationFn: (settings: ProposalSettings) => { - const buildHttpVolume = (volume: Volume): APIVolume => { - return { - autoSize: volume.autoSize, - fsType: volume.fsType, - maxSize: volume.maxSize, - minSize: volume.minSize, - mountOptions: [], - mountPath: volume.mountPath, - snapshots: volume.snapshots, - target: volume.target, - targetDevice: volume.targetDevice?.name, - }; - }; - - const buildHttpSettings = (settings: ProposalSettings): ProposalSettingsPatch => { - return { - bootDevice: settings.bootDevice, - configureBoot: settings.configureBoot, - encryptionMethod: settings.encryptionMethod, - encryptionPBKDFunction: settings.encryptionPBKDFunction, - encryptionPassword: settings.encryptionPassword, - spaceActions: settings.spacePolicy === "custom" ? settings.spaceActions : undefined, - spacePolicy: settings.spacePolicy, - target: settings.target, - targetDevice: settings.targetDevice, - targetPVDevices: settings.targetPVDevices, - volumes: settings.volumes?.map(buildHttpVolume), - }; - }; - - const httpSettings = buildHttpSettings(settings); - return calculate(httpSettings); - }, - onSuccess: () => queryClient.invalidateQueries({ queryKey: ["storage"] }), - }; - - return useMutation(query); +const useActions = (): Action[] | undefined => { + const { data } = useSuspenseQuery(proposalActionsQuery); + return data; }; const deprecatedQuery = { @@ -339,8 +283,7 @@ export { useProductParams, useVolumeTemplates, useVolumeDevices, - useProposalResult, - useProposalMutation, + useActions, useDeprecated, useDeprecatedChanges, useReprobeMutation, diff --git a/web/src/routes/storage.tsx b/web/src/routes/storage.tsx index 7a94b22553..3cd1f9bd71 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 SpacePolicySelection from "~/components/storage/SpacePolicySelection"; -import { DeviceSelection, ISCSIPage, ProposalPage } from "~/components/storage"; +import { ISCSIPage, ProposalPage } from "~/components/storage"; import { Route } from "~/types/routes"; import { supportedDASD, probeDASD } from "~/api/storage/dasd"; @@ -42,10 +42,6 @@ const routes = (): Route => ({ index: true, element: , }, - { - path: PATHS.targetDevice, - element: , - }, { path: PATHS.bootDevice, element: , diff --git a/web/src/types/storage.ts b/web/src/types/storage.ts index 01321780fc..ebba8e7724 100644 --- a/web/src/types/storage.ts +++ b/web/src/types/storage.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) [2024] SUSE LLC + * Copyright (c) [2024-2025] SUSE LLC * * All Rights Reserved. * @@ -84,11 +84,6 @@ type ShrinkingInfo = { unsupported?: string[]; }; -type ProposalResult = { - settings?: ProposalSettings; - actions: Action[]; -}; - type Action = { device: number; text: string; @@ -102,27 +97,6 @@ type SpacePolicyAction = { value: "delete" | "resizeIfNeeded"; }; -type ProposalSettings = { - target: ProposalTarget; - targetDevice?: string; - targetPVDevices: string[]; - configureBoot: boolean; - bootDevice: string; - defaultBootDevice: string; - encryptionPassword: string; - encryptionMethod: string; - encryptionPBKDFunction?: string; - spacePolicy: string; - spaceActions: SpaceAction[]; - volumes: Volume[]; - installationDevices: StorageDevice[]; -}; - -type SpaceAction = { - device: string; - action: "force_delete" | "resize" | "keep"; -}; - type Volume = { mountPath: string; target: VolumeTarget; @@ -147,17 +121,6 @@ type VolumeOutline = { sizeRelevantVolumes: string[]; }; -/** - * Enum for the possible proposal targets. - * - * @readonly - */ -enum ProposalTarget { - DISK = "disk", - NEW_LVM_VG = "newLvmVg", - REUSED_LVM_VG = "reusedLvmVg", -} - /** * Enum for the possible volume targets. * @@ -207,14 +170,11 @@ export type { ISCSINode, PartitionSlot, PartitionTable, - ProposalResult, - ProposalSettings, ShrinkingInfo, - SpaceAction, SpacePolicyAction, StorageDevice, Volume, VolumeOutline, }; -export { EncryptionMethods, ProposalTarget, VolumeTarget }; +export { EncryptionMethods, VolumeTarget };