From fe183008d88bbe160934e495c2aba2df8ed5a78a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20D=C3=ADaz=20Gonz=C3=A1lez?= Date: Wed, 30 Oct 2024 21:03:03 +0000 Subject: [PATCH 1/5] fix(web): render InstallButton only when needed Since it does not make sense to render the InstallButton when the installation is in progress or finished. The same apply for the login screen, product selection, and product progress. --- .../components/core/InstallButton.test.tsx | 22 +++++++--------- web/src/components/core/InstallButton.tsx | 25 ++++++++++++++++--- 2 files changed, 30 insertions(+), 17 deletions(-) diff --git a/web/src/components/core/InstallButton.test.tsx b/web/src/components/core/InstallButton.test.tsx index 1adb4570c8..bbc147543b 100644 --- a/web/src/components/core/InstallButton.test.tsx +++ b/web/src/components/core/InstallButton.test.tsx @@ -25,6 +25,7 @@ import { screen, waitFor } from "@testing-library/react"; import { installerRender, mockRoutes } from "~/test-utils"; import { InstallButton } from "~/components/core"; import { IssuesList } from "~/types/issues"; +import { PATHS as ROOT_PATHS } from "~/router"; import { PATHS as PRODUCT_PATHS } from "~/routes/products"; const mockStartInstallationFn = jest.fn(); @@ -97,20 +98,15 @@ describe("when there are not installation issues", () => { }); }); - describe("but installer is rendering the product selection", () => { + describe.each([ + ["login", ROOT_PATHS.login], + ["product selection", PRODUCT_PATHS.changeProduct], + ["product selection progress", PRODUCT_PATHS.progress], + ["installation progress", ROOT_PATHS.installationProgress], + ["installation finished", ROOT_PATHS.installationFinished], + ])(`but the installer is rendering the %s screen`, (_, path) => { beforeEach(() => { - mockRoutes(PRODUCT_PATHS.changeProduct); - }); - - it("renders nothing", () => { - const { container } = installerRender(); - expect(container).toBeEmptyDOMElement(); - }); - }); - - describe("but installer is configuring a product", () => { - beforeEach(() => { - mockRoutes(PRODUCT_PATHS.progress); + mockRoutes(path); }); it("renders nothing", () => { diff --git a/web/src/components/core/InstallButton.tsx b/web/src/components/core/InstallButton.tsx index 0d7c56512f..f24825cfc9 100644 --- a/web/src/components/core/InstallButton.tsx +++ b/web/src/components/core/InstallButton.tsx @@ -29,8 +29,24 @@ import { _ } from "~/i18n"; import { startInstallation } from "~/api/manager"; import { useAllIssues } from "~/queries/issues"; import { useLocation } from "react-router-dom"; +import { PATHS as ROOT_PATHS } from "~/router"; import { PATHS as PRODUCT_PATHS } from "~/routes/products"; +/** + * List of paths where the InstallButton must not be shown. + * + * Apart from obvious login and installation paths, it does not make sense to + * show the button neither, when the user is about to change the product nor + * when the installer is setting the chosen product. + * */ +const EXCLUDED_FROM = [ + ROOT_PATHS.login, + PRODUCT_PATHS.changeProduct, + PRODUCT_PATHS.progress, + ROOT_PATHS.installationProgress, + ROOT_PATHS.installationFinished, +]; + const InstallConfirmationPopup = ({ onAccept, onClose }) => { return ( @@ -60,8 +76,11 @@ according to the provided installation settings.", /** * Installation button * - * It starts the installation after asking for confirmation. + * It will be shown only if there aren't installation issues and the current + * path is not in the EXCLUDED_FROM list. * + * When clicked, it will ask for a confirmation before triggering the request + * for starting the installation. */ const InstallButton = (props: Omit) => { const issues = useAllIssues(); @@ -69,9 +88,7 @@ const InstallButton = (props: Omit) => { const location = useLocation(); if (!issues.isEmpty) return; - // Do not show the button if the user is about to change the product or the - // installer is configuring a product. - if ([PRODUCT_PATHS.changeProduct, PRODUCT_PATHS.progress].includes(location.pathname)) return; + if (EXCLUDED_FROM.includes(location.pathname)) return; const open = async () => setIsOpen(true); const close = () => setIsOpen(false); From cbab2e402fa5272f7e7db7055bab8720ec631f1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20D=C3=ADaz=20Gonz=C3=A1lez?= Date: Wed, 30 Oct 2024 21:31:42 +0000 Subject: [PATCH 2/5] fix(web): do not start installation by mistake Changing the default focus in the confirmation dialog from 'Continue' to 'Cancel' to avoid the user triggering the installation because accidentally sending the {enter} keystroke. --- .../components/core/InstallButton.test.tsx | 152 +++++++++++------- web/src/components/core/InstallButton.tsx | 8 +- 2 files changed, 103 insertions(+), 57 deletions(-) diff --git a/web/src/components/core/InstallButton.test.tsx b/web/src/components/core/InstallButton.test.tsx index bbc147543b..6fdea141ca 100644 --- a/web/src/components/core/InstallButton.test.tsx +++ b/web/src/components/core/InstallButton.test.tsx @@ -21,7 +21,7 @@ */ import React from "react"; -import { screen, waitFor } from "@testing-library/react"; +import { screen, waitFor, within } from "@testing-library/react"; import { installerRender, mockRoutes } from "~/test-utils"; import { InstallButton } from "~/components/core"; import { IssuesList } from "~/types/issues"; @@ -41,77 +41,119 @@ jest.mock("~/queries/issues", () => ({ useAllIssues: () => mockIssuesList, })); -describe("when there are installation issues", () => { - beforeEach(() => { - mockIssuesList = new IssuesList( - [ - { - description: "Fake Issue", - source: 0, - severity: 0, - details: "Fake Issue details", - }, - ], - [], - [], - [], - ); +const clickInstallButton = async () => { + const { user } = installerRender(); + const button = await screen.findByRole("button", { name: "Install" }); + await user.click(button); + + const dialog = screen.getByRole("dialog", { name: "Confirm Installation" }); + return { user, dialog }; +}; + +describe("InstallButton", () => { + describe("when there are installation issues", () => { + beforeEach(() => { + mockIssuesList = new IssuesList( + [ + { + description: "Fake Issue", + source: 0, + severity: 0, + details: "Fake Issue details", + }, + ], + [], + [], + [], + ); + }); + + it("renders nothing", () => { + const { container } = installerRender(); + expect(container).toBeEmptyDOMElement(); + }); }); - it("renders nothing", () => { - const { container } = installerRender(); - expect(container).toBeEmptyDOMElement(); + describe("when there are not installation issues", () => { + beforeEach(() => { + mockIssuesList = new IssuesList([], [], [], []); + }); + + it("renders an Install button", () => { + installerRender(); + screen.getByRole("button", { name: "Install" }); + }); + + it("renders a confirmation dialog when clicked", async () => { + const { user } = installerRender(); + const button = await screen.findByRole("button", { name: "Install" }); + await user.click(button); + + screen.getByRole("dialog", { name: "Confirm Installation" }); + }); + + describe.each([ + ["login", ROOT_PATHS.login], + ["product selection", PRODUCT_PATHS.changeProduct], + ["product selection progress", PRODUCT_PATHS.progress], + ["installation progress", ROOT_PATHS.installationProgress], + ["installation finished", ROOT_PATHS.installationFinished], + ])(`but the installer is rendering the %s screen`, (_, path) => { + beforeEach(() => { + mockRoutes(path); + }); + + it("renders nothing", () => { + const { container } = installerRender(); + expect(container).toBeEmptyDOMElement(); + }); + }); }); }); -describe("when there are not installation issues", () => { - beforeEach(() => { - mockIssuesList = new IssuesList([], [], [], []); +describe("InstallConfirmationPopup", () => { + it("closes the dialog without triggering installation if user press {enter} before 'Continue' gets the focus", async () => { + const { user, dialog } = await clickInstallButton(); + const continueButton = within(dialog).getByRole("button", { name: "Continue" }); + expect(continueButton).not.toHaveFocus(); + await user.keyboard("{enter}"); + expect(mockStartInstallationFn).not.toHaveBeenCalled(); + await waitFor(() => { + expect(dialog).not.toBeInTheDocument(); + }); }); - it("renders an Install button", () => { - installerRender(); - screen.getByRole("button", { name: "Install" }); + it("closes the dialog and triggers installation if user {enter} when 'Continue' has the focus", async () => { + const { user, dialog } = await clickInstallButton(); + const continueButton = within(dialog).getByRole("button", { name: "Continue" }); + expect(continueButton).not.toHaveFocus(); + await user.keyboard("{tab}"); + expect(continueButton).toHaveFocus(); + await user.keyboard("{enter}"); + expect(mockStartInstallationFn).toHaveBeenCalled(); + await waitFor(() => { + expect(dialog).not.toBeInTheDocument(); + }); }); - it("starts the installation after user clicks on it and accept the confirmation", async () => { - const { user } = installerRender(); - const button = await screen.findByRole("button", { name: "Install" }); - await user.click(button); - - const continueButton = await screen.findByRole("button", { name: "Continue" }); + it("closes the dialog and triggers installation if user clicks on 'Continue'", async () => { + const { user, dialog } = await clickInstallButton(); + const continueButton = within(dialog).getByRole("button", { name: "Continue" }); await user.click(continueButton); expect(mockStartInstallationFn).toHaveBeenCalled(); + await waitFor(() => { + expect(dialog).not.toBeInTheDocument(); + }); }); - it("does not start the installation if the user clicks on it but cancels the confirmation", async () => { - const { user } = installerRender(); - const button = await screen.findByRole("button", { name: "Install" }); - await user.click(button); - - const cancelButton = await screen.findByRole("button", { name: "Cancel" }); + it("closes the dialog without triggering installation if the user clicks on 'Cancel'", async () => { + const { user, dialog } = await clickInstallButton(); + const cancelButton = within(dialog).getByRole("button", { name: "Cancel" }); await user.click(cancelButton); expect(mockStartInstallationFn).not.toHaveBeenCalled(); await waitFor(() => { - expect(screen.queryByRole("button", { name: "Continue" })).not.toBeInTheDocument(); - }); - }); - - describe.each([ - ["login", ROOT_PATHS.login], - ["product selection", PRODUCT_PATHS.changeProduct], - ["product selection progress", PRODUCT_PATHS.progress], - ["installation progress", ROOT_PATHS.installationProgress], - ["installation finished", ROOT_PATHS.installationFinished], - ])(`but the installer is rendering the %s screen`, (_, path) => { - beforeEach(() => { - mockRoutes(path); - }); - - it("renders nothing", () => { - const { container } = installerRender(); - expect(container).toBeEmptyDOMElement(); + expect(dialog).not.toBeInTheDocument(); }); }); }); diff --git a/web/src/components/core/InstallButton.tsx b/web/src/components/core/InstallButton.tsx index f24825cfc9..6657adf718 100644 --- a/web/src/components/core/InstallButton.tsx +++ b/web/src/components/core/InstallButton.tsx @@ -64,7 +64,7 @@ according to the provided installation settings.", {/* TRANSLATORS: button label */} {_("Continue")} - + {/* TRANSLATORS: button label */} {_("Cancel")} @@ -92,6 +92,10 @@ const InstallButton = (props: Omit) => { const open = async () => setIsOpen(true); const close = () => setIsOpen(false); + const onAccept = () => { + close(); + startInstallation(); + }; return ( <> @@ -100,7 +104,7 @@ const InstallButton = (props: Omit) => { {_("Install")} - {isOpen && } + {isOpen && } ); }; From e162083a07977636d3aa4e51f7b3356c8f42d0eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20D=C3=ADaz=20Gonz=C3=A1lez?= Date: Wed, 30 Oct 2024 22:27:15 +0000 Subject: [PATCH 3/5] fix(web): move paths to its own file To avoid circular dependencies. --- web/src/App.tsx | 19 ++-- .../core/ChangeProductLink.test.tsx | 2 +- web/src/components/core/ChangeProductLink.tsx | 2 +- .../components/core/InstallButton.test.tsx | 13 ++- web/src/components/core/InstallButton.tsx | 13 ++- .../components/core/InstallationFinished.tsx | 2 +- .../components/core/InstallationProgress.tsx | 2 +- web/src/components/core/IssuesLink.test.tsx | 6 +- web/src/components/core/IssuesLink.tsx | 7 +- web/src/components/l10n/L10nPage.jsx | 2 +- web/src/components/layout/Header.tsx | 2 +- .../components/network/ConnectionsTable.tsx | 2 +- web/src/components/network/NetworkPage.tsx | 2 +- .../network/WifiNetworksListPage.tsx | 2 +- .../product/ProductSelectionProgress.jsx | 2 +- web/src/components/software/SoftwarePage.tsx | 2 +- .../components/storage/BootConfigField.tsx | 4 +- .../storage/InstallationDeviceField.tsx | 2 +- .../storage/ProposalActionsSummary.tsx | 6 +- web/src/components/storage/dasd/DASDPage.tsx | 6 +- web/src/components/storage/zfcp/ZFCPPage.tsx | 2 +- web/src/router.js | 11 +-- web/src/routes/l10n.tsx | 8 +- web/src/routes/network.tsx | 7 +- web/src/routes/paths.ts | 86 +++++++++++++++++++ web/src/routes/products.tsx | 8 +- web/src/routes/software.tsx | 6 +- web/src/routes/storage.tsx | 16 +--- web/src/routes/users.tsx | 8 +- 29 files changed, 139 insertions(+), 111 deletions(-) create mode 100644 web/src/routes/paths.ts diff --git a/web/src/App.tsx b/web/src/App.tsx index 9fe9761571..ca9c11b9bc 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -32,8 +32,7 @@ import { useL10nConfigChanges } from "~/queries/l10n"; import { useIssuesChanges } from "~/queries/issues"; import { useInstallerStatus, useInstallerStatusChanges } from "~/queries/status"; import { useDeprecatedChanges } from "~/queries/storage"; -import { PATHS as PRODUCT_PATHS } from "~/routes/products"; -import { PATHS as ROOT_PATHS } from "~/router"; +import { ROOT, PRODUCT } from "~/routes/paths"; import { InstallationPhase } from "~/types/status"; /** @@ -60,11 +59,11 @@ function App() { ); if (phase === InstallationPhase.Install && isBusy) { - return ; + return ; } if (phase === InstallationPhase.Install && !isBusy) { - return ; + return ; } if (!products || !connected) return ; @@ -77,16 +76,12 @@ function App() { ); } - if (selectedProduct === undefined && location.pathname !== PRODUCT_PATHS.root) { - return ; + if (selectedProduct === undefined && location.pathname !== PRODUCT.root) { + return ; } - if ( - phase === InstallationPhase.Config && - isBusy && - location.pathname !== PRODUCT_PATHS.progress - ) { - return ; + if (phase === InstallationPhase.Config && isBusy && location.pathname !== PRODUCT.progress) { + return ; } return ; diff --git a/web/src/components/core/ChangeProductLink.test.tsx b/web/src/components/core/ChangeProductLink.test.tsx index e6e4b05208..ddbdd369d1 100644 --- a/web/src/components/core/ChangeProductLink.test.tsx +++ b/web/src/components/core/ChangeProductLink.test.tsx @@ -23,7 +23,7 @@ import React from "react"; import { screen } from "@testing-library/react"; import { installerRender } from "~/test-utils"; -import { PATHS } from "~/routes/products"; +import { PRODUCT as PATHS } from "~/routes/paths"; import { Product } from "~/types/software"; import ChangeProductLink from "./ChangeProductLink"; diff --git a/web/src/components/core/ChangeProductLink.tsx b/web/src/components/core/ChangeProductLink.tsx index f851ae57c1..d5fd67b8fb 100644 --- a/web/src/components/core/ChangeProductLink.tsx +++ b/web/src/components/core/ChangeProductLink.tsx @@ -23,7 +23,7 @@ import React from "react"; import { Link, LinkProps } from "react-router-dom"; import { useProduct } from "~/queries/software"; -import { PATHS } from "~/routes/products"; +import { PRODUCT as PATHS } from "~/routes/paths"; import { _ } from "~/i18n"; /** diff --git a/web/src/components/core/InstallButton.test.tsx b/web/src/components/core/InstallButton.test.tsx index 6fdea141ca..965660de11 100644 --- a/web/src/components/core/InstallButton.test.tsx +++ b/web/src/components/core/InstallButton.test.tsx @@ -25,8 +25,7 @@ import { screen, waitFor, within } from "@testing-library/react"; import { installerRender, mockRoutes } from "~/test-utils"; import { InstallButton } from "~/components/core"; import { IssuesList } from "~/types/issues"; -import { PATHS as ROOT_PATHS } from "~/router"; -import { PATHS as PRODUCT_PATHS } from "~/routes/products"; +import { PRODUCT, ROOT } from "~/routes/paths"; const mockStartInstallationFn = jest.fn(); let mockIssuesList: IssuesList; @@ -93,11 +92,11 @@ describe("InstallButton", () => { }); describe.each([ - ["login", ROOT_PATHS.login], - ["product selection", PRODUCT_PATHS.changeProduct], - ["product selection progress", PRODUCT_PATHS.progress], - ["installation progress", ROOT_PATHS.installationProgress], - ["installation finished", ROOT_PATHS.installationFinished], + ["login", ROOT.login], + ["product selection", PRODUCT.changeProduct], + ["product selection progress", PRODUCT.progress], + ["installation progress", ROOT.installationProgress], + ["installation finished", ROOT.installationFinished], ])(`but the installer is rendering the %s screen`, (_, path) => { beforeEach(() => { mockRoutes(path); diff --git a/web/src/components/core/InstallButton.tsx b/web/src/components/core/InstallButton.tsx index 6657adf718..5d483d0360 100644 --- a/web/src/components/core/InstallButton.tsx +++ b/web/src/components/core/InstallButton.tsx @@ -29,8 +29,7 @@ import { _ } from "~/i18n"; import { startInstallation } from "~/api/manager"; import { useAllIssues } from "~/queries/issues"; import { useLocation } from "react-router-dom"; -import { PATHS as ROOT_PATHS } from "~/router"; -import { PATHS as PRODUCT_PATHS } from "~/routes/products"; +import { PRODUCT, ROOT } from "~/routes/paths"; /** * List of paths where the InstallButton must not be shown. @@ -40,11 +39,11 @@ import { PATHS as PRODUCT_PATHS } from "~/routes/products"; * when the installer is setting the chosen product. * */ const EXCLUDED_FROM = [ - ROOT_PATHS.login, - PRODUCT_PATHS.changeProduct, - PRODUCT_PATHS.progress, - ROOT_PATHS.installationProgress, - ROOT_PATHS.installationFinished, + ROOT.login, + PRODUCT.changeProduct, + PRODUCT.progress, + ROOT.installationProgress, + ROOT.installationFinished, ]; const InstallConfirmationPopup = ({ onAccept, onClose }) => { diff --git a/web/src/components/core/InstallationFinished.tsx b/web/src/components/core/InstallationFinished.tsx index 0c9a464b62..d61e5f5e96 100644 --- a/web/src/components/core/InstallationFinished.tsx +++ b/web/src/components/core/InstallationFinished.tsx @@ -46,7 +46,7 @@ import { useProposalResult } from "~/queries/storage"; import { finishInstallation } from "~/api/manager"; import { InstallationPhase } from "~/types/status"; import { Navigate } from "react-router-dom"; -import { PATHS } from "~/router"; +import { ROOT as PATHS } from "~/routes/paths"; const TpmHint = () => { const [isExpanded, setIsExpanded] = useState(false); diff --git a/web/src/components/core/InstallationProgress.tsx b/web/src/components/core/InstallationProgress.tsx index 5c387413f4..1725b8b828 100644 --- a/web/src/components/core/InstallationProgress.tsx +++ b/web/src/components/core/InstallationProgress.tsx @@ -24,7 +24,7 @@ import React from "react"; import { _ } from "~/i18n"; import ProgressReport from "./ProgressReport"; import { InstallationPhase } from "~/types/status"; -import { PATHS } from "~/router"; +import { ROOT as PATHS } from "~/routes/paths"; import { Navigate } from "react-router-dom"; import { useInstallerStatus } from "~/queries/status"; diff --git a/web/src/components/core/IssuesLink.test.tsx b/web/src/components/core/IssuesLink.test.tsx index de6ae3a37f..5416526fb6 100644 --- a/web/src/components/core/IssuesLink.test.tsx +++ b/web/src/components/core/IssuesLink.test.tsx @@ -25,7 +25,7 @@ import { screen } from "@testing-library/react"; import { installerRender, mockRoutes } from "~/test-utils"; import { IssuesLink } from "~/components/core"; import { IssuesList } from "~/types/issues"; -import { PATHS as PRODUCT_PATHS } from "~/routes/products"; +import { PRODUCT as PATHS } from "~/routes/paths"; const mockStartInstallationFn = jest.fn(); let mockIssuesList: IssuesList; @@ -64,7 +64,7 @@ describe("when there are installation issues", () => { describe("but installer is rendering the product selection", () => { beforeEach(() => { - mockRoutes(PRODUCT_PATHS.changeProduct); + mockRoutes(PATHS.changeProduct); }); it("renders nothing", () => { @@ -75,7 +75,7 @@ describe("when there are installation issues", () => { describe("but installer is configuring the product", () => { beforeEach(() => { - mockRoutes(PRODUCT_PATHS.progress); + mockRoutes(PATHS.progress); }); it("renders nothing", () => { diff --git a/web/src/components/core/IssuesLink.tsx b/web/src/components/core/IssuesLink.tsx index ea596df2e6..4aaf88de53 100644 --- a/web/src/components/core/IssuesLink.tsx +++ b/web/src/components/core/IssuesLink.tsx @@ -24,10 +24,9 @@ import React from "react"; import { useLocation } from "react-router-dom"; import { useAllIssues } from "~/queries/issues"; import Link, { LinkProps } from "~/components/core/Link"; -import { PATHS as PRODUCT_PATHS } from "~/routes/products"; -import { PATHS as ROOT_PATHS } from "~/router"; import { Icon } from "../layout"; import { Tooltip } from "@patternfly/react-core"; +import { PRODUCT, ROOT } from "~/routes/paths"; import { _ } from "~/i18n"; /** @@ -43,13 +42,13 @@ const IssuesLink = (props: Omit) => { if (issues.isEmpty) return; // Do not show the button if the user is about to change the product or the // installer is configuring a product. - if ([PRODUCT_PATHS.changeProduct, PRODUCT_PATHS.progress].includes(location.pathname)) return; + if ([PRODUCT.changeProduct, PRODUCT.progress].includes(location.pathname)) return; return ( - + diff --git a/web/src/components/l10n/L10nPage.jsx b/web/src/components/l10n/L10nPage.jsx index b6d68bbad1..5ead5d01f6 100644 --- a/web/src/components/l10n/L10nPage.jsx +++ b/web/src/components/l10n/L10nPage.jsx @@ -23,7 +23,7 @@ import React from "react"; import { Gallery, GalleryItem } from "@patternfly/react-core"; import { Link, Page } from "~/components/core"; -import { PATHS } from "~/routes/l10n"; +import { L10N as PATHS } from "~/routes/paths"; import { _ } from "~/i18n"; import { useL10n } from "~/queries/l10n"; diff --git a/web/src/components/layout/Header.tsx b/web/src/components/layout/Header.tsx index c187652f19..fdd229c31a 100644 --- a/web/src/components/layout/Header.tsx +++ b/web/src/components/layout/Header.tsx @@ -47,7 +47,7 @@ import { InstallationPhase } from "~/types/status"; import { useInstallerStatus } from "~/queries/status"; import { InstallButton, InstallerOptions, IssuesLink } from "~/components/core"; import { useLocation } from "react-router-dom"; -import { PATHS } from "~/router"; +import { ROOT as PATHS } from "~/routes/paths"; export type HeaderProps = { /** Whether the application sidebar should be mounted or not */ diff --git a/web/src/components/network/ConnectionsTable.tsx b/web/src/components/network/ConnectionsTable.tsx index 3d9a156162..254b6a07b3 100644 --- a/web/src/components/network/ConnectionsTable.tsx +++ b/web/src/components/network/ConnectionsTable.tsx @@ -25,8 +25,8 @@ import { useNavigate, generatePath } from "react-router-dom"; import { Table, Thead, Tr, Th, Tbody, Td } from "@patternfly/react-table"; import { RowActions } from "~/components/core"; import { Icon } from "~/components/layout"; -import { PATHS } from "~/routes/network"; import { Connection, Device } from "~/types/network"; +import { NETWORK as PATHS } from "~/routes/paths"; import { formatIp } from "~/utils/network"; import { sprintf } from "sprintf-js"; import { _ } from "~/i18n"; diff --git a/web/src/components/network/NetworkPage.tsx b/web/src/components/network/NetworkPage.tsx index 92a7e0b8ef..f28015222a 100644 --- a/web/src/components/network/NetworkPage.tsx +++ b/web/src/components/network/NetworkPage.tsx @@ -28,7 +28,7 @@ import { _ } from "~/i18n"; import { connectionAddresses } from "~/utils/network"; import { sprintf } from "sprintf-js"; import { useNetwork, useNetworkConfigChanges } from "~/queries/network"; -import { PATHS } from "~/routes/network"; +import { NETWORK as PATHS } from "~/routes/paths"; import { partition } from "~/utils"; import { Connection, Device } from "~/types/network"; diff --git a/web/src/components/network/WifiNetworksListPage.tsx b/web/src/components/network/WifiNetworksListPage.tsx index 68b4d30f51..3f68e3621d 100644 --- a/web/src/components/network/WifiNetworksListPage.tsx +++ b/web/src/components/network/WifiNetworksListPage.tsx @@ -48,8 +48,8 @@ import { generatePath } from "react-router-dom"; import { Icon } from "~/components/layout"; import { Link, EmptyState } from "~/components/core"; import WifiConnectionForm from "~/components/network/WifiConnectionForm"; -import { PATHS } from "~/routes/network"; import { DeviceState, WifiNetwork } from "~/types/network"; +import { NETWORK as PATHS } from "~/routes/paths"; import { _ } from "~/i18n"; import { formatIp } from "~/utils/network"; import { diff --git a/web/src/components/product/ProductSelectionProgress.jsx b/web/src/components/product/ProductSelectionProgress.jsx index 17f5f072ca..cfc80bed82 100644 --- a/web/src/components/product/ProductSelectionProgress.jsx +++ b/web/src/components/product/ProductSelectionProgress.jsx @@ -25,7 +25,7 @@ import { Navigate } from "react-router-dom"; import { Page, ProgressReport } from "~/components/core"; import { useProduct } from "~/queries/software"; import { useInstallerStatus } from "~/queries/status"; -import { PATHS } from "~/router"; +import { ROOT as PATHS } from "~/routes/paths"; import { _ } from "~/i18n"; /** diff --git a/web/src/components/software/SoftwarePage.tsx b/web/src/components/software/SoftwarePage.tsx index 157af4590b..f22a2ae263 100644 --- a/web/src/components/software/SoftwarePage.tsx +++ b/web/src/components/software/SoftwarePage.tsx @@ -36,7 +36,7 @@ import { useIssues } from "~/queries/issues"; import { usePatterns, useProposal, useProposalChanges } from "~/queries/software"; import { Pattern, SelectedBy } from "~/types/software"; import { _ } from "~/i18n"; -import { PATHS } from "~/routes/software"; +import { SOFTWARE as PATHS } from "~/routes/paths"; /** * List of selected patterns. diff --git a/web/src/components/storage/BootConfigField.tsx b/web/src/components/storage/BootConfigField.tsx index caf47ee3ee..e9398a9379 100644 --- a/web/src/components/storage/BootConfigField.tsx +++ b/web/src/components/storage/BootConfigField.tsx @@ -20,8 +20,6 @@ * find current contact information at www.suse.com. */ -// @ts-check - import React from "react"; import { Link as RouterLink } from "react-router-dom"; import { Skeleton } from "@patternfly/react-core"; @@ -29,8 +27,8 @@ import { _ } from "~/i18n"; import { sprintf } from "sprintf-js"; import { deviceLabel } from "~/components/storage/utils"; import { Icon } from "~/components/layout"; -import { PATHS } from "~/routes/storage"; import { StorageDevice } from "~/types/storage"; +import { STORAGE as PATHS } from "~/routes/paths"; /** * Internal component for building the link that navigates to selector diff --git a/web/src/components/storage/InstallationDeviceField.tsx b/web/src/components/storage/InstallationDeviceField.tsx index 520e45c413..624bc95b9b 100644 --- a/web/src/components/storage/InstallationDeviceField.tsx +++ b/web/src/components/storage/InstallationDeviceField.tsx @@ -24,8 +24,8 @@ import React from "react"; import { Skeleton } from "@patternfly/react-core"; import { Link, Page } from "~/components/core"; import { ProposalTarget, StorageDevice } from "~/types/storage"; -import { PATHS } from "~/routes/storage"; import { deviceLabel } from "~/components/storage/utils"; +import { STORAGE as PATHS } from "~/routes/paths"; import { sprintf } from "sprintf-js"; import { _ } from "~/i18n"; diff --git a/web/src/components/storage/ProposalActionsSummary.tsx b/web/src/components/storage/ProposalActionsSummary.tsx index 4e343f8735..fb0052bdb9 100644 --- a/web/src/components/storage/ProposalActionsSummary.tsx +++ b/web/src/components/storage/ProposalActionsSummary.tsx @@ -24,13 +24,13 @@ import React from "react"; import { Button, Skeleton, Stack, List, ListItem } from "@patternfly/react-core"; import { Link, Page } from "~/components/core"; import DevicesManager from "~/components/storage/DevicesManager"; -import { _, n_ } from "~/i18n"; -import { sprintf } from "sprintf-js"; import textStyles from "@patternfly/react-styles/css/utilities/Text/text"; -import { PATHS } from "~/routes/storage"; import { Action, SpaceAction, StorageDevice } from "~/types/storage"; import { SpacePolicy } from "./utils"; import { ValidationError } from "~/types/issues"; +import { STORAGE as PATHS } from "~/routes/paths"; +import { sprintf } from "sprintf-js"; +import { _, n_ } from "~/i18n"; /** * Renders information about delete actions diff --git a/web/src/components/storage/dasd/DASDPage.tsx b/web/src/components/storage/dasd/DASDPage.tsx index 390655b053..0c10845f67 100644 --- a/web/src/components/storage/dasd/DASDPage.tsx +++ b/web/src/components/storage/dasd/DASDPage.tsx @@ -22,12 +22,12 @@ import React from "react"; import { Stack } from "@patternfly/react-core"; -import { _ } from "~/i18n"; import { Page } from "~/components/core"; -import { useDASDDevicesChanges, useDASDFormatJobChanges } from "~/queries/storage/dasd"; -import { PATHS } from "~/routes/storage"; import DASDTable from "./DASDTable"; import DASDFormatProgress from "./DASDFormatProgress"; +import { useDASDDevicesChanges, useDASDFormatJobChanges } from "~/queries/storage/dasd"; +import { STORAGE as PATHS } from "~/routes/paths"; +import { _ } from "~/i18n"; export default function DASDPage() { useDASDDevicesChanges(); diff --git a/web/src/components/storage/zfcp/ZFCPPage.tsx b/web/src/components/storage/zfcp/ZFCPPage.tsx index 5eeaa53ba8..defb1678e9 100644 --- a/web/src/components/storage/zfcp/ZFCPPage.tsx +++ b/web/src/components/storage/zfcp/ZFCPPage.tsx @@ -43,7 +43,7 @@ import { import ZFCPDisksTable from "./ZFCPDisksTable"; import ZFCPControllersTable from "./ZFCPControllersTable"; import { probeZFCP } from "~/api/storage/zfcp"; -import { PATHS } from "~/routes/storage"; +import { STORAGE as PATHS } from "~/routes/paths"; import { useNavigate } from "react-router-dom"; import { inactiveLuns } from "~/utils/zfcp"; diff --git a/web/src/router.js b/web/src/router.js index 8a97b790e0..ec9abe6369 100644 --- a/web/src/router.js +++ b/web/src/router.js @@ -33,18 +33,9 @@ import productsRoutes from "~/routes/products"; import storageRoutes from "~/routes/storage"; import softwareRoutes from "~/routes/software"; import usersRoutes from "~/routes/users"; +import { ROOT as PATHS } from "./routes/paths"; import { N_ } from "~/i18n"; -const PATHS = { - root: "/", - login: "/login", - overview: "/overview", - installation: "/installation", - installationProgress: "/installation/progress", - installationFinished: "/installation/finished", - logs: "/api/manager/logs.tar.gz", -}; - const rootRoutes = () => [ { path: "/overview", diff --git a/web/src/routes/l10n.tsx b/web/src/routes/l10n.tsx index eed610d5c5..62ad35e55f 100644 --- a/web/src/routes/l10n.tsx +++ b/web/src/routes/l10n.tsx @@ -23,15 +23,9 @@ import React from "react"; import { L10nPage, LocaleSelection, KeymapSelection, TimezoneSelection } from "~/components/l10n"; import { Route } from "~/types/routes"; +import { L10N as PATHS } from "~/routes/paths"; import { N_ } from "~/i18n"; -const PATHS = { - root: "/l10n", - localeSelection: "/l10n/locale/select", - keymapSelection: "/l10n/keymap/select", - timezoneSelection: "/l10n/timezone/select", -}; - const routes = (): Route => ({ path: PATHS.root, handle: { diff --git a/web/src/routes/network.tsx b/web/src/routes/network.tsx index 79a30a54b2..7e73609172 100644 --- a/web/src/routes/network.tsx +++ b/web/src/routes/network.tsx @@ -23,14 +23,9 @@ import React from "react"; import { NetworkPage, IpSettingsForm, WifiSelectorPage } from "~/components/network"; import { Route } from "~/types/routes"; +import { NETWORK as PATHS } from "~/routes/paths"; import { N_ } from "~/i18n"; -const PATHS = { - root: "/network", - editConnection: "/network/connections/:id/edit", - wifis: "/network/wifis", -}; - const routes = (): Route => ({ path: PATHS.root, handle: { diff --git a/web/src/routes/paths.ts b/web/src/routes/paths.ts new file mode 100644 index 0000000000..0d4e5fb56b --- /dev/null +++ b/web/src/routes/paths.ts @@ -0,0 +1,86 @@ +/* + * 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. + */ + +const L10N = { + root: "/l10n", + localeSelection: "/l10n/locale/select", + keymapSelection: "/l10n/keymap/select", + timezoneSelection: "/l10n/timezone/select", +}; + +const NETWORK = { + root: "/network", + editConnection: "/network/connections/:id/edit", + wifis: "/network/wifis", +}; + +const PRODUCT = { + root: "/products", + changeProduct: "/products", + progress: "/products/progress", +}; + +const ROOT = { + root: "/", + login: "/login", + overview: "/overview", + installation: "/installation", + installationProgress: "/installation/progress", + installationFinished: "/installation/finished", + logs: "/api/manager/logs.tar.gz", +}; + +const USER = { + root: "/users", + firstUser: { + create: "/users/first", + edit: "/users/first/edit", + }, +}; + +const SOFTWARE = { + root: "/software", + patternsSelection: "/software/patterns/select", +}; + +const STORAGE = { + root: "/storage", + targetDevice: "/storage/target-device", + bootingPartition: "/storage/booting-partition", + spacePolicy: "/storage/space-policy", + iscsi: "/storage/iscsi", + dasd: "/storage/dasd", + zfcp: { + root: "/storage/zfcp", + activateDisk: "/storage/zfcp/active-disk", + }, +}; + +export { + L10N, + NETWORK, + PRODUCT, + ROOT, + SOFTWARE, + STORAGE, + USER, +}; diff --git a/web/src/routes/products.tsx b/web/src/routes/products.tsx index c6e41f7a2f..7185b5e9cd 100644 --- a/web/src/routes/products.tsx +++ b/web/src/routes/products.tsx @@ -23,12 +23,7 @@ import React from "react"; import { ProductSelectionPage, ProductSelectionProgress } from "~/components/product"; import { Route } from "~/types/routes"; - -const PATHS = { - root: "/products", - changeProduct: "/products", - progress: "/products/progress", -}; +import { PRODUCT as PATHS } from "~/routes/paths"; const routes = (): Route => ({ path: PATHS.root, @@ -45,4 +40,3 @@ const routes = (): Route => ({ }); export default routes; -export { PATHS }; diff --git a/web/src/routes/software.tsx b/web/src/routes/software.tsx index 83ddf307be..2795a0c38f 100644 --- a/web/src/routes/software.tsx +++ b/web/src/routes/software.tsx @@ -24,13 +24,9 @@ import React from "react"; import SoftwarePage from "~/components/software/SoftwarePage"; import SoftwarePatternsSelection from "~/components/software/SoftwarePatternsSelection"; import { Route } from "~/types/routes"; +import { SOFTWARE as PATHS } from "~/routes/paths"; import { N_ } from "~/i18n"; -const PATHS = { - root: "/software", - patternsSelection: "/software/patterns/select", -}; - const routes = (): Route => ({ path: PATHS.root, handle: { diff --git a/web/src/routes/storage.tsx b/web/src/routes/storage.tsx index e7f2ccf73b..975c3b8d86 100644 --- a/web/src/routes/storage.tsx +++ b/web/src/routes/storage.tsx @@ -26,25 +26,13 @@ import SpacePolicySelection from "~/components/storage/SpacePolicySelection"; import { DeviceSelection, ISCSIPage, ProposalPage } from "~/components/storage"; import { Route } from "~/types/routes"; -import { N_ } from "~/i18n"; import { supportedDASD, probeDASD } from "~/api/storage/dasd"; import { probeZFCP, supportedZFCP } from "~/api/storage/zfcp"; import { redirect } from "react-router-dom"; import { ZFCPPage, ZFCPDiskActivationPage } from "~/components/storage/zfcp"; import { DASDPage } from "~/components/storage/dasd"; - -const PATHS = { - root: "/storage", - targetDevice: "/storage/target-device", - bootingPartition: "/storage/booting-partition", - spacePolicy: "/storage/space-policy", - iscsi: "/storage/iscsi", - dasd: "/storage/dasd", - zfcp: { - root: "/storage/zfcp", - activateDisk: "/storage/zfcp/active-disk", - }, -}; +import { STORAGE as PATHS } from "~/routes/paths"; +import { N_ } from "~/i18n"; const routes = (): Route => ({ path: PATHS.root, diff --git a/web/src/routes/users.tsx b/web/src/routes/users.tsx index 82a190f9d9..05d398ccc4 100644 --- a/web/src/routes/users.tsx +++ b/web/src/routes/users.tsx @@ -24,15 +24,9 @@ import React from "react"; import UsersPage from "~/components/users/UsersPage"; import FirstUserForm from "~/components/users/FirstUserForm"; import { Route } from "~/types/routes"; +import { USER as PATHS } from "~/routes/paths"; import { N_ } from "~/i18n"; -const PATHS = { - root: "/users", - firstUser: { - create: "/users/first", - edit: "/users/first/edit", - }, -}; const routes = (): Route => ({ path: PATHS.root, handle: { From ac95ac8d2ce0d64977fd5cc6252ed0636a345421 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20D=C3=ADaz=20Gonz=C3=A1lez?= Date: Wed, 30 Oct 2024 22:34:24 +0000 Subject: [PATCH 4/5] doc(web) add entry in changes file --- web/package/agama-web-ui.changes | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/web/package/agama-web-ui.changes b/web/package/agama-web-ui.changes index 1643765ffa..7f6a0a5a6b 100644 --- a/web/package/agama-web-ui.changes +++ b/web/package/agama-web-ui.changes @@ -1,3 +1,10 @@ +------------------------------------------------------------------- +Wed Oct 30 22:26:29 UTC 2024 - David Diaz + +- Render Install button only where it makes sense and prevent the + user starting the installation by mistake + (gh#agama-project/agama#1717). + ------------------------------------------------------------------- Mon Oct 28 19:26:29 UTC 2024 - David Diaz From 29068f1af8c48c689bf9de47a01bb35fe27663b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20D=C3=ADaz=20Gonz=C3=A1lez?= Date: Wed, 30 Oct 2024 22:42:00 +0000 Subject: [PATCH 5/5] fix(web): make prettier happy --- web/src/routes/paths.ts | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/web/src/routes/paths.ts b/web/src/routes/paths.ts index 0d4e5fb56b..31b07e639e 100644 --- a/web/src/routes/paths.ts +++ b/web/src/routes/paths.ts @@ -75,12 +75,4 @@ const STORAGE = { }, }; -export { - L10N, - NETWORK, - PRODUCT, - ROOT, - SOFTWARE, - STORAGE, - USER, -}; +export { L10N, NETWORK, PRODUCT, ROOT, SOFTWARE, STORAGE, USER };