Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package agama-web-ui for openSUSE:Factory 
checked in at 2025-06-03 19:11:34
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/agama-web-ui (Old)
 and      /work/SRC/openSUSE:Factory/.agama-web-ui.new.16005 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "agama-web-ui"

Tue Jun  3 19:11:34 2025 rev:17 rq:1282416 version:0

Changes:
--------
--- /work/SRC/openSUSE:Factory/agama-web-ui/agama-web-ui.changes        
2025-05-27 18:43:11.323072232 +0200
+++ /work/SRC/openSUSE:Factory/.agama-web-ui.new.16005/agama-web-ui.changes     
2025-06-03 19:11:38.572169990 +0200
@@ -1,0 +2,8 @@
+Tue Jun  3 08:32:23 UTC 2025 - Knut Anderssen <kanders...@suse.com>
+
+- Allow to select which connections will be used only for
+  installation and warn the user in case that there is no one
+  expected to be copied to the target system (no network).
+  (gh#agama-project/agama#2402).
+
+-------------------------------------------------------------------

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ agama.obscpio ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/agama/package/agama-web-ui.changes 
new/agama/package/agama-web-ui.changes
--- old/agama/package/agama-web-ui.changes      2025-05-27 09:03:23.000000000 
+0200
+++ new/agama/package/agama-web-ui.changes      2025-06-03 17:34:25.000000000 
+0200
@@ -1,4 +1,12 @@
 -------------------------------------------------------------------
+Tue Jun  3 08:32:23 UTC 2025 - Knut Anderssen <kanders...@suse.com>
+
+- Allow to select which connections will be used only for
+  installation and warn the user in case that there is no one
+  expected to be copied to the target system (no network).
+  (gh#agama-project/agama#2402).
+
+-------------------------------------------------------------------
 Mon May 26 19:51:54 UTC 2025 - Imobach Gonzalez Sosa <igonzalezs...@suse.com>
 
 - Version 15
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/agama/src/api/network.ts new/agama/src/api/network.ts
--- old/agama/src/api/network.ts        2025-05-27 09:03:23.000000000 +0200
+++ new/agama/src/api/network.ts        2025-06-03 17:34:25.000000000 +0200
@@ -20,7 +20,7 @@
  * find current contact information at www.suse.com.
  */
 
-import { del, get, patch, post, put } from "~/api/http";
+import { del, get, post, put } from "~/api/http";
 import { APIAccessPoint, APIConnection, APIDevice, NetworkGeneralState } from 
"~/types/network";
 
 /**
@@ -77,12 +77,22 @@
 /**
  * Performs the connect action for connection matching given name
  */
-const connect = (name: string) => 
patch(`/api/network/connections/${name}/connect`);
+const connect = (name: string) => 
post(`/api/network/connections/${name}/connect`);
 
 /**
  * Performs the disconnect action for connection matching given name
  */
-const disconnect = (name: string) => 
patch(`/api/network/connections/${name}/disconnect`);
+const disconnect = (name: string) => 
post(`/api/network/connections/${name}/disconnect`);
+
+/**
+ * Make the connection persistent after the installation
+ */
+const keep = (name: string) => post(`/api/network/connections/${name}/keep`);
+
+/**
+ * Make the connection to be used only for the installation
+ */
+const unkeep = (name: string) => 
post(`/api/network/connections/${name}/unkeep`);
 
 export {
   fetchState,
@@ -96,4 +106,6 @@
   deleteConnection,
   connect,
   disconnect,
+  keep,
+  unkeep,
 };
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/agama/src/assets/styles/index.scss 
new/agama/src/assets/styles/index.scss
--- old/agama/src/assets/styles/index.scss      2025-05-27 09:03:23.000000000 
+0200
+++ new/agama/src/assets/styles/index.scss      2025-06-03 17:34:25.000000000 
+0200
@@ -132,6 +132,10 @@
   --pf-t--global--font--family--200: "Noto Sans KR Display", "Noto Sans KR", 
serif;
 }
 
+strong {
+  font-weight: var(--pf-t--global--font--weight--400);
+}
+
 // Temporary CSS rules written during migration to PFv6
 
 // Reserve the sidebar space also for "lg" breakpoint
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/agama/src/components/core/Annotation.test.tsx 
new/agama/src/components/core/Annotation.test.tsx
--- old/agama/src/components/core/Annotation.test.tsx   1970-01-01 
01:00:00.000000000 +0100
+++ new/agama/src/components/core/Annotation.test.tsx   2025-06-03 
17:34:25.000000000 +0200
@@ -0,0 +1,51 @@
+/*
+ * 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 React from "react";
+import { screen } from "@testing-library/react";
+import { plainRender } from "~/test-utils";
+import Annotation from "./Annotation";
+
+describe("Annotation", () => {
+  it('renders the default "emergency" icon when no icon is provided', () => {
+    const { container } = plainRender(<Annotation>Configured for installation 
only</Annotation>);
+
+    const icon = container.querySelector("svg");
+    expect(icon).toHaveAttribute("data-icon-name", "emergency");
+  });
+
+  it("renders a custom icon when icon is provided", () => {
+    const { container } = plainRender(
+      <Annotation icon="info">Configured for installation only</Annotation>,
+    );
+
+    const icon = container.querySelector("svg");
+    expect(icon).toHaveAttribute("data-icon-name", "info");
+  });
+
+  it("renders children inside a <b> element", () => {
+    plainRender(<Annotation>Configured for installation only</Annotation>);
+
+    const content = screen.getByText("Configured for installation only");
+    expect(content.tagName).toBe("STRONG");
+  });
+});
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/agama/src/components/core/Annotation.tsx 
new/agama/src/components/core/Annotation.tsx
--- old/agama/src/components/core/Annotation.tsx        1970-01-01 
01:00:00.000000000 +0100
+++ new/agama/src/components/core/Annotation.tsx        2025-06-03 
17:34:25.000000000 +0200
@@ -0,0 +1,53 @@
+/*
+ * 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 React from "react";
+import { Flex } from "@patternfly/react-core";
+import { Icon } from "~/components/layout";
+import { IconProps } from "../layout/Icon";
+
+type AnnotationProps = React.PropsWithChildren<{
+  /** Name of the icon to display alongside the annotation. */
+  icon?: IconProps["name"];
+}>;
+
+/**
+ * Displays a short note or clarification, wrapped in a `<strong>` HTML 
element.
+ *
+ * Intended for non-alert annotations that still require emphasis. The icon is
+ * optional and defaults to "emergency" (asterisk) if not provided.
+ *
+ * For more details on the `<strong>` element, refer to the HTML specification:
+ * 
https://html.spec.whatwg.org/multipage/text-level-semantics.html#the-strong-element
+ *
+ * @example
+ * ```tsx
+ * <Annotation icon="info">Configured for installation only.</Annotation>
+ * ```
+ */
+export default function Annotation({ icon = "emergency", children }: 
AnnotationProps) {
+  return (
+    <Flex component="p" alignItems={{ default: "alignItemsCenter" }} gap={{ 
default: "gapXs" }}>
+      <Icon name={icon} /> <strong>{children}</strong>
+    </Flex>
+  );
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/agama/src/components/core/SwitchEnhanced.test.tsx 
new/agama/src/components/core/SwitchEnhanced.test.tsx
--- old/agama/src/components/core/SwitchEnhanced.test.tsx       1970-01-01 
01:00:00.000000000 +0100
+++ new/agama/src/components/core/SwitchEnhanced.test.tsx       2025-06-03 
17:34:25.000000000 +0200
@@ -0,0 +1,67 @@
+/*
+ * 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 React from "react";
+import { screen } from "@testing-library/react";
+import { plainRender } from "~/test-utils";
+import SwitchEnhanced from "./SwitchEnhanced";
+
+describe("SwitchEnhanced", () => {
+  it("renders a switch with label and description", () => {
+    plainRender(
+      <SwitchEnhanced
+        id="installation-only-connection"
+        label="Use for installation only"
+        description="Not persisted to the installed system."
+        isChecked={false}
+      />,
+    );
+
+    const switchElement = screen.getByRole("switch");
+    const label = screen.getByText(/Use for installation only/);
+    const description = screen.getByText(/Not persisted to the installed 
system/);
+
+    // Ensure aria-labelledby and aria-describedby point to the correct 
elements
+    expect(switchElement).toHaveAttribute("aria-labelledby", label.id);
+    expect(switchElement).toHaveAttribute("aria-describedby", description.id);
+  });
+
+  it("fires onChange handler when toggled", async () => {
+    const onChangeMock = jest.fn();
+
+    const { user } = plainRender(
+      <SwitchEnhanced
+        id="installation-only-connection"
+        label="Use for installation only"
+        description="Not persisted to the installed system."
+        isChecked={false}
+        onChange={onChangeMock}
+      />,
+    );
+
+    const switchElement = screen.getByRole("switch");
+
+    await user.click(switchElement);
+
+    expect(onChangeMock).toHaveBeenCalledTimes(1);
+  });
+});
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/agama/src/components/core/SwitchEnhanced.tsx 
new/agama/src/components/core/SwitchEnhanced.tsx
--- old/agama/src/components/core/SwitchEnhanced.tsx    1970-01-01 
01:00:00.000000000 +0100
+++ new/agama/src/components/core/SwitchEnhanced.tsx    2025-06-03 
17:34:25.000000000 +0200
@@ -0,0 +1,76 @@
+/*
+ * 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 React, { useId } from "react";
+import { Content, Flex, FlexItem, Switch, SwitchProps } from 
"@patternfly/react-core";
+
+type SwitchEnhancedProps = Omit<
+  SwitchProps,
+  "ref" | "label" | "aria-labelledby" | "aria-describedby"
+> & {
+  /** Must describe the isChecked="true" state. */
+  label: React.ReactNode;
+  /** Description or helper text displayed below the label. */
+  description: React.ReactNode;
+};
+
+/**
+ * A wrapper around PatternFly's `Switch` component that adds support for a
+ * description or helper text displayed below the label.
+ *
+ * This component is useful when users can benefit from additional context 
about
+ * the switch’s function.
+ *
+ * Use this component when a toggle requires more explanation. If no 
description
+ * is needed, prefer using the standard PatternFly `Switch` component directly.
+ *
+ * @example
+ * ```tsx
+ * <SwitchEnhanced
+ *   id="installation-only-connection"
+ *   label="Use for installation only"
+ *   description="The connection will be used only during installation and not 
persisted to the installed system."
+ *   isChecked={isEnabled}
+ *   onChange={toggleEmailNotifications}
+ * />
+ * ```
+ */
+export default function SwitchEnhanced({ description, label, ...props }: 
SwitchEnhancedProps) {
+  const labelId = useId();
+  const descriptionId = useId();
+
+  return (
+    <Flex flexWrap={{ default: "nowrap" }}>
+      <FlexItem>
+        <Switch {...props} aria-labelledby={labelId} 
aria-describedby={descriptionId} />
+      </FlexItem>
+      <FlexItem>
+        <Content isEditorial id={labelId}>
+          {label}
+        </Content>
+        <Content component="small" id={descriptionId}>
+          {description}
+        </Content>
+      </FlexItem>
+    </Flex>
+  );
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/agama/src/components/core/index.ts 
new/agama/src/components/core/index.ts
--- old/agama/src/components/core/index.ts      2025-05-27 09:03:23.000000000 
+0200
+++ new/agama/src/components/core/index.ts      2025-06-03 17:34:25.000000000 
+0200
@@ -49,3 +49,5 @@
 export { default as MenuHeader } from "./MenuHeader";
 export { default as SplitButton } from "./SplitButton";
 export { default as SkipTo } from "./SkipTo";
+export { default as Annotation } from "./Annotation";
+export { default as SwitchEnhanced } from "./SwitchEnhanced";
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/agama/src/components/layout/Icon.tsx 
new/agama/src/components/layout/Icon.tsx
--- old/agama/src/components/layout/Icon.tsx    2025-05-27 09:03:23.000000000 
+0200
+++ new/agama/src/components/layout/Icon.tsx    2025-06-03 17:34:25.000000000 
+0200
@@ -32,6 +32,7 @@
 import ChevronRight from "@icons/chevron_right.svg?component";
 import Delete from "@icons/delete.svg?component";
 import EditSquare from "@icons/edit_square.svg?component";
+import Emergency from "@icons/emergency.svg?component";
 import Error from "@icons/error.svg?component";
 import ErrorFill from "@icons/error-fill.svg?component";
 import ExpandCircleDown from "@icons/expand_circle_down.svg?component";
@@ -69,6 +70,7 @@
   chevron_right: ChevronRight,
   delete: Delete,
   edit_square: EditSquare,
+  emergency: Emergency,
   error: Error,
   error_fill: ErrorFill,
   expand_circle_down: ExpandCircleDown,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/agama/src/components/network/InstallationOnlySwitch.test.tsx 
new/agama/src/components/network/InstallationOnlySwitch.test.tsx
--- old/agama/src/components/network/InstallationOnlySwitch.test.tsx    
1970-01-01 01:00:00.000000000 +0100
+++ new/agama/src/components/network/InstallationOnlySwitch.test.tsx    
2025-06-03 17:34:25.000000000 +0200
@@ -0,0 +1,79 @@
+/*
+ * 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 React from "react";
+import { screen } from "@testing-library/react";
+import { plainRender } from "~/test-utils";
+import InstallationOnlySwitch from "./InstallationOnlySwitch";
+import { Connection, ConnectionMethod, ConnectionOptions, ConnectionState } 
from "~/types/network";
+
+const mockKeepMutation = jest.fn();
+const mockConnection = (options: Partial<ConnectionOptions> = {}) =>
+  new Connection("Newtwork 2", {
+    method4: ConnectionMethod.AUTO,
+    method6: ConnectionMethod.AUTO,
+    wireless: {
+      security: "none",
+      ssid: "Network 2",
+      mode: "infrastructure",
+    },
+    state: ConnectionState.activating,
+    ...options,
+  });
+
+jest.mock("~/queries/network", () => ({
+  ...jest.requireActual("~/queries/network"),
+  useConnectionKeepMutation: () => ({
+    mutateAsync: mockKeepMutation,
+  }),
+}));
+
+describe("InstallationOnlySwitch", () => {
+  it("renders the switch with the correct label and description", () => {
+    plainRender(<InstallationOnlySwitch connection={mockConnection()} />);
+    const switchInput = screen.getByRole("switch", { name: "Use for 
installation only" });
+    const switchDescription = screen.getByText(
+      /The connection will be used only during installation/,
+    );
+    expect(switchInput).toHaveAttribute("aria-describedby", 
switchDescription.id);
+  });
+
+  it("renders as checked when connection is transient (`keep` is false)", () 
=> {
+    plainRender(<InstallationOnlySwitch connection={mockConnection({ keep: 
false })} />);
+    const switchInput = screen.getByRole("switch", { name: "Use for 
installation only" });
+    expect(switchInput).toBeChecked();
+  });
+
+  it("renders as not checked when connection is permanent (`keep` is true)", 
() => {
+    plainRender(<InstallationOnlySwitch connection={mockConnection({ keep: 
true })} />);
+    const switchInput = screen.getByRole("switch", { name: "Use for 
installation only" });
+    expect(switchInput).not.toBeChecked();
+  });
+
+  it("triggers mutation for switching between transient and permanent", async 
() => {
+    const connection = mockConnection({ keep: true });
+    const { user } = plainRender(<InstallationOnlySwitch 
connection={connection} />);
+    const switchInput = screen.getByRole("switch", { name: "Use for 
installation only" });
+    await user.click(switchInput);
+    expect(mockKeepMutation).toHaveBeenCalledWith(connection);
+  });
+});
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/agama/src/components/network/InstallationOnlySwitch.tsx 
new/agama/src/components/network/InstallationOnlySwitch.tsx
--- old/agama/src/components/network/InstallationOnlySwitch.tsx 1970-01-01 
01:00:00.000000000 +0100
+++ new/agama/src/components/network/InstallationOnlySwitch.tsx 2025-06-03 
17:34:25.000000000 +0200
@@ -0,0 +1,55 @@
+/*
+ * 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 React from "react";
+import { Connection } from "~/types/network";
+import { SwitchEnhanced } from "~/components/core";
+import { useConnectionKeepMutation } from "~/queries/network";
+import { _ } from "~/i18n";
+
+type InstallationOnlySwitchProps = {
+  /** The connection to configure as installation-only or not */
+  connection: Connection;
+};
+
+/**
+ * A switch for setting a network connection as "installation only".
+ *
+ * Intended to mark connections as transient (used only during
+ * OS installation) or persistent (persisted to the installed system)
+ *
+ */
+export default function InstallationOnlySwitch({ connection }: 
InstallationOnlySwitchProps) {
+  const { mutateAsync: toggleKeep } = useConnectionKeepMutation();
+  const onChange = () => toggleKeep(connection);
+
+  return (
+    <SwitchEnhanced
+      label={_("Use for installation only")}
+      description={_(
+        "The connection will be used only during installation and not 
available in the installed system.",
+      )}
+      onChange={onChange}
+      isChecked={!connection.keep}
+    />
+  );
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/agama/src/components/network/NetworkPage.test.tsx 
new/agama/src/components/network/NetworkPage.test.tsx
--- old/agama/src/components/network/NetworkPage.test.tsx       2025-05-27 
09:03:23.000000000 +0200
+++ new/agama/src/components/network/NetworkPage.test.tsx       2025-06-03 
17:34:25.000000000 +0200
@@ -36,6 +36,10 @@
   <div>WiredConnectionsList Mock</div>
 ));
 
+jest.mock("~/components/network/NoPersistentConnectionsAlert", () => () => (
+  <div>NoPersistentConnectionsAlert Mock</div>
+));
+
 const mockNetworkState = {
   wirelessEnabled: true,
 };
@@ -46,6 +50,11 @@
 }));
 
 describe("NetworkPage", () => {
+  it("mounts alert for all connections status", () => {
+    installerRender(<NetworkPage />);
+    expect(screen.queryByText("NoPersistentConnectionsAlert 
Mock")).toBeInTheDocument();
+  });
+
   it("renders a section for wired connections", () => {
     installerRender(<NetworkPage />);
     expect(screen.queryByText("WiredConnectionsList 
Mock")).toBeInTheDocument();
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/agama/src/components/network/NetworkPage.tsx 
new/agama/src/components/network/NetworkPage.tsx
--- old/agama/src/components/network/NetworkPage.tsx    2025-05-27 
09:03:23.000000000 +0200
+++ new/agama/src/components/network/NetworkPage.tsx    2025-06-03 
17:34:25.000000000 +0200
@@ -23,10 +23,11 @@
 import React from "react";
 import { Content, Grid, GridItem } from "@patternfly/react-core";
 import { EmptyState, Page } from "~/components/core";
-import { _ } from "~/i18n";
 import { useNetworkChanges, useNetworkState } from "~/queries/network";
 import WifiNetworksList from "./WifiNetworksList";
 import WiredConnectionsList from "./WiredConnectionsList";
+import NoPersistentConnectionsAlert from "./NoPersistentConnectionsAlert";
+import { _ } from "~/i18n";
 
 const NoWifiAvailable = () => (
   <Page.Section>
@@ -52,6 +53,8 @@
       </Page.Header>
 
       <Page.Content>
+        <NoPersistentConnectionsAlert />
+
         <Grid hasGutter>
           <GridItem sm={12} xl={6}>
             <Page.Section title={_("Wired connections")}>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/agama/src/components/network/NoPersistentConnectionsAlert.test.tsx 
new/agama/src/components/network/NoPersistentConnectionsAlert.test.tsx
--- old/agama/src/components/network/NoPersistentConnectionsAlert.test.tsx      
1970-01-01 01:00:00.000000000 +0100
+++ new/agama/src/components/network/NoPersistentConnectionsAlert.test.tsx      
2025-06-03 17:34:25.000000000 +0200
@@ -0,0 +1,95 @@
+/*
+ * 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 React from "react";
+import { screen } from "@testing-library/react";
+import { plainRender } from "~/test-utils";
+import { Connection } from "~/types/network";
+import NoPersistentConnectionsAlert from "./NoPersistentConnectionsAlert";
+
+let mockConnections: Connection[];
+
+jest.mock("~/queries/network", () => ({
+  ...jest.requireActual("~/queries/network"),
+  useConnections: () => mockConnections,
+}));
+
+describe("NoPersistentConnectionsAlert", () => {
+  describe("when there are connections to be kept", () => {
+    beforeEach(() => {
+      mockConnections = [
+        new Connection("Newtwork 2", {
+          wireless: {
+            security: "none",
+            ssid: "Network 2",
+            mode: "infrastructure",
+          },
+          keep: true,
+        }),
+        new Connection("Newtwork 3", {
+          wireless: {
+            security: "none",
+            ssid: "Network 2",
+            mode: "infrastructure",
+          },
+          keep: false,
+        }),
+      ];
+    });
+
+    it("renders nothing", () => {
+      const { container } = plainRender(<NoPersistentConnectionsAlert />);
+      expect(container).toBeEmptyDOMElement();
+    });
+  });
+
+  describe("when there are no connections to be kept", () => {
+    beforeEach(() => {
+      mockConnections = [
+        new Connection("Newtwork 2", {
+          wireless: {
+            security: "none",
+            ssid: "Network 2",
+            mode: "infrastructure",
+          },
+          keep: false,
+        }),
+        new Connection("Newtwork 3", {
+          wireless: {
+            security: "none",
+            ssid: "Network 2",
+            mode: "infrastructure",
+          },
+          keep: false,
+        }),
+      ];
+    });
+
+    it("renders a 'full-transient' alert", () => {
+      plainRender(<NoPersistentConnectionsAlert />);
+
+      screen.getByText("Warning alert:");
+      screen.getByText("Installed system may not have network connections");
+      screen.getByText(/All.*managed through this interface.*not be copied.*/);
+    });
+  });
+});
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/agama/src/components/network/NoPersistentConnectionsAlert.tsx 
new/agama/src/components/network/NoPersistentConnectionsAlert.tsx
--- old/agama/src/components/network/NoPersistentConnectionsAlert.tsx   
1970-01-01 01:00:00.000000000 +0100
+++ new/agama/src/components/network/NoPersistentConnectionsAlert.tsx   
2025-06-03 17:34:25.000000000 +0200
@@ -0,0 +1,46 @@
+/*
+ * 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 React from "react";
+import { Alert } from "@patternfly/react-core";
+import { useConnections } from "~/queries/network";
+import { Connection } from "~/types/network";
+import { _ } from "~/i18n";
+/**
+ * Displays a warning alert when no network connections are set to persist in
+ * the installed system.
+ */
+export default function NoPersistentConnectionsAlert() {
+  const connections: Connection[] = useConnections();
+  const persistentConnections: number = connections.filter((c) => 
c.keep).length;
+
+  if (persistentConnections !== 0) return;
+
+  return (
+    <Alert variant="warning" title={_("Installed system may not have network 
connections")}>
+      {_(
+        "All network connections managed through this interface are currently 
set to be \
+        used only during installation and will not be copied to the installed 
system",
+      )}
+    </Alert>
+  );
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/agama/src/components/network/WifiConnectionDetails.test.tsx 
new/agama/src/components/network/WifiConnectionDetails.test.tsx
--- old/agama/src/components/network/WifiConnectionDetails.test.tsx     
1970-01-01 01:00:00.000000000 +0100
+++ new/agama/src/components/network/WifiConnectionDetails.test.tsx     
2025-06-03 17:34:25.000000000 +0200
@@ -0,0 +1,114 @@
+/*
+ * 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 React from "react";
+import { screen, within } from "@testing-library/react";
+import { plainRender } from "~/test-utils";
+import WifiConnectionDetails from "./WifiConnectionDetails";
+import {
+  Connection,
+  ConnectionMethod,
+  ConnectionType,
+  Device,
+  DeviceState,
+  SecurityProtocols,
+  WifiNetworkStatus,
+} from "~/types/network";
+
+jest.mock("~/components/network/InstallationOnlySwitch", () => () => (
+  <div>InstallationOnlySwitch mock</div>
+));
+
+const wlan0: Device = {
+  name: "wlan0",
+  connection: "Network 1",
+  type: ConnectionType.WIFI,
+  state: DeviceState.CONNECTED,
+  addresses: [{ address: "192.168.69.201", prefix: 24 }],
+  nameservers: ["192.168.69.100"],
+  method4: ConnectionMethod.MANUAL,
+  method6: ConnectionMethod.AUTO,
+  gateway4: "192.168.69.4",
+  gateway6: "192.168.69.6",
+  macAddress: "AA:11:22:33:44::FF",
+  routes4: [],
+  routes6: [],
+};
+
+const mockNetwork = {
+  ssid: "Network 1",
+  strength: 25,
+  hwAddress: "??",
+  security: [SecurityProtocols.RSN],
+  device: wlan0,
+  settings: new Connection("Network 1", {
+    iface: "wlan0",
+    addresses: [{ address: "192.168.69.201", prefix: 24 }],
+  }),
+  status: WifiNetworkStatus.CONNECTED,
+};
+
+describe("WifiConnectionDetails", () => {
+  it("renders the device data", () => {
+    plainRender(<WifiConnectionDetails network={mockNetwork} />);
+    const section = screen.getByRole("region", { name: "Device" });
+    within(section).getByText("wlan0");
+    within(section).getByText("connected");
+    within(section).getByText("AA:11:22:33:44::FF");
+  });
+
+  it("renders the network data", () => {
+    plainRender(<WifiConnectionDetails network={mockNetwork} />);
+    const section = screen.getByRole("region", { name: "Network" });
+    within(section).getByText("Network 1");
+    within(section).getByText("25%");
+    within(section).getByText("connected");
+    within(section).getByText("WPA2");
+  });
+
+  it("renders the IP data", () => {
+    plainRender(<WifiConnectionDetails network={mockNetwork} />);
+    const section = screen.getByRole("region", { name: "IP settings" });
+    within(section).getByText("IPv4 auto");
+    within(section).getByText("IPv6 auto");
+    // IP
+    within(section).getByText("192.168.69.201/24");
+    // DNS
+    within(section).getByText("192.168.69.100");
+    // Gateway 4
+    within(section).getByText("192.168.69.4");
+    // Gateway 6
+    within(section).getByText("192.168.69.6");
+  });
+
+  it("renders link for editing connection", () => {
+    plainRender(<WifiConnectionDetails network={mockNetwork} />);
+    const section = screen.getByRole("region", { name: "IP settings" });
+    const editLink = within(section).getByRole("link", { name: "Edit" });
+    expect(editLink).toHaveAttribute("href", "/network/connections/Network 
1/edit");
+  });
+
+  it("renders the switch for making connection available only during 
installation", () => {
+    plainRender(<WifiConnectionDetails network={mockNetwork} />);
+    screen.getByText("InstallationOnlySwitch mock");
+  });
+});
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/agama/src/components/network/WifiConnectionDetails.tsx 
new/agama/src/components/network/WifiConnectionDetails.tsx
--- old/agama/src/components/network/WifiConnectionDetails.tsx  2025-05-27 
09:03:23.000000000 +0200
+++ new/agama/src/components/network/WifiConnectionDetails.tsx  2025-06-03 
17:34:25.000000000 +0200
@@ -34,6 +34,7 @@
   Stack,
 } from "@patternfly/react-core";
 import { Link, Page } from "~/components/core";
+import InstallationOnlySwitch from "./InstallationOnlySwitch";
 import { Device, WifiNetwork } from "~/types/network";
 import { formatIp } from "~/utils/network";
 import { NETWORK } from "~/routes/paths";
@@ -163,7 +164,7 @@
 
   return (
     <Grid hasGutter>
-      <GridItem md={6} order={{ default: "2", md: "1" }}>
+      <GridItem md={6} order={{ default: "2", md: "1" }} rowSpan={3}>
         <IpDetails device={network.device} settings={network.settings} />
       </GridItem>
       <GridItem md={6} order={{ default: "1", md: "2" }}>
@@ -172,6 +173,9 @@
           <NetworkDetails network={network} />
         </Stack>
       </GridItem>
+      <GridItem md={6} order={{ default: "3" }}>
+        <InstallationOnlySwitch connection={network.settings} />
+      </GridItem>
     </Grid>
   );
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/agama/src/components/network/WifiNetworksList.test.tsx 
new/agama/src/components/network/WifiNetworksList.test.tsx
--- old/agama/src/components/network/WifiNetworksList.test.tsx  2025-05-27 
09:03:23.000000000 +0200
+++ new/agama/src/components/network/WifiNetworksList.test.tsx  2025-06-03 
17:34:25.000000000 +0200
@@ -142,6 +142,82 @@
       });
     });
 
+    describe("and the connection is selected to be kept", () => {
+      beforeEach(() => {
+        mockWifiConnections = [
+          new Connection("Newtwork 2", {
+            method4: ConnectionMethod.AUTO,
+            method6: ConnectionMethod.AUTO,
+            wireless: {
+              security: "none",
+              ssid: "Network 2",
+              mode: "infrastructure",
+            },
+            state: ConnectionState.activating,
+            keep: true,
+          }),
+        ];
+
+        mockWifiNetworks = [
+          {
+            ssid: "Network 2",
+            strength: 88,
+            hwAddress: "??",
+            security: [SecurityProtocols.RSN],
+            settings: new Connection("Network 2", {
+              iface: "wlan1",
+              addresses: [{ address: "192.168.69.202", prefix: 24 }],
+            }),
+            status: WifiNetworkStatus.CONFIGURED,
+          },
+        ];
+      });
+
+      it("does not render any hint", () => {
+        // @ts-expect-error: you need to specify the aria-label
+        installerRender(<WifiNetworksList />);
+        expect(screen.queryByText("Configured for installation 
only")).toBeNull;
+      });
+    });
+
+    describe("and the connection is not selected to be kept", () => {
+      beforeEach(() => {
+        mockWifiConnections = [
+          new Connection("Newtwork 2", {
+            method4: ConnectionMethod.AUTO,
+            method6: ConnectionMethod.AUTO,
+            wireless: {
+              security: "none",
+              ssid: "Network 2",
+              mode: "infrastructure",
+            },
+            state: ConnectionState.activating,
+            keep: false,
+          }),
+        ];
+
+        mockWifiNetworks = [
+          {
+            ssid: "Network 2",
+            strength: 88,
+            hwAddress: "??",
+            security: [SecurityProtocols.RSN],
+            settings: new Connection("Network 2", {
+              iface: "wlan1",
+              addresses: [{ address: "192.168.69.202", prefix: 24 }],
+            }),
+            status: WifiNetworkStatus.CONFIGURED,
+          },
+        ];
+      });
+
+      it("renders an installation only hint", () => {
+        // @ts-expect-error: you need to specify the aria-label
+        installerRender(<WifiNetworksList />);
+        screen.getByText("Configured for installation only");
+      });
+    });
+
     describe.skip("and user selects a connected network", () => {
       it("renders basic network information and actions instead of the 
connection form", async () => {
         // @ts-expect-error: you need to specify the aria-label
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/agama/src/components/network/WifiNetworksList.tsx 
new/agama/src/components/network/WifiNetworksList.tsx
--- old/agama/src/components/network/WifiNetworksList.tsx       2025-05-27 
09:03:23.000000000 +0200
+++ new/agama/src/components/network/WifiNetworksList.tsx       2025-06-03 
17:34:25.000000000 +0200
@@ -35,15 +35,15 @@
   Spinner,
 } from "@patternfly/react-core";
 import a11yStyles from 
"@patternfly/react-styles/css/utilities/Accessibility/accessibility";
-import { EmptyState } from "~/components/core";
+import { Annotation, EmptyState } from "~/components/core";
 import Icon, { IconProps } from "~/components/layout/Icon";
 import { Connection, ConnectionState, WifiNetwork, WifiNetworkStatus } from 
"~/types/network";
 import { useConnections, useNetworkChanges, useWifiNetworks } from 
"~/queries/network";
 import { NETWORK as PATHS } from "~/routes/paths";
 import { isEmpty } from "~/utils";
 import { formatIp } from "~/utils/network";
-import { _ } from "~/i18n";
 import { sprintf } from "sprintf-js";
+import { _ } from "~/i18n";
 
 const NetworkSignal = ({ id, signal }) => {
   let label: string;
@@ -141,6 +141,10 @@
                     {network.device?.addresses.map(formatIp).join(", ")}
                   </Content>
                 )}
+
+                {connection && !connection.keep && (
+                  <Annotation>{_("Configured for installation 
only")}</Annotation>
+                )}
               </Flex>
             </DataListCell>,
             <DataListCell key="badges" isFilled={false} alignRight>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/agama/src/components/network/WiredConnectionDetails.test.tsx 
new/agama/src/components/network/WiredConnectionDetails.test.tsx
--- old/agama/src/components/network/WiredConnectionDetails.test.tsx    
1970-01-01 01:00:00.000000000 +0100
+++ new/agama/src/components/network/WiredConnectionDetails.test.tsx    
2025-06-03 17:34:25.000000000 +0200
@@ -0,0 +1,101 @@
+/*
+ * 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 React from "react";
+import { screen, within } from "@testing-library/react";
+import { plainRender } from "~/test-utils";
+import WiredConnectionDetails from "./WiredConnectionDetails";
+import {
+  Connection,
+  ConnectionMethod,
+  ConnectionState,
+  ConnectionType,
+  Device,
+  DeviceState,
+} from "~/types/network";
+
+jest.mock("~/components/network/InstallationOnlySwitch", () => () => (
+  <div>InstallationOnlySwitch mock</div>
+));
+
+const mockDevice: Device = {
+  name: "enp1s0",
+  connection: "Network 1",
+  type: ConnectionType.ETHERNET,
+  state: DeviceState.CONNECTED,
+  addresses: [{ address: "192.168.69.201", prefix: 24 }],
+  nameservers: ["192.168.69.100"],
+  gateway4: "192.168.69.4",
+  gateway6: "192.168.69.6",
+  method4: ConnectionMethod.AUTO,
+  method6: ConnectionMethod.AUTO,
+  macAddress: "AA:11:22:33:44::FF",
+  routes4: [],
+  routes6: [],
+};
+
+const mockConnection: Connection = new Connection("Network 1", {
+  state: ConnectionState.activated,
+  iface: "enp1s0",
+});
+
+jest.mock("~/queries/network", () => ({
+  ...jest.requireActual("~/queries/network"),
+  useNetworkDevices: () => [mockDevice],
+}));
+
+describe("WiredConnectionDetails", () => {
+  it("renders the device data", () => {
+    plainRender(<WiredConnectionDetails connection={mockConnection} />);
+    const section = screen.getByRole("region", { name: "Device" });
+    within(section).getByText("enp1s0");
+    within(section).getByText("connected");
+    within(section).getByText("AA:11:22:33:44::FF");
+  });
+
+  it("renders the IP data", () => {
+    plainRender(<WiredConnectionDetails connection={mockConnection} />);
+    const section = screen.getByRole("region", { name: "IP settings" });
+    within(section).getByText("IPv4 auto");
+    within(section).getByText("IPv6 auto");
+    // IP
+    within(section).getByText("192.168.69.201/24");
+    // DNS
+    within(section).getByText("192.168.69.100");
+    // Gateway 4
+    within(section).getByText("192.168.69.4");
+    // Gateway 6
+    within(section).getByText("192.168.69.6");
+  });
+
+  it("renders link for editing connection", () => {
+    plainRender(<WiredConnectionDetails connection={mockConnection} />);
+    const section = screen.getByRole("region", { name: "IP settings" });
+    const editLink = within(section).getByRole("link", { name: "Edit" });
+    expect(editLink).toHaveAttribute("href", "/network/connections/Network 
1/edit");
+  });
+
+  it("renders the switch for making connection available only during 
installation", () => {
+    plainRender(<WiredConnectionDetails connection={mockConnection} />);
+    screen.getByText("InstallationOnlySwitch mock");
+  });
+});
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/agama/src/components/network/WiredConnectionDetails.tsx 
new/agama/src/components/network/WiredConnectionDetails.tsx
--- old/agama/src/components/network/WiredConnectionDetails.tsx 2025-05-27 
09:03:23.000000000 +0200
+++ new/agama/src/components/network/WiredConnectionDetails.tsx 2025-06-03 
17:34:25.000000000 +0200
@@ -34,6 +34,7 @@
   Stack,
 } from "@patternfly/react-core";
 import { Link, Page } from "~/components/core";
+import InstallationOnlySwitch from "./InstallationOnlySwitch";
 import { Connection, Device } from "~/types/network";
 import { formatIp } from "~/utils/network";
 import { NETWORK } from "~/routes/paths";
@@ -87,7 +88,6 @@
               <FlexItem>
                 {_("IPv6")} {connection.method6}
               </FlexItem>
-              <FlexItem>{device.gateway6}</FlexItem>
             </Flex>
           </DescriptionListDescription>
         </DescriptionListGroup>
@@ -144,7 +144,7 @@
 
   return (
     <Grid hasGutter>
-      <GridItem md={6} order={{ default: "2", md: "1" }}>
+      <GridItem md={6} order={{ default: "2", md: "1" }} rowSpan={3}>
         <IpDetails device={device} connection={connection} />
       </GridItem>
       <GridItem md={6} order={{ default: "1", md: "2" }}>
@@ -152,6 +152,9 @@
           <DeviceDetails device={device} />
         </Stack>
       </GridItem>
+      <GridItem md={6} order={{ default: "3" }}>
+        <InstallationOnlySwitch connection={connection} />
+      </GridItem>
     </Grid>
   );
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/agama/src/components/network/WiredConnectionsList.test.tsx 
new/agama/src/components/network/WiredConnectionsList.test.tsx
--- old/agama/src/components/network/WiredConnectionsList.test.tsx      
1970-01-01 01:00:00.000000000 +0100
+++ new/agama/src/components/network/WiredConnectionsList.test.tsx      
2025-06-03 17:34:25.000000000 +0200
@@ -0,0 +1,98 @@
+/*
+ * 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 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 { screen } from "@testing-library/react";
+import { installerRender } from "~/test-utils";
+import WiredConnectionsList from "~/components/network/WiredConnectionsList";
+import {
+  Connection,
+  ConnectionMethod,
+  ConnectionState,
+  ConnectionType,
+  Device,
+  DeviceState,
+} from "~/types/network";
+
+const mockDevice: Device = {
+  name: "enp1s0",
+  connection: "Network 1",
+  type: ConnectionType.ETHERNET,
+  state: DeviceState.CONNECTED,
+  addresses: [{ address: "192.168.69.201", prefix: 24 }],
+  nameservers: ["192.168.69.100"],
+  gateway4: "192.168.69.4",
+  gateway6: "192.168.69.6",
+  method4: ConnectionMethod.AUTO,
+  method6: ConnectionMethod.AUTO,
+  macAddress: "AA:11:22:33:44::FF",
+  routes4: [],
+  routes6: [],
+};
+
+let mockConnections: Connection[];
+
+jest.mock("~/queries/network", () => ({
+  ...jest.requireActual("~/queries/network"),
+  useNetworkChanges: jest.fn(),
+  useNetworkDevices: () => [mockDevice],
+  useConnections: () => mockConnections,
+}));
+
+describe("WiredConnectionsList", () => {
+  describe("and the connection is selected to be kept", () => {
+    beforeEach(() => {
+      mockConnections = [
+        new Connection("Newtwork 1", {
+          method4: ConnectionMethod.AUTO,
+          method6: ConnectionMethod.AUTO,
+          state: ConnectionState.activating,
+          keep: true,
+        }),
+      ];
+    });
+
+    it("does not render any hint", () => {
+      // @ts-expect-error: you need to specify the aria-label
+      installerRender(<WiredConnectionsList />);
+      expect(screen.queryByText("Configured for installation only")).toBeNull;
+    });
+  });
+
+  describe("and the connection is not selected to be kept", () => {
+    beforeEach(() => {
+      mockConnections = [
+        new Connection("Newtwork 1", {
+          method4: ConnectionMethod.AUTO,
+          method6: ConnectionMethod.AUTO,
+          state: ConnectionState.activating,
+          keep: false,
+        }),
+      ];
+    });
+
+    it("renders an installation only hint", () => {
+      // @ts-expect-error: you need to specify the aria-label
+      installerRender(<WiredConnectionsList />);
+      screen.getByText("Configured for installation only");
+    });
+  });
+});
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/agama/src/components/network/WiredConnectionsList.tsx 
new/agama/src/components/network/WiredConnectionsList.tsx
--- old/agama/src/components/network/WiredConnectionsList.tsx   2025-05-27 
09:03:23.000000000 +0200
+++ new/agama/src/components/network/WiredConnectionsList.tsx   2025-06-03 
17:34:25.000000000 +0200
@@ -33,7 +33,7 @@
   Flex,
 } from "@patternfly/react-core";
 import a11yStyles from 
"@patternfly/react-styles/css/utilities/Accessibility/accessibility";
-import { EmptyState } from "~/components/core";
+import { Annotation, EmptyState } from "~/components/core";
 import { Connection } from "~/types/network";
 import { useConnections, useNetworkDevices } from "~/queries/network";
 import { NETWORK as PATHS } from "~/routes/paths";
@@ -66,6 +66,9 @@
                   <Content className={a11yStyles.screenReader}>{_("IP 
addresses")}</Content>
                   {addresses.map(formatIp).join(", ")}
                 </Content>
+                {!connection.keep && (
+                  <Annotation>{_("Configured for installation 
only")}</Annotation>
+                )}
               </Flex>
             </DataListCell>,
           ]}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/agama/src/queries/network.ts 
new/agama/src/queries/network.ts
--- old/agama/src/queries/network.ts    2025-05-27 09:03:23.000000000 +0200
+++ new/agama/src/queries/network.ts    2025-06-03 17:34:25.000000000 +0200
@@ -42,6 +42,8 @@
   fetchConnections,
   fetchDevices,
   fetchState,
+  keep,
+  unkeep,
   updateConnection,
 } from "~/api/network";
 
@@ -123,6 +125,7 @@
   };
   return useMutation(query);
 };
+
 /**
  * Hook that builds a mutation to update a network connection
  *
@@ -142,6 +145,54 @@
 };
 
 /**
+ * Hook that provides a mutation for toggling the "keep" state of a network
+ * connection.
+ *
+ * This hook uses optimistic updates to immediately reflect the change in the 
UI
+ * before the mutation completes. If the mutation fails, it will rollback to 
the
+ * previous state.
+ */
+const useConnectionKeepMutation = () => {
+  const queryClient = useQueryClient();
+  const query = {
+    mutationFn: (connection: Connection) => {
+      const method = connection.keep ? unkeep : keep;
+      return method(connection.id);
+    },
+    onMutate: async (connection: Connection) => {
+      // Get the current list of cached connections
+      const previousConnections: Connection[] = queryClient.getQueryData([
+        "network",
+        "connections",
+      ]);
+
+      // Optimistically toggle the 'keep' status of the matching connection
+      const updatedConnections = previousConnections.map((cachedConnection) => 
{
+        if (connection.id !== cachedConnection.id) return cachedConnection;
+
+        const { id, ...nextConnection } = cachedConnection;
+        return new Connection(id, { ...nextConnection, keep: 
!cachedConnection.keep });
+      });
+
+      // Update the cached data with the optimistically updated connections
+      queryClient.setQueryData(["network", "connections"], updatedConnections);
+
+      // Return the previous state for potential rollback
+      return { previousConnections };
+    },
+
+    /**
+     * Called if the mutation fails for whatever reason. Rolls back the cache 
to
+     * the previous state.
+     */
+    onError: (_, connection: Connection, context: { previousConnections: 
Connection[] }) => {
+      queryClient.setQueryData(["network", "connections"], 
context.previousConnections);
+    },
+  };
+
+  return useMutation(query);
+};
+/**
  * Hook that builds a mutation to remove a network connection
  *
  * It does not require to call `useMutation`.
@@ -316,6 +367,7 @@
   useAddConnectionMutation,
   useConnections,
   useConnectionMutation,
+  useConnectionKeepMutation,
   useRemoveConnectionMutation,
   useConnection,
   useNetworkDevices,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/agama/src/types/network.ts 
new/agama/src/types/network.ts
--- old/agama/src/types/network.ts      2025-05-27 09:03:23.000000000 +0200
+++ new/agama/src/types/network.ts      2025-06-03 17:34:25.000000000 +0200
@@ -20,7 +20,7 @@
  * find current contact information at www.suse.com.
  */
 
-import { isObject } from "~/utils";
+import { isEmpty, isObject } from "~/utils";
 import {
   buildAddress,
   buildAddresses,
@@ -232,6 +232,7 @@
   wireless?: Wireless;
   status: ConnectionStatus;
   state: ConnectionState;
+  keep: boolean;
 };
 
 type WirelessOptions = {
@@ -268,6 +269,7 @@
   method6?: ConnectionMethod;
   wireless?: Wireless;
   state?: ConnectionState;
+  keep?: boolean;
 };
 
 class Connection {
@@ -282,6 +284,7 @@
   method4: ConnectionMethod = ConnectionMethod.AUTO;
   method6: ConnectionMethod = ConnectionMethod.AUTO;
   wireless?: Wireless;
+  keep: boolean;
 
   constructor(id: string, options?: ConnectionOptions) {
     this.id = id;
@@ -289,7 +292,7 @@
     if (!isObject(options)) return;
 
     for (const [key, value] of Object.entries(options)) {
-      if (value) this[key] = value;
+      if (!isEmpty(value)) this[key] = value;
     }
   }
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/agama/src/utils.test.ts new/agama/src/utils.test.ts
--- old/agama/src/utils.test.ts 2025-05-27 09:03:23.000000000 +0200
+++ new/agama/src/utils.test.ts 2025-06-03 17:34:25.000000000 +0200
@@ -178,6 +178,14 @@
     expect(isEmpty(() => {})).toBe(false);
   });
 
+  it("returns false when called with `true` (boolean)", () => {
+    expect(isEmpty(true)).toBe(false);
+  });
+
+  it("returns false when called with `false` (boolean)", () => {
+    expect(isEmpty(false)).toBe(false);
+  });
+
   it("returns false when called with a number", () => {
     expect(isEmpty(1)).toBe(false);
   });
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/agama/src/utils.ts new/agama/src/utils.ts
--- old/agama/src/utils.ts      2025-05-27 09:03:23.000000000 +0200
+++ new/agama/src/utils.ts      2025-06-03 17:34:25.000000000 +0200
@@ -62,6 +62,10 @@
     return true;
   }
 
+  if (typeof value === "boolean") {
+    return false;
+  }
+
   if (typeof value === "function") {
     return false;
   }

++++++ agama.obsinfo ++++++
--- /var/tmp/diff_new_pack.fSdpeV/_old  2025-06-03 19:11:43.520375523 +0200
+++ /var/tmp/diff_new_pack.fSdpeV/_new  2025-06-03 19:11:43.524375688 +0200
@@ -1,5 +1,5 @@
 name: agama
-version: 15+4.dc6580496
-mtime: 1748329403
-commit: dc6580496dea8904e55a8525ade52ac27f60cab1
+version: 15+54.8e10affb2
+mtime: 1748964865
+commit: 8e10affb228ff198651e5338d8ad91b481a86be0
 

Reply via email to