diff --git a/web/src/components/questions/UnsupportedAutoYaST.tsx b/web/src/components/questions/UnsupportedAutoYaST.tsx index 60f8a7268c..cb532a9a2c 100644 --- a/web/src/components/questions/UnsupportedAutoYaST.tsx +++ b/web/src/components/questions/UnsupportedAutoYaST.tsx @@ -102,7 +102,6 @@ export default function UnsupportedAutoYaST({ {/* gettext v0.26 does not handle correctly escaped single quote inside */} {/* a single quote string ('foo\'s') so split it into several parts */} - {/* eslint-disable agama-i18n/string-literals */} {_( 'If you want to disable this check, please specify "inst.ay_check=0" at kernel' + "'s command-line", diff --git a/web/src/components/storage/FixableConfigInfo.tsx b/web/src/components/storage/FixableConfigInfo.tsx index df62fc0eef..548a39c5d2 100644 --- a/web/src/components/storage/FixableConfigInfo.tsx +++ b/web/src/components/storage/FixableConfigInfo.tsx @@ -23,9 +23,9 @@ import React from "react"; import { Alert, List, ListItem } from "@patternfly/react-core"; import { n_ } from "~/i18n"; -import { useConfigIssues } from "~/hooks/storage/issue"; +import type { Issue } from "~/api/issue"; -const Description = ({ errors }) => { +const Description = ({ errors }: Issue[]) => { return ( {errors.map((e, i) => ( @@ -39,20 +39,16 @@ const Description = ({ errors }) => { * Information about a wrong but fixable storage configuration * */ -export default function FixableConfigInfo() { - const configErrors = useConfigIssues(); - - if (!configErrors.length) return; - +export default function FixableConfigInfo({ issues }: Issue[]) { const title = n_( "The configuration must be adapted to address the following issue:", "The configuration must be adapted to address the following issues:", - configErrors.length, + issues.length, ); return ( - + ); } diff --git a/web/src/components/storage/ProposalFailedInfo.tsx b/web/src/components/storage/ProposalFailedInfo.tsx index 83fe91392a..c8292e5776 100644 --- a/web/src/components/storage/ProposalFailedInfo.tsx +++ b/web/src/components/storage/ProposalFailedInfo.tsx @@ -23,8 +23,6 @@ import React from "react"; import { Alert, Content } from "@patternfly/react-core"; import { useStorageModel } from "~/hooks/api/storage"; -import { useIssues } from "~/hooks/api/issue"; -import { useConfigIssues } from "~/hooks/storage/issue"; import * as partitionUtils from "~/components/storage/utils/partition"; import { _, formatList } from "~/i18n"; import { sprintf } from "sprintf-js"; @@ -81,19 +79,8 @@ const Description = () => { /** * Displays information to help users understand why a storage proposal * could not be generated with the current configuration. - * - * Renders nothing if: - * - The proposal could not be generated at all (known by the presence of - * configuration errors in the storage scope) - * - The generated proposal contains no errors. */ export default function ProposalFailedInfo() { - const configIssues = useConfigIssues(); - const issues = useIssues("storage"); - - if (configIssues.length !== 0) return; - if (issues.length === 0) return; - return ( diff --git a/web/src/components/storage/ProposalPage.tsx b/web/src/components/storage/ProposalPage.tsx index 987be9b47d..bd081b9019 100644 --- a/web/src/components/storage/ProposalPage.tsx +++ b/web/src/components/storage/ProposalPage.tsx @@ -25,7 +25,6 @@ import { Button, Content, Grid, - GridItem, Split, SplitItem, Stack, @@ -53,7 +52,7 @@ import ProposalResultSection from "./ProposalResultSection"; import ProposalTransactionalInfo from "./ProposalTransactionalInfo"; import UnsupportedModelInfo from "./UnsupportedModelInfo"; import { useAvailableDevices } from "~/hooks/api/system/storage"; -import { useConfigIssues } from "~/hooks/storage/issue"; +import { useIssues } from "~/hooks/api/issue"; import { useReset } from "~/hooks/api/config/storage"; import { useProposal } from "~/hooks/api/proposal/storage"; import { useStorageModel } from "~/hooks/api/storage"; @@ -67,8 +66,7 @@ import { useStorageUiState } from "~/context/storage-ui-state"; import MenuButton from "../core/MenuButton"; import spacingStyles from "@patternfly/react-styles/css/utilities/Spacing/spacing"; -function InvalidConfigEmptyState(): React.ReactNode { - const errors = useConfigIssues(); +function InvalidConfigEmptyState({ issues }: Issue[]): React.ReactNode { const reset = useReset(); return ( @@ -82,11 +80,11 @@ function InvalidConfigEmptyState(): React.ReactNode { {n_( "The current storage configuration has the following issue:", "The current storage configuration has the following issues:", - errors.length, + issues.length, )} - {errors.map((e, i) => ( + {issues.map((e, i) => ( {e.description} ))} @@ -105,7 +103,7 @@ function InvalidConfigEmptyState(): React.ReactNode { ); } -function UnknowConfigEmptyState(): React.ReactNode { +function UnknownConfigEmptyState(): React.ReactNode { const reset = useReset(); return ( @@ -175,19 +173,8 @@ function UnavailableDevicesEmptyState(): React.ReactNode { ); } -function ProposalEmptyState(): React.ReactNode { - const model = useStorageModel(); - const availableDevices = useAvailableDevices(); - - if (!availableDevices.length) return ; - - return model ? : ; -} - -function ProposalSections(): React.ReactNode { +function ModelSection(): React.ReactNode { const { uiState, setUiState } = useStorageUiState(); - const model = useStorageModel(); - const proposal = useProposal(); const reset = useReset(); const handleTabClick = ( event: React.MouseEvent | React.KeyboardEvent | MouseEvent, @@ -208,88 +195,101 @@ function ProposalSections(): React.ReactNode { }); }; + return ( + + + + {_("Reset to defaults")} + , + ]} + > + + + + } + description={_( + "Changes in these settings will immediately update the 'Result' section below.", + )} + > + + {_("Installation devices")}} + > + + +
+ {_( + "Structure of the new system, including disks to use and additional devices like LVM volume groups.", + )} +
+ +
+
+
+ {_("Encryption")}}> + + + + + {_("Boot options")}}> + + + + +
+
+ ); +} + +function ProposalPageContent(): React.ReactNode { + const model = useStorageModel(); + const availableDevices = useAvailableDevices(); + const proposal = useProposal(); + const issues = useIssues("storage"); + + const fixable = [ + "configNoRoot", + "configRequiredPaths", + "configOverusedPvTarget", + "configOverusedMdMember", + "proposal", + ]; + const configIssues = issues.filter((i) => i.class !== "proposal"); + const unfixableIssues = issues.filter((i) => !fixable.includes(i.class)); + const isModelEditable = model && !unfixableIssues.length; + + if (!availableDevices.length) return ; + if (configIssues.length && !isModelEditable) + return ; + if (!configIssues.length && !model && !proposal) return ; + return ( - - - - {model && ( - <> - - - - - {_("Reset to defaults")} - , - ]} - > - - - - } - description={_( - "Changes in these settings will immediately update the 'Result' section below.", - )} - > - - {_("Installation devices")}} - > - - -
- {_( - "Structure of the new system, including disks to use and additional devices like LVM volume groups.", - )} -
- -
-
-
- {_("Encryption")}} - > - - - - - {_("Boot options")}} - > - - - - -
-
-
- - )} + {!configIssues.length && !proposal && } + {!!configIssues.length && } + {!model && } + {model && } {proposal && }
); @@ -300,10 +300,6 @@ function ProposalSections(): React.ReactNode { * and test them individually. The proposal page should simply mount all those components. */ export default function ProposalPage(): React.ReactNode { - const model = useStorageModel(); - const availableDevices = useAvailableDevices(); - const proposal = useProposal(); - const configIssues = useConfigIssues(); const progress = useProgress("storage"); const navigate = useNavigate(); const location = useLocation(); @@ -324,18 +320,6 @@ export default function ProposalPage(): React.ReactNode { } }, [resetNeeded, setUiState]); - const fixable = [ - "configNoRoot", - "configRequiredPaths", - "configOverusedPvTarget", - "configOverusedMdMember", - ]; - const unfixableIssues = configIssues.filter((e) => !fixable.includes(e.class)); - const isModelEditable = model && !unfixableIssues.length; - const hasDevices = !!availableDevices.length; - const hasResult = !!proposal; - const showSections = hasDevices && (isModelEditable || hasResult); - if (resetNeeded) return; return ( @@ -352,8 +336,7 @@ export default function ProposalPage(): React.ReactNode { - {!showSections && } - {showSections && } + ); diff --git a/web/src/hooks/storage/issue.ts b/web/src/hooks/storage/issue.ts deleted file mode 100644 index fd6fb7660f..0000000000 --- a/web/src/hooks/storage/issue.ts +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (c) [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 { useSuspenseQuery } from "@tanstack/react-query"; -import { issuesQuery } from "~/hooks/api/issue"; -import type { Issue } from "~/api/issue"; - -function selectIssues(issues: Issue[]) { - return issues.filter((i: Issue) => i.scope === "storage" && i.class !== "proposal"); -} - -function useConfigIssues(): Issue[] { - const { data } = useSuspenseQuery({ - ...issuesQuery, - select: selectIssues, - }); - return data; -} - -export { useConfigIssues };