diff --git a/web/package/cockpit-agama.changes b/web/package/cockpit-agama.changes index c204d48697..db0d9246d9 100644 --- a/web/package/cockpit-agama.changes +++ b/web/package/cockpit-agama.changes @@ -1,3 +1,9 @@ +------------------------------------------------------------------- +Mon Apr 24 15:53:35 UTC 2023 - David Diaz + +- Extract page options from the Sidebar to make them + more discoverable (gh#openSUSE/agama#545) + ------------------------------------------------------------------- Fri Apr 14 13:08:05 UTC 2023 - José Iván López González diff --git a/web/src/assets/styles/blocks.scss b/web/src/assets/styles/blocks.scss index ace68ee440..3bccf67918 100644 --- a/web/src/assets/styles/blocks.scss +++ b/web/src/assets/styles/blocks.scss @@ -1,7 +1,7 @@ // Standard section // In the future we might need to use an specific CSS class for it if we start having different // section layouts. -section { +section:not([class^="pf-c"]) { display: grid; grid-template-columns: var(--icon-size-m) 1fr; grid-template-areas: @@ -10,7 +10,7 @@ section { gap: var(--spacer-small); } -section:not(:last-child) { +section:not(:last-child, [class^="pf-c"]) { margin-block-end: var(--spacer-large); } @@ -196,3 +196,25 @@ section > .content { --pf-c-progress--GridGap: var(--spacer-small); } } + +.page-options > button { + --pf-c-button--PaddingRight: 0 +} + +.page-options a { + font-size: 16px; + font-weight: var(--fw-bold); + text-decoration: none; + + svg { + color: inherit; + } + + &:hover { + color: var(--color-link-hover); + + svg { + color: var(--color-link); + } + } +} diff --git a/web/src/components/core/PageOptions.jsx b/web/src/components/core/PageOptions.jsx index 42b3ab3937..a25a5a264e 100644 --- a/web/src/components/core/PageOptions.jsx +++ b/web/src/components/core/PageOptions.jsx @@ -19,64 +19,120 @@ * find current contact information at www.suse.com. */ -// @ts-check +import React, { useState } from 'react'; +import { Button, Dropdown, DropdownItem, DropdownGroup } from '@patternfly/react-core'; +import { Icon, PageOptions as PageOptionsSlot } from "~/components/layout"; -import React from "react"; -import { PageOptionsContent } from '~/components/layout'; +/** + * Internal component to build the {PageOptions} toggler + * @component + * + * @param {object} props + * @param {function} props.onClick + */ +const Toggler = ({ onClick }) => { + return ( + + ); +}; + +/** + * A group of actions belonging to a {PageOptions} component + * @component + * + * Built on top of {@link https://www.patternfly.org/v4/components/dropdown/#dropdowngroup PF DropdownGroup} + * + * @see {PageOptions } examples. + * + * @param {object} props - PF DropdownItem props, See {@link https://www.patternfly.org/v4/components/dropdowngroup} + */ +const Group = ({ children, ...props }) => { + return ( + + {children} + + ); +}; /** - * Wrapper for teleported page options that bubbles onClick - * event to the slot element. - * - * Needed to "dispatch" the onClick events bind to any - * parent on the DOM tree for "teleported nodes", bypassing the - * default React Portal behavior of bubbling events up through the - * React tree only. - * - * @example Simple usage - * - * Configure iSCSI devices - * + * + * Reprobe + * + * + * + * DASD + * + * + * iSCSI + * + * * * * @param {object} props - * @param {string} [props.title="Page options"] - a title for the group - * @param {string} [props.className="flex-stack"] - CSS class for the wrapper div - * @param {React.ReactElement} props.children - the teleported content + * @param {Group|Item|Array} props.children */ -export default function PageOptions({ - title = "Page options", - className = "flex-stack", - children -}) { - const forwardEvents = (target) => { - return ( -
{ - // Using a CustomEvent because the originalTarget is needed to check the dataset. - // See Sidebar.jsx for better understanding - const customEvent = new CustomEvent( - e.type, - { ...e, detail: { originalTarget: e.target } } - ); - target.dispatchEvent(customEvent); - }} - > -

{title}

- {children} -
- ); - }; +const PageOptions = ({ children }) => { + const [isOpen, setIsOpen] = useState(false); + const onToggle = () => setIsOpen(!isOpen); + const onSelect = () => setIsOpen(false); return ( - - { (target) => forwardEvents(target) } - + + } + onSelect={onSelect} + dropdownItems={Array(children)} + position="right" + className="page-options" + isGrouped + /> + ); -} +}; + +PageOptions.Group = Group; +PageOptions.Item = Item; + +export default PageOptions; diff --git a/web/src/components/core/PageOptions.test.jsx b/web/src/components/core/PageOptions.test.jsx index 543d8a4e4d..00c26aaafa 100644 --- a/web/src/components/core/PageOptions.test.jsx +++ b/web/src/components/core/PageOptions.test.jsx @@ -1,5 +1,5 @@ /* - * Copyright (c) [2023] SUSE LLC + * Copyright (c) [2022-2023] SUSE LLC * * All Rights Reserved. * @@ -21,51 +21,56 @@ import React from "react"; import { screen } from "@testing-library/react"; -import { plainRender } from "~/test-utils"; -import { PageOptionsSlot } from "~/components/layout"; +import { plainRender, mockLayout } from "~/test-utils"; import { PageOptions } from "~/components/core"; -describe("PageOptions", () => { - it("renders given title", () => { - plainRender( - <> - - - The page options content - - - ); +jest.mock("~/components/layout/Layout", () => mockLayout()); - screen.getByText("Awesome options"); - }); +it("renders the component initially closed", async () => { + plainRender( + + A dummy action + + ); - it("renders given children", () => { - plainRender( - <> - - - The page options content - - - ); + expect(screen.queryByRole("menuitem", { name: "A dummy action" })).toBeNull(); +}); + +it("show and hide the component content on user request", async () => { + const { user } = plainRender( + + <>A dummy action + + ); + + const toggler = screen.getByRole("button"); + + expect(screen.queryByRole("menuitem", { name: "A dummy action" })).toBeNull(); - screen.getByText("The page options content"); - }); + await user.click(toggler); + + screen.getByRole("menuitem", { name: "A dummy action" }); + + await user.click(toggler); + + expect(screen.queryByRole("menuitem", { name: "A dummy action" })).toBeNull(); +}); - it("dispatches onClick events to the target", async () => { - const onClickHandler = jest.fn(); - const { user } = plainRender( - <> - - - - - - ); +it("hide the component content when the user clicks on one of its actions", async () => { + const { user } = plainRender( + + + <>Section + <>Page + + <>Exit + + ); - const button = screen.getByRole("button", { name: "Click tester" }); - await user.click(button); + const toggler = screen.getByRole("button"); + await user.click(toggler); + const action = screen.getByRole("menuitem", { name: "Section" }); + await user.click(action); - expect(onClickHandler).toHaveBeenCalled(); - }); + expect(screen.queryByRole("menuitem", { name: "A dummy action" })).toBeNull(); }); diff --git a/web/src/components/core/Sidebar.jsx b/web/src/components/core/Sidebar.jsx index d25e343e88..38be25ad92 100644 --- a/web/src/components/core/Sidebar.jsx +++ b/web/src/components/core/Sidebar.jsx @@ -21,10 +21,7 @@ import React, { useEffect, useRef, useState } from "react"; import { Button, Text } from "@patternfly/react-core"; -import { Icon, PageActions } from "~/components/layout"; - -// FIXME: look for a better way to allow opening the Sidebar from outside -let openButtonRef = {}; +import { Icon, AppActions } from "~/components/layout"; /** * Agama sidebar navigation @@ -33,9 +30,8 @@ let openButtonRef = {}; * @param {object} props * @param {React.ReactElement} props.children */ -const Sidebar = ({ children }) => { +export default function Sidebar ({ children }) { const [isOpen, setIsOpen] = useState(false); - openButtonRef = useRef(null); const closeButtonRef = useRef(null); const open = () => setIsOpen(true); @@ -89,9 +85,8 @@ const Sidebar = ({ children }) => { return ( <> - + - + ); -}; - -/** - * Button for opening the sidebar - * @component - * - * @param {object} props - * @param {onClickFn} [props.onClick] - On click callback - * @param {React.ReactElement} props.children - */ -Sidebar.OpenButton = ({ onClick: onClickProp, children }) => { - const onClick = () => { - if (onClickProp !== undefined) onClickProp(); - openButtonRef.current.click(); - }; - - return ; -}; - -export default Sidebar; +} diff --git a/web/src/components/core/Sidebar.test.jsx b/web/src/components/core/Sidebar.test.jsx index 00d1e8b64a..a29e802a86 100644 --- a/web/src/components/core/Sidebar.test.jsx +++ b/web/src/components/core/Sidebar.test.jsx @@ -21,18 +21,10 @@ import React from "react"; import { screen, within } from "@testing-library/react"; -import { plainRender, mockComponent, mockLayout } from "~/test-utils"; -import { PageOptions, Sidebar } from "~/components/core"; +import { plainRender, mockLayout } from "~/test-utils"; +import { Sidebar } from "~/components/core"; jest.mock("~/components/layout/Layout", () => mockLayout()); -jest.mock("~/components/core/PageOptions", () => mockComponent( - <> - Goes somewhere - Keep it open! - - - -)); it("renders the sidebar initially hidden", async () => { plainRender(); @@ -41,7 +33,7 @@ it("renders the sidebar initially hidden", async () => { }); it("renders a link for displaying the sidebar", async () => { - const { user } = plainRender(); + const { user } = plainRender(); const link = await screen.findByLabelText(/Show/i); const nav = await screen.findByRole("navigation", { name: /options/i }); @@ -52,7 +44,7 @@ it("renders a link for displaying the sidebar", async () => { }); it("renders a link for hiding the sidebar", async () => { - const { user } = plainRender(); + const { user } = plainRender(); const openLink = await screen.findByLabelText(/Show/i); const closeLink = await screen.findByLabelText(/Hide/i); @@ -66,7 +58,7 @@ it("renders a link for hiding the sidebar", async () => { }); it("moves the focus to the close action after opening it", async () => { - const { user } = plainRender(); + const { user } = plainRender(); const openLink = await screen.findByLabelText(/Show/i); const closeLink = await screen.findByLabelText(/Hide/i); @@ -78,7 +70,15 @@ it("moves the focus to the close action after opening it", async () => { describe("onClick bubbling", () => { it("hides the sidebar only if the user clicked on a link or button w/o keepSidebarOpen attribute", async () => { - const { user } = plainRender(); + const { user } = plainRender( + + Goes somewhere + Keep it open! + + + + ); + const openLink = screen.getByLabelText(/Show/i); await user.click(openLink); const nav = screen.getByRole("navigation", { name: /options/i }); diff --git a/web/src/components/core/index.js b/web/src/components/core/index.js index adfef7db16..aa646259a7 100644 --- a/web/src/components/core/index.js +++ b/web/src/components/core/index.js @@ -20,6 +20,7 @@ */ export { default as About } from "./About"; +export { default as PageOptions } from "./PageOptions"; export { default as Disclosure } from "./Disclosure"; export { default as Sidebar } from "./Sidebar"; export { default as Section } from "./Section"; @@ -39,7 +40,6 @@ export { default as FileViewer } from "./FileViewer"; export { default as RowActions } from "./RowActions"; export { default as ShowLogButton } from "./ShowLogButton"; export { default as Page } from "./Page"; -export { default as PageOptions } from "./PageOptions"; export { default as PasswordAndConfirmationInput } from "./PasswordAndConfirmationInput"; export { default as Popup } from "./Popup"; export { default as ProgressReport } from "./ProgressReport"; diff --git a/web/src/components/layout/Icon.jsx b/web/src/components/layout/Icon.jsx index 7075aa4b19..ea88ac9b0a 100644 --- a/web/src/components/layout/Icon.jsx +++ b/web/src/components/layout/Icon.jsx @@ -36,6 +36,7 @@ import Downloading from "@icons/downloading.svg?component"; import Edit from "@icons/edit.svg?component"; import EditSquare from "@icons/edit_square.svg?component"; import Error from "@icons/error.svg?component"; +import ExpandMore from "@icons/expand_more.svg?component"; import HardDrive from "@icons/hard_drive.svg?component"; import Help from "@icons/help.svg?component"; import HomeStorage from "@icons/home_storage.svg?component"; @@ -78,6 +79,7 @@ const icons = { edit: Edit, edit_square: EditSquare, error: Error, + expand_more: ExpandMore, hard_drive: HardDrive, help: Help, home_storage: HomeStorage, diff --git a/web/src/components/layout/Layout.jsx b/web/src/components/layout/Layout.jsx index 28ddbfaeaf..1cbdbb836b 100644 --- a/web/src/components/layout/Layout.jsx +++ b/web/src/components/layout/Layout.jsx @@ -23,10 +23,11 @@ import React from "react"; import logoUrl from "~/assets/suse-horizontal-logo.svg"; import { createTeleporter } from "react-teleporter"; +import { ChangeProductLink } from "~/components/software"; import { About, Disclosure, LogsButton, Sidebar, ShowLogButton, ShowTerminalButton } from "~/components/core"; const PageTitle = createTeleporter(); -const PageOptions = createTeleporter(); +const PageActions = createTeleporter(); const HeaderActions = createTeleporter(); const HeaderIcon = createTeleporter(); const FooterActions = createTeleporter(); @@ -51,7 +52,7 @@ const FooterInfoArea = createTeleporter(); * @example * * - * + * * * Dashboard * @@ -76,14 +77,15 @@ function Layout({ children }) { - +
+ + +
<> - - -

Other options

+ @@ -138,7 +140,20 @@ const PageIcon = HeaderIcon.Source; * console.log("do something")} /> * */ -const PageActions = HeaderActions.Source; +const PageOptions = PageActions.Source; + +/** + * Component for setting global actions shown on the header right + * + * @example + * import { AppActions } from "agama-layout"; + * import { FancyButton } from "somewhere"; + * ... + * + * console.log("do something")} /> + * + */ +const AppActions = HeaderActions.Source; /** * Component for setting the main actions shown on the footer right @@ -169,23 +184,12 @@ const MainActions = FooterActions.Source; */ const AdditionalInfo = FooterInfoArea.Source; -/** - * Component for setting the slot to place related options, usually in the Sidebar - */ -const PageOptionsSlot = PageOptions.Target; - -/** - * Component for teleport page related options to the PageOptions slot - */ -const PageOptionsContent = PageOptions.Source; - export { Layout as default, Title, PageIcon, - PageActions, + AppActions, + PageOptions, MainActions, AdditionalInfo, - PageOptionsSlot, - PageOptionsContent, }; diff --git a/web/src/components/network/NetworkPage.jsx b/web/src/components/network/NetworkPage.jsx index a1d6e44888..f643f2a76c 100644 --- a/web/src/components/network/NetworkPage.jsx +++ b/web/src/components/network/NetworkPage.jsx @@ -24,35 +24,8 @@ import { Button, Skeleton } from "@patternfly/react-core"; import { Icon } from "~/components/layout"; import { useInstallerClient } from "~/context/installer"; import { ConnectionTypes, NetworkEventTypes } from "~/client/network"; -import { Page, PageOptions, Section } from "~/components/core"; -import { ConnectionsTable, IpSettingsForm, WifiSelector } from "~/components/network"; - -/** - * Internal component for displaying the WifiSelector when applicable - * @component - * - * @param {object} props - * @param {boolean} props.supported - whether the system supports scanning WiFi networks - * @param {string} [buttonVariant="link"] - the PF4/Button variant prop for the button. See {@link https://www.patternfly.org/v4/components/button#props } - */ -const WifiScan = ({ supported, actionVariant = "link" }) => { - const [wifiSelectorOpen, setWifiSelectorOpen] = useState(false); - - if (!supported) return null; - - return ( - <> - - setWifiSelectorOpen(false)} /> - - ); -}; +import { If, Page, Section } from "~/components/core"; +import { ConnectionsTable, IpSettingsForm, NetworkPageOptions, WifiSelector } from "~/components/network"; /** * Internal component for displaying info when none wire connection is found @@ -72,8 +45,9 @@ const NoWiredConnections = () => { * * @param {object} props * @param {boolean} props.supported - whether the system supports scanning WiFi networks + * @param {boolean} props.openWifiSelector - the function for opening the WiFi selector */ -const NoWifiConnections = ({ wifiScanSupported }) => { +const NoWifiConnections = ({ wifiScanSupported, openWifiSelector }) => { const message = wifiScanSupported ? "The system has not been configured for connecting to a WiFi network yet." : "The system does not support WiFi connections, probably because of missing or disabled hardware."; @@ -82,7 +56,20 @@ const NoWifiConnections = ({ wifiScanSupported }) => {
No WiFi connections found
{message}
- + + + + } + />
); }; @@ -98,6 +85,10 @@ export default function NetworkPage() { const [connections, setConnections] = useState([]); const [selectedConnection, setSelectedConnection] = useState(null); const [wifiScanSupported, setWifiScanSupported] = useState(false); + const [wifiSelectorOpen, setWifiSelectorOpen] = useState(false); + + const openWifiSelector = () => setWifiSelectorOpen(true); + const closeWifiSelector = () => setWifiSelectorOpen(false); useEffect(() => { client.setUp().then(() => setInitialized(true)); @@ -156,13 +147,13 @@ export default function NetworkPage() { const WifiConnections = () => { if (activeWifiConnections.length === 0) { - return ; + return ( + + ); } return ( - <> - - + ); }; @@ -182,12 +173,18 @@ export default function NetworkPage() { { ready ? : } + + + } + /> + { /* TODO: improve the connections edition */ } - { selectedConnection && setSelectedConnection(null)} /> } - { wifiScanSupported && - - - } + setSelectedConnection(null)} /> + /> ); } diff --git a/web/src/components/network/NetworkPageOptions.jsx b/web/src/components/network/NetworkPageOptions.jsx new file mode 100644 index 0000000000..b27cb1d4ab --- /dev/null +++ b/web/src/components/network/NetworkPageOptions.jsx @@ -0,0 +1,48 @@ +/* + * Copyright (c) [2023] 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 { PageOptions } from "~/components/core"; + +/** + * Component for rendering the options available from Network page + * @component + * + * @param {object} props + * @param {boolean} [props.wifiScanSupported=false] - wether the scan of WiFi networks is supported + * @param {function} props.openWifiSelector - the function for opening the WiFi network selector + */ +export default function NetworkPageOptions ({ + wifiScanSupported = false, + openWifiSelector, +}) { + // Since there are not more options yet, do not show the menu if WiFi scan is + // not supported. + if (!wifiScanSupported) return null; + + return ( + + + <>Connect to a Wi-Fi network + + + ); +} diff --git a/web/src/components/network/index.js b/web/src/components/network/index.js index 5cb1b445b2..7850d43466 100644 --- a/web/src/components/network/index.js +++ b/web/src/components/network/index.js @@ -20,6 +20,7 @@ */ export { default as NetworkPage } from "./NetworkPage"; +export { default as NetworkPageOptions } from "./NetworkPageOptions"; export { default as AddressesDataList } from "./AddressesDataList"; export { default as ConnectionsTable } from "./ConnectionsTable"; export { default as DnsDataList } from "./DnsDataList"; diff --git a/web/src/components/overview/Overview.jsx b/web/src/components/overview/Overview.jsx index 657c37c0e4..ed13813746 100644 --- a/web/src/components/overview/Overview.jsx +++ b/web/src/components/overview/Overview.jsx @@ -23,8 +23,7 @@ import React, { useState } from "react"; import { useSoftware } from "~/context/software"; import { Navigate } from "react-router-dom"; -import { Page, PageOptions, InstallButton } from "~/components/core"; -import { ChangeProductLink } from "~/components/software"; +import { Page, InstallButton } from "~/components/core"; import { L10nSection, NetworkSection, @@ -47,10 +46,6 @@ function Overview() { icon="inventory_2" action={ setShowErrors(true)} />} > - - - - diff --git a/web/src/components/overview/Overview.test.jsx b/web/src/components/overview/Overview.test.jsx index 790d2b5e3c..3e7630f1e2 100644 --- a/web/src/components/overview/Overview.test.jsx +++ b/web/src/components/overview/Overview.test.jsx @@ -50,7 +50,6 @@ jest.mock("~/components/overview/StorageSection", () => mockComponent("Storage S jest.mock("~/components/overview/NetworkSection", () => mockComponent("Network Section")); jest.mock("~/components/overview/UsersSection", () => mockComponent("Users Section")); jest.mock("~/components/overview/SoftwareSection", () => mockComponent("Software Section")); -jest.mock("~/components/core/PageOptions", () => mockComponent("PageOptions")); jest.mock("~/components/core/InstallButton", () => mockComponent("Install Button")); beforeEach(() => { diff --git a/web/src/components/storage/ProposalPage.jsx b/web/src/components/storage/ProposalPage.jsx index 66c21b14e4..87b3cf68db 100644 --- a/web/src/components/storage/ProposalPage.jsx +++ b/web/src/components/storage/ProposalPage.jsx @@ -19,15 +19,14 @@ * find current contact information at www.suse.com. */ -import React, { useCallback, useReducer, useEffect, useState } from "react"; +import React, { useCallback, useReducer, useEffect } from "react"; import { Alert } from "@patternfly/react-core"; -import { Link } from "react-router-dom"; import { useInstallerClient } from "~/context/installer"; import { useCancellablePromise } from "~/utils"; import { Icon } from "~/components/layout"; -import { Page, PageOptions } from "~/components/core"; -import { ProposalActionsSection, ProposalSettingsSection } from "~/components/storage"; +import { Page } from "~/components/core"; +import { ProposalActionsSection, ProposalPageOptions, ProposalSettingsSection } from "~/components/storage"; const initialState = { loading: true, @@ -149,33 +148,10 @@ export default function ProposalPage() { ); }; - const DASDLink = () => { - const [show, setShow] = useState(false); - - useEffect(() => { - client.dasd.isSupported().then(setShow); - }, []); - - if (!show) return null; - - return ( - - - Configure DASD - - ); - }; - return ( - - - - - Configure iSCSI - - + ); } diff --git a/web/src/components/storage/ProposalPage.test.jsx b/web/src/components/storage/ProposalPage.test.jsx index a3878bce8e..9909a63964 100644 --- a/web/src/components/storage/ProposalPage.test.jsx +++ b/web/src/components/storage/ProposalPage.test.jsx @@ -26,6 +26,7 @@ import { createClient } from "~/client"; import { ProposalPage } from "~/components/storage"; jest.mock("~/client"); +jest.mock("~/components/storage/ProposalPageOptions", () => mockComponent("ProposalPage Options")); jest.mock("@patternfly/react-core", () => { const original = jest.requireActual("@patternfly/react-core"); diff --git a/web/src/components/storage/ProposalPageOptions.jsx b/web/src/components/storage/ProposalPageOptions.jsx new file mode 100644 index 0000000000..858e8310e3 --- /dev/null +++ b/web/src/components/storage/ProposalPageOptions.jsx @@ -0,0 +1,86 @@ +/* + * Copyright (c) [2023] 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, { useEffect, useState } from "react"; +import { useHref } from "react-router-dom"; +import { useInstallerClient } from "~/context/installer"; +import { If, PageOptions } from "~/components/core"; + +/** + * Internal component for build the link to Storage/DASD page + * @component + */ +const DASDLink = () => { + const href = useHref("/storage/dasd"); + + return ( + + DASD + + ); +}; + +/** + * Internal component for build the link to Storage/iSCSI page + * @component + */ +const ISCSILink = () => { + const href = useHref("/storage/iscsi"); + + return ( + + iSCSI + + ); +}; + +/** + * Component for rendering the options available from Storage/ProposalPage + * @component + */ +export default function ProposalPageOptions () { + const [showDasdLink, setShowDasdLink] = useState(false); + const { storage: client } = useInstallerClient(); + + useEffect(() => { + client.dasd.isSupported().then(setShowDasdLink); + }, [client.dasd]); + + return ( + + + } /> + + + + ); +} diff --git a/web/src/components/storage/ProposalSettingsSection.jsx b/web/src/components/storage/ProposalSettingsSection.jsx index 637b8af7bf..41ee3fac3e 100644 --- a/web/src/components/storage/ProposalSettingsSection.jsx +++ b/web/src/components/storage/ProposalSettingsSection.jsx @@ -26,7 +26,7 @@ import { Tooltip } from "@patternfly/react-core"; -import { If, PasswordAndConfirmationInput, Section, Sidebar, Popup } from "~/components/core"; +import { If, PasswordAndConfirmationInput, Section, Popup } from "~/components/core"; import { ProposalVolumes } from "~/components/storage"; import { Icon } from "~/components/layout"; import { noop } from "~/utils"; @@ -130,14 +130,6 @@ const InstallationDeviceField = ({ current, devices, isLoading, onChange }) => { return ; }; - const SidebarLink = ({ label }) => { - return ( - - {label} - - ); - }; - if (isLoading) { return ; } @@ -153,23 +145,18 @@ const InstallationDeviceField = ({ current, devices, isLoading, onChange }) => { title="Installation device" isOpen={isFormOpen} > -
- No devices found
} - else={ - - } - /> -

- Use the to configure access to more disks. -

- + No devices found} + else={ + + } + /> ({ default: ({ children }) => children, Title: ({ children }) => children, PageIcon: ({ children }) => children, - PageActions: ({ children }) => children, + AppActions: ({ children }) => children, + PageOptions: ({ children }) => children, MainActions: ({ children }) => children, AdditionalInfo: ({ children }) => children, - PageOptionsContent: ({ children }) => children, }); export {