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
8 changes: 8 additions & 0 deletions web/package/agama-web-ui.changes
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
-------------------------------------------------------------------
Tue Mar 3 10:09:09 UTC 2026 - David Diaz <dgonzalez@suse.com>

- Restores DASD format progress with API v2 event model
(gh#agama-project/agama#3143).
- Improves progress backdrop layout and readbility
(gh#agama-project/agama#2947).

-------------------------------------------------------------------
Mon Mar 2 23:06:08 UTC 2026 - David Diaz <dgonzalez@suse.com>

Expand Down
8 changes: 6 additions & 2 deletions web/src/assets/styles/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -188,14 +188,18 @@ strong {
overflow: hidden;
}

// ProgressBackdrop overlay styles
.pf-v6-c-page__main-container:has(.agm-main-content-overlay) {
position: relative;

.agm-main-content-overlay {
position: absolute;
padding-block-start: 2.2rem;
backdrop-filter: blur(2px);
background-color: color-mix(in srgb, var(--agm-t--color--fog) 50%, transparent);
background-color: color-mix(
in srgb,
var(--pf-t--global--background--color--backdrop--default) 80%,
transparent
);
}
}

Expand Down
55 changes: 52 additions & 3 deletions web/src/components/core/ProgressBackdrop.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
*/

import React from "react";
import { screen, waitFor, within } from "@testing-library/react";
import { act, screen, waitFor, within } from "@testing-library/react";
import { installerRender, mockProgresses } from "~/test-utils";
import useTrackQueriesRefetch from "~/hooks/use-track-queries-refetch";
import { COMMON_PROPOSAL_KEYS } from "~/hooks/model/proposal";
Expand Down Expand Up @@ -85,7 +85,7 @@ describe("ProgressBackdrop", () => {
});
});

it("shows 'Refreshing data...' message temporarily", async () => {
it("shows 'Refreshing data...' message temporarily by default", async () => {
// Start with active progress
mockProgresses([
{
Expand Down Expand Up @@ -115,6 +115,33 @@ describe("ProgressBackdrop", () => {
expect(mockStartTracking).toHaveBeenCalled();
});

it("shows custom `waitingLabel` when provided", async () => {
mockProgresses([
{
scope: "storage",
step: "Calculating proposal",
steps: ["Calculating proposal"],
index: 1,
size: 1,
},
]);

const { rerender } = installerRender(
<ProgressBackdrop scope="storage" waitingLabel="Applying storage settings..." />,
);

const backdrop = screen.getByRole("alert", { name: /Calculating proposal/ });

mockProgresses([]);
rerender(<ProgressBackdrop scope="storage" waitingLabel="Applying storage settings..." />);

await waitFor(() => {
within(backdrop).getByText(/Applying storage settings/);
});

expect(within(backdrop).queryByText(/Refreshing data/)).toBeNull();
});

it("hides backdrop after queries are refetched", async () => {
// Start with active progress
mockProgresses([
Expand Down Expand Up @@ -143,7 +170,9 @@ describe("ProgressBackdrop", () => {

// Simulate queries completing by calling the callback
const startedAt = Date.now();
mockCallback(startedAt, startedAt + 100);
act(() => {
mockCallback(startedAt, startedAt + 100);
});

// Backdrop should be hidden
await waitFor(() => {
Expand Down Expand Up @@ -289,4 +318,24 @@ describe("ProgressBackdrop", () => {
});
});
});
describe("when extraContent is provided", () => {
it("renders the extra content below the progress information", () => {
mockProgresses([
{
scope: "software",
step: "Installing packages",
steps: [],
index: 1,
size: 3,
},
]);

installerRender(
<ProgressBackdrop scope="software" extraContent={<div>Extra content</div>} />,
);

const backdrop = screen.getByRole("alert", { name: /Installing packages/ });
within(backdrop).getByText("Extra content");
});
});
});
130 changes: 101 additions & 29 deletions web/src/components/core/ProgressBackdrop.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,29 @@
*/

import React from "react";
import { Alert, Backdrop, Flex, FlexItem, Spinner } from "@patternfly/react-core";
import { concat } from "radashi";
import { sprintf } from "sprintf-js";
import {
Alert,
Backdrop,
Card,
CardBody,
CardTitle,
Flex,
FlexItem,
Spinner,
} from "@patternfly/react-core";
import NestedContent from "~/components/core/NestedContent";
import { COMMON_PROPOSAL_KEYS } from "~/hooks/model/proposal";
import type { Scope } from "~/model/status";
import { useProgressTracking } from "~/hooks/use-progress-tracking";
import { _ } from "~/i18n";

import type { Scope } from "~/model/status";

import textStyles from "@patternfly/react-styles/css/utilities/Text/text";
import { useProgressTracking } from "~/hooks/use-progress-tracking";
import sizingStyles from "@patternfly/react-styles/css/utilities/Sizing/sizing";
import spacingStyles from "@patternfly/react-styles/css/utilities/Spacing/spacing";
import shadowStyles from "@patternfly/react-styles/css/utilities/BoxShadow/box-shadow";

/**
* Props for the ProgressBackdrop component.
Expand All @@ -40,7 +55,6 @@ export type ProgressBackdropProps = {
* displayed.
*/
scope: Scope;

/**
* Additional query keys to track during progress operations.
*
Expand All @@ -64,6 +78,26 @@ export type ProgressBackdropProps = {
* >
*/
ensureRefetched?: string | string[];
/**
* Additional content to render below the progress information.
*
* Use this to display extra UI within the backdrop overlay, such as
* per-device progress details for long-running operations.
*
* @example
* <ProgressBackdrop scope="dasd" extraContent={<DASDFormatProgress />} />
*/
extraContent?: React.ReactNode;
/**
* Label displayed when no active progress step is available but the backdrop
* is still visible because queries have not finished refetching yet.
*
* Defaults to `"Refreshing data..."` if not provided.
*
* @example
* <ProgressBackdrop scope="storage" waitingLabel={_("Applying changes...")} />
*/
waitingLabel?: string;
};

/**
Expand All @@ -84,6 +118,10 @@ export type ProgressBackdropProps = {
export default function ProgressBackdrop({
scope,
ensureRefetched,
extraContent,
// TRANSLATORS: Message shown next to a spinner while the UI is being updated
// after an operation has completed.
waitingLabel = _("Refreshing data..."),
}: ProgressBackdropProps): React.ReactNode {
const { loading: isBlocked, progress } = useProgressTracking(
scope,
Expand All @@ -93,31 +131,65 @@ export default function ProgressBackdrop({
if (!isBlocked) return null;

return (
<Backdrop className="agm-main-content-overlay" role="alert" aria-labelledby="progressStatus">
<Alert
isPlain
customIcon={<></>}
title={
<Flex
id="progressStatus"
gap={{ default: "gapMd" }}
alignItems={{ default: "alignItemsCenter" }}
className={textStyles.fontSizeXl}
>
<Spinner size="lg" aria-hidden />
<FlexItem>
{progress ? (
<>
{progress.step}{" "}
<small>{sprintf(_("(step %s of %s)"), progress.index, progress.size)}</small>
</>
) : (
<>{_("Refreshing data...")}</>
)}
</FlexItem>
</Flex>
}
/>
<Backdrop
role="alert"
aria-labelledby="progressStatus"
className={["agm-main-content-overlay", spacingStyles.pt_4xl, spacingStyles.pt_0OnMd].join(
" ",
)}
>
<Flex
alignContent={{ default: "alignContentFlexStart", md: "alignContentCenter" }}
justifyContent={{ default: "justifyContentCenter" }}
className={sizingStyles.h_100}
>
<Card
isCompact
className={[
sizingStyles.w_100,
sizingStyles.w_75OnMd,
sizingStyles.w_50OnLg,
spacingStyles.mxMd,
spacingStyles.mx_0OnMd,
shadowStyles.boxShadowMdBottom,
].join(" ")}
style={{ maxHeight: "90%", overflow: "hidden" }}
>
<CardTitle>
<Alert
isPlain
customIcon={<></>}
title={
<Flex
id="progressStatus"
gap={{ default: "gapMd" }}
alignItems={{ default: "alignItemsCenter" }}
flexWrap={{ default: "nowrap" }}
className={textStyles.fontSizeLg}
style={{ textWrap: "balance" }}
>
<Spinner size="md" aria-hidden />
<FlexItem>
{progress ? (
<>
{progress.step}{" "}
<small>
{sprintf(_("(step %s of %s)"), progress.index, progress.size)}
</small>
</>
) : (
<>{waitingLabel}</>
)}
</FlexItem>
</Flex>
}
/>
</CardTitle>
<CardBody style={{ overflow: "auto" }}>
{extraContent && <NestedContent margin="mxXl">{extraContent}</NestedContent>}
</CardBody>
</Card>
</Flex>
</Backdrop>
);
}
Loading
Loading