Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion web/src/components/questions/UnsupportedAutoYaST.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,6 @@ export default function UnsupportedAutoYaST({
<Content component="small">
{/* 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",
Expand Down
14 changes: 5 additions & 9 deletions web/src/components/storage/FixableConfigInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<List isPlain>
{errors.map((e, i) => (
Expand All @@ -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 (
<Alert variant="warning" title={title}>
<Description errors={configErrors} />
<Description errors={issues} />
</Alert>
);
}
13 changes: 0 additions & 13 deletions web/src/components/storage/ProposalFailedInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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 (
<Alert variant="warning" title={_("Failed to calculate a storage layout")}>
<Description />
Expand Down
215 changes: 99 additions & 116 deletions web/src/components/storage/ProposalPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import {
Button,
Content,
Grid,
GridItem,
Split,
SplitItem,
Stack,
Expand Down Expand Up @@ -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";
Expand All @@ -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 (
Expand All @@ -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,
)}
</Content>
<List isPlain>
{errors.map((e, i) => (
{issues.map((e, i) => (
<ListItem key={i}>{e.description}</ListItem>
))}
</List>
Expand All @@ -105,7 +103,7 @@ function InvalidConfigEmptyState(): React.ReactNode {
);
}

function UnknowConfigEmptyState(): React.ReactNode {
function UnknownConfigEmptyState(): React.ReactNode {
const reset = useReset();

return (
Expand Down Expand Up @@ -175,19 +173,8 @@ function UnavailableDevicesEmptyState(): React.ReactNode {
);
}

function ProposalEmptyState(): React.ReactNode {
const model = useStorageModel();
const availableDevices = useAvailableDevices();

if (!availableDevices.length) return <UnavailableDevicesEmptyState />;

return model ? <InvalidConfigEmptyState /> : <UnknowConfigEmptyState />;
}

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,
Expand All @@ -208,88 +195,101 @@ function ProposalSections(): React.ReactNode {
});
};

return (
<Page.Section
title={_("Settings")}
titleActions={
<Flex>
<FlexItem grow={{ default: "grow" }} />
<MenuButton
menuProps={{
popperProps: {
position: "end",
},
}}
toggleProps={{
variant: "plain",
className: spacingStyles.p_0,
}}
items={[
<MenuButton.Item
key="reset-link"
onClick={onReset}
description={_("Start from scratch with the default configuration")}
>
{_("Reset to defaults")}
</MenuButton.Item>,
]}
>
<Icon name="more_horiz" className="agm-three-dots-icon" />
</MenuButton>
</Flex>
}
description={_(
"Changes in these settings will immediately update the 'Result' section below.",
)}
>
<Tabs activeKey={uiState.get("st") || "0"} onSelect={handleTabClick} role="region">
<Tab
key="devices"
eventKey={"0"}
title={<TabTitleText>{_("Installation devices")}</TabTitleText>}
>
<NestedContent margin="mtSm">
<Stack hasGutter>
<div className={textStyles.textColorPlaceholder}>
{_(
"Structure of the new system, including disks to use and additional devices like LVM volume groups.",
)}
</div>
<ConfigEditor />
</Stack>
</NestedContent>
</Tab>
<Tab key="encryption" eventKey={"1"} title={<TabTitleText>{_("Encryption")}</TabTitleText>}>
<NestedContent margin="mtSm">
<EncryptionSection />
</NestedContent>
</Tab>
<Tab key="system" eventKey={"2"} title={<TabTitleText>{_("Boot options")}</TabTitleText>}>
<NestedContent margin="mtSm">
<BootSection />
</NestedContent>
</Tab>
</Tabs>
</Page.Section>
);
}

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 <UnavailableDevicesEmptyState />;
if (configIssues.length && !isModelEditable)
return <InvalidConfigEmptyState issues={configIssues} />;
if (!configIssues.length && !model && !proposal) return <UnknownConfigEmptyState />;

return (
<Grid hasGutter>
<ProposalTransactionalInfo />
<ProposalFailedInfo />
<FixableConfigInfo />
<UnsupportedModelInfo />
{model && (
<>
<GridItem>
<Page.Section
title={_("Settings")}
titleActions={
<Flex>
<FlexItem grow={{ default: "grow" }} />
<MenuButton
menuProps={{
popperProps: {
position: "end",
},
}}
toggleProps={{
variant: "plain",
className: spacingStyles.p_0,
}}
items={[
<MenuButton.Item
key="reset-link"
onClick={onReset}
description={_("Start from scratch with the default configuration")}
>
{_("Reset to defaults")}
</MenuButton.Item>,
]}
>
<Icon name="more_horiz" className="agm-three-dots-icon" />
</MenuButton>
</Flex>
}
description={_(
"Changes in these settings will immediately update the 'Result' section below.",
)}
>
<Tabs activeKey={uiState.get("st") || "0"} onSelect={handleTabClick} role="region">
<Tab
key="devices"
eventKey={"0"}
title={<TabTitleText>{_("Installation devices")}</TabTitleText>}
>
<NestedContent margin="mtSm">
<Stack hasGutter>
<div className={textStyles.textColorPlaceholder}>
{_(
"Structure of the new system, including disks to use and additional devices like LVM volume groups.",
)}
</div>
<ConfigEditor />
</Stack>
</NestedContent>
</Tab>
<Tab
key="encryption"
eventKey={"1"}
title={<TabTitleText>{_("Encryption")}</TabTitleText>}
>
<NestedContent margin="mtSm">
<EncryptionSection />
</NestedContent>
</Tab>
<Tab
key="system"
eventKey={"2"}
title={<TabTitleText>{_("Boot options")}</TabTitleText>}
>
<NestedContent margin="mtSm">
<BootSection />
</NestedContent>
</Tab>
</Tabs>
</Page.Section>
</GridItem>
</>
)}
{!configIssues.length && !proposal && <ProposalFailedInfo />}
{!!configIssues.length && <FixableConfigInfo issues={configIssues} />}
{!model && <UnsupportedModelInfo />}
{model && <ModelSection />}
{proposal && <ProposalResultSection />}
</Grid>
);
Expand All @@ -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();
Expand All @@ -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 (
Expand All @@ -352,8 +336,7 @@ export default function ProposalPage(): React.ReactNode {
</Flex>
</Page.Header>
<Page.Content>
{!showSections && <ProposalEmptyState />}
{showSections && <ProposalSections />}
<ProposalPageContent />
</Page.Content>
</Page>
);
Expand Down
Loading
Loading