diff --git a/web/package/agama-web-ui.changes b/web/package/agama-web-ui.changes index 8c310c0cda..515e683344 100644 --- a/web/package/agama-web-ui.changes +++ b/web/package/agama-web-ui.changes @@ -14,6 +14,12 @@ Fri Sep 27 13:00:05 UTC 2024 - Imobach Gonzalez Sosa - Properly translate the storage interface when switching the language of the UI (gh#agama-project/agama#1629). + +------------------------------------------------------------------- +Mon Sep 23 09:04:56 UTC 2024 - Knut Anderssen + +- Added confirmation dialog when formatting DASD devices as it is + considered a dangerous action (gh#openSUSE/agama#1618). ------------------------------------------------------------------- Fri Sep 20 11:42:25 UTC 2024 - Imobach Gonzalez Sosa diff --git a/web/src/components/storage/dasd/DASDTable.test.tsx b/web/src/components/storage/dasd/DASDTable.test.tsx index 2be3f30f12..78d765527b 100644 --- a/web/src/components/storage/dasd/DASDTable.test.tsx +++ b/web/src/components/storage/dasd/DASDTable.test.tsx @@ -39,8 +39,20 @@ describe("DASDTable", () => { beforeEach(() => { mockDASDDevices = [ { - id: "0.0.0200", + id: "0.0.0160", enabled: false, + deviceName: "", + deviceType: "", + formatted: false, + diag: false, + status: "offline", + accessType: "", + partitionInfo: "", + hexId: 0x160, + }, + { + id: "0.0.0200", + enabled: true, deviceName: "dasda", deviceType: "eckd", formatted: false, @@ -57,5 +69,51 @@ describe("DASDTable", () => { installerRender(); screen.getByText("active"); }); + + it("does not allow to perform any action if not selected any device", () => { + installerRender(); + const button = screen.getByRole("button", { name: "Perform an action" }); + expect(button).toHaveAttribute("disabled"); + }); + + describe("when there are some DASD selected", () => { + it("allows to perform a set of actions over them", async () => { + const { user } = installerRender(); + const selection = screen.getByRole("checkbox", { name: "Select row 0" }); + await user.click(selection); + const button = screen.getByRole("button", { name: "Perform an action" }); + expect(button).not.toHaveAttribute("disabled"); + await user.click(button); + screen.getByRole("menuitem", { name: "Format" }); + }); + + describe("and the user click on format", () => { + it("shows a confirmation dialog if all the devices are online", async () => { + const { user } = installerRender(); + const selection = screen.getByRole("checkbox", { name: "Select row 1" }); + await user.click(selection); + const button = screen.getByRole("button", { name: "Perform an action" }); + expect(button).not.toHaveAttribute("disabled"); + await user.click(button); + const format = screen.getByRole("menuitem", { name: "Format" }); + await user.click(format); + screen.getByRole("dialog", { name: "Format selected devices?" }); + }); + + it("shows a warning dialog if some device is offline", async () => { + const { user } = installerRender(); + let selection = screen.getByRole("checkbox", { name: "Select row 0" }); + await user.click(selection); + selection = screen.getByRole("checkbox", { name: "Select row 1" }); + await user.click(selection); + const button = screen.getByRole("button", { name: "Perform an action" }); + expect(button).not.toHaveAttribute("disabled"); + await user.click(button); + const format = screen.getByRole("menuitem", { name: "Format" }); + await user.click(format); + screen.getByRole("dialog", { name: "Cannot format all selected devices" }); + }); + }); + }); }); }); diff --git a/web/src/components/storage/dasd/DASDTable.tsx b/web/src/components/storage/dasd/DASDTable.tsx index e54646fa07..90983a2732 100644 --- a/web/src/components/storage/dasd/DASDTable.tsx +++ b/web/src/components/storage/dasd/DASDTable.tsx @@ -27,7 +27,11 @@ import { Dropdown, DropdownItem, DropdownList, + List, + ListItem, MenuToggle, + Stack, + Text, TextInputGroup, TextInputGroupMain, TextInputGroupUtilities, @@ -36,6 +40,7 @@ import { ToolbarGroup, ToolbarItem, } from "@patternfly/react-core"; +import { Popup } from "~/components/core"; import { Table, Thead, Tr, Th, Tbody, Td } from "@patternfly/react-table"; import { Page } from "~/components/core"; import { Icon } from "~/components/layout"; @@ -56,7 +61,7 @@ const columnData = (device: DASDDevice, column: { id: string; sortId?: string; l if (!device.enabled) data = ""; break; case "partitionInfo": - data = data.split(",").map((d) =>
{d}
); + data = data.split(",").map((d: string) =>
{d}
); break; } @@ -79,29 +84,64 @@ const columns = [ { id: "formatted", label: _("Formatted") }, { id: "partitionInfo", label: _("Partition Info") }, ]; +const DevicesList = ({ devices }) => ( + + {devices.map((d: DASDDevice) => ( + {d.id} + ))} + +); +const FormatNotPossible = ({ devices, onAccept }) => ( + + + + {_( + "Offline devices must be activated before formatting them. Please, unselect or activate the devices listed below and try it again", + )} + + + + + {_("Accept")} + + +); + +const FormatConfirmation = ({ devices, onCancel, onConfirm }) => ( + + + + {_( + "This action could destroy any data stored on the devices listed below. Please, confirm that you really want to continue.", + )} + + + + + + + + +); const Actions = ({ devices, isDisabled }: { devices: DASDDevice[]; isDisabled: boolean }) => { const { mutate: updateDASD } = useDASDMutation(); const { mutate: formatDASD } = useFormatDASDMutation(); const [isOpen, setIsOpen] = useState(false); + const [requestFormat, setRequestFormat] = useState(false); const onToggle = () => setIsOpen(!isOpen); const onSelect = () => setIsOpen(false); + const cancelFormatRequest = () => setRequestFormat(false); const deviceIds = devices.map((d) => d.id); + const offlineDevices = devices.filter((d) => !d.enabled); + const offlineDevicesSelected = offlineDevices.length > 0; 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); - - if (offline.length > 0) { - return false; - } - - return formatDASD(devices.map((d) => d.id)); - }; + const format = () => formatDASD(devices.map((d) => d.id)); const Action = ({ children, ...props }) => ( @@ -110,41 +150,57 @@ const Actions = ({ devices, isDisabled }: { devices: DASDDevice[]; isDisabled: b ); return ( - ( - - {/* TRANSLATORS: drop down menu label */} - {_("Perform an action")} - + <> + {requestFormat && offlineDevicesSelected && ( + + )} + + {requestFormat && !offlineDevicesSelected && ( + { + cancelFormatRequest(); + format(); + }} + /> )} - > - - {/** TRANSLATORS: drop down menu action, activate the device */} - - {_("Activate")} - - {/** TRANSLATORS: drop down menu action, deactivate the device */} - - {_("Deactivate")} - - - {/** TRANSLATORS: drop down menu action, enable DIAG access method */} - - {_("Set DIAG On")} - - {/** TRANSLATORS: drop down menu action, disable DIAG access method */} - - {_("Set DIAG Off")} - - - {/** TRANSLATORS: drop down menu action, format the disk */} - - {_("Format")} - - - + ( + + {/* TRANSLATORS: drop down menu label */} + {_("Perform an action")} + + )} + > + + {/** TRANSLATORS: drop down menu action, activate the device */} + + {_("Activate")} + + {/** TRANSLATORS: drop down menu action, deactivate the device */} + + {_("Deactivate")} + + + {/** TRANSLATORS: drop down menu action, enable DIAG access method */} + + {_("Set DIAG On")} + + {/** TRANSLATORS: drop down menu action, disable DIAG access method */} + + {_("Set DIAG Off")} + + + {/** TRANSLATORS: drop down menu action, format the disk */} + setRequestFormat(true)}> + {_("Format")} + + + + ); }; @@ -326,7 +382,7 @@ export default function DASDTable() { - +