LCOV - code coverage report
Current view: top level - lcov - github-pr.diff Coverage Total Hit
Test: cockpit Lines: 63.5 % 189 120
Test Date: 2026-08-04 16:34:20

            Line data    Source code
       1              : diff --git a/pkg/lib/anaconda/_anaconda.scss b/pkg/lib/anaconda/_anaconda.scss
       2              : index 06b99354f..0e4bb8768 100644
       3              : --- a/pkg/lib/anaconda/_anaconda.scss
       4              : +++ b/pkg/lib/anaconda/_anaconda.scss
       5              : @@ -30,9 +30,9 @@
       6              :      --pf-v6-c-page--BackgroundColor: var(--pf-t--global--background--color--primary--default);
       7              :    }
       8              :  
       9              : -  .pf-v6-c-card {
      10              : -    --pf-v6-c-card--BorderColor: transparent;
      11              : -  }
      12              : +  // .pf-v6-c-card {
      13              : +    // --pf-v6-c-card--BorderColor: transparent;
      14              : +  // }
      15              :  
      16              :    // Approximates PageSection padding={{ default: "noPadding" }} isFilled={false} (PF .pf-m-no-padding / .pf-m-no-fill)
      17              :    .pf-v6-c-page__main-section {
      18              : diff --git a/pkg/networkmanager/anaconda-main.css b/pkg/networkmanager/anaconda-main.css
      19              : new file mode 100644
      20              : index 000000000..b32fa5fa5
      21              : --- /dev/null
      22              : +++ b/pkg/networkmanager/anaconda-main.css
      23              : @@ -0,0 +1,5 @@
      24              : +#network-interface {
      25              : +  .pf-v6-c-card.pf-m-plain .pf-v6-c-card__header, .pf-v6-c-card.pf-m-plain > .pf-v6-c-card__title {
      26              : +    padding-block-start: 0;
      27              : +  }
      28              : +}
      29              : diff --git a/pkg/networkmanager/anaconda-main.tsx b/pkg/networkmanager/anaconda-main.tsx
      30              : new file mode 100644
      31              : index 000000000..e7da8c3a8
      32              : --- /dev/null
      33              : +++ b/pkg/networkmanager/anaconda-main.tsx
      34              : @@ -0,0 +1,182 @@
      35              : +/*
      36              : + * Copyright (C) 2021 Red Hat, Inc.
      37              : + * SPDX-License-Identifier: LGPL-2.1-or-later
      38              : + */
      39              : +
      40           37 : +import cockpit from "cockpit";
      41           37 : +import React, { useState } from 'react';
      42              : +
      43              : +import { Flex, FlexItem } from "@patternfly/react-core/dist/esm/layouts/Flex/index.js";
      44              : +import { Page, } from "@patternfly/react-core/dist/esm/components/Page/index.js";
      45              : +
      46              : +import { ListingTableRowProps } from "cockpit-components-table.jsx";
      47              : +
      48              : +import {
      49              : +    has_group,
      50              : +    is_loopback,
      51              : +    is_managed,
      52              : +    is_wireless,
      53              : +    render_active_connection,
      54              : +} from './interfaces.js';
      55              : +import { Content, ContentVariants, SimpleList, SimpleListGroup, SimpleListItem, Split, SplitItem } from "@patternfly/react-core";
      56              : +import { NetworkInterfacePage } from "./network-interface.jsx";
      57              : +import "./anaconda-main.css";
      58              : +
      59           37 : +const _ = cockpit.gettext;
      60              : +
      61              : +interface AnacondaNetworkPageProps {
      62              : +    privileged: boolean;
      63              : +    operationInProgress: boolean;
      64              : +    usage_monitor: any;
      65              : +    interfaces: any[];
      66              : +    iface?: any;
      67              : +}
      68              : +
      69              : +interface AnacondaActiveNetwork {
      70              : +    isWireless?: boolean;
      71              : +    iface: any;
      72              : +}
      73              : +
      74            0 : +export const AnacondaNetworkPage = ({ privileged, operationInProgress, usage_monitor, interfaces }: AnacondaNetworkPageProps) => {
      75            0 : +    const [active, setActive] = useState<AnacondaActiveNetwork>();
      76              : +    // useEvent(usage_monitor.grid, "notify");
      77              : +
      78            0 : +    const managedWired: ListingTableRowProps[] = [];
      79            0 : +    const managedWireless: ListingTableRowProps[] = [];
      80            0 : +    let hasDetails = false;
      81              : +
      82            0 : +    interfaces.forEach(iface => {
      83              : +        // Skip loopback
      84            0 : +        if (is_loopback(iface))
      85            0 : +            return;
      86              : +
      87              : +        // Skip members
      88            0 : +        else if (has_group(iface))
      89            0 : +            return;
      90              : +
      91            0 : +        const dev = iface.Device;
      92              : +        // const show_traffic = (dev && (dev.State == 100 || dev.State == 10) && dev.Carrier === true);
      93              : +
      94              : +        // usage_monitor.add(iface.Name);
      95              : +
      96            0 : +        const activeConnection = render_active_connection(dev, false, true);
      97            0 : +        const isWireless = is_wireless(iface);
      98              : +
      99            0 : +        let connectionStatus;
     100            0 : +        if (activeConnection) {
     101            0 : +            connectionStatus = _("Connected")
     102            0 : +        } else {
     103            0 : +            connectionStatus = _("Disconnected")
     104            0 : +        }
     105              : +
     106            0 : +        const row = (
     107            0 : +            <SimpleListItem key={iface.name} onClick={() => {setActive({isWireless, iface})}}>
     108            0 : +                <Flex
     109            0 : +                    direction={{ default: 'row' }}
     110            0 : +                    justifyContent={{ default: 'justifyContentSpaceBetween' }}
     111            0 : +                    flexWrap={{ default: 'nowrap' }}
     112              : +                >
     113            0 : +                    <FlexItem flex={{ default: 'flex_1' }}>{iface.Name}</FlexItem>
     114            0 : +                    <FlexItem>
     115            0 : +                        <Content component={ContentVariants.small}>{connectionStatus}</Content>
     116            0 : +                    </FlexItem>
     117            0 : +                </Flex>
     118            0 : +            </SimpleListItem>
     119              : +        )
     120              : +
     121              : +        // Details column: show type-specific information
     122              : +        // let detailsColumn = null;
     123              : +        // if (dev?.DeviceType === '802-11-wireless') {
     124              : +        //     const networkCount = dev.visibleSsids.length;
     125              : +        //     if (networkCount > 0 || dev.ActiveAccessPoint?.Ssid) {
     126              : +        //         hasDetails = true;
     127              : +        //         detailsColumn = (
     128              : +        //             <Flex columnGap={{ default: 'columnGapSm' }}>
     129              : +        //                 {networkCount > 0 && (
     130              : +        //                     <FlexItem>
     131              : +        //                         <Label status="info">
     132              : +        //                             {cockpit.format(cockpit.ngettext("$0 network", "$0 networks", networkCount), networkCount)}
     133              : +        //                         </Label>
     134              : +        //                     </FlexItem>
     135              : +        //                 )}
     136              : +        //                 {dev.ActiveAccessPoint?.Ssid && (
     137              : +        //                     <FlexItem>
     138              : +        //                         <Label status="success" icon={<ConnectedIcon />}>{dev.ActiveAccessPoint?.Ssid}</Label>
     139              : +        //                     </FlexItem>
     140              : +        //                 )}
     141              : +        //             </Flex>
     142              : +        //         );
     143              : +        //     }
     144              : +        // }
     145              : +        // row.columns.push({ title: detailsColumn });
     146              : +
     147            0 : +        if (!dev || is_managed(dev)) {
     148            0 : +            isWireless ? managedWireless.push(row) : managedWired.push(row);
     149            0 : +        }
     150            0 : +    });
     151              : +
     152              : +    // TODO: Actions: turn on off action and edit (wired) or join wifi (wireless)
     153            0 : +    const actions = privileged && (
     154            0 : +        <>
     155              : +            {/* <NetworkAction buttonText={_("Add VPN")} type='wg' />
     156              : +            <NetworkAction buttonText={_("Add bond")} type='bond' />
     157              : +            <NetworkAction buttonText={_("Add team")} type='team' />
     158              : +            <NetworkAction buttonText={_("Add bridge")} type='bridge' />
     159              : +            <NetworkAction buttonText={_("Add VLAN")} type='vlan' /> */}
     160            0 : +        </>
     161              : +    );
     162              : +
     163            0 : +    return (
     164            0 : +        <Page data-test-wait={operationInProgress} id="networking" className="pf-m-no-sidebar anaconda">
     165            0 : +            <Content component="h1">{_("Networks")}</Content>
     166            0 : +            <Split hasGutter>
     167            0 : +                <SplitItem>
     168            0 : +                    <SimpleList>
     169            0 : +                        {managedWireless.length !== 0 && (
     170            0 : +                            <SimpleListGroup title={_("Wireless")} id="wireless-connections">{...managedWireless}</SimpleListGroup>
     171              : +                        )}
     172            0 : +                        {managedWired.length !== 0 && (
     173            0 : +                            <SimpleListGroup title={_("Wired")} id="wired-connections">{...managedWired}</SimpleListGroup>
     174              : +                        )}
     175            0 : +                        {(managedWireless.length === 0 && managedWired.length === 0) && (
     176            0 : +                            <SimpleListItem key="not-found">{_("No networks found")}</SimpleListItem>
     177              : +                        )}
     178            0 : +                    </SimpleList>
     179            0 : +                </SplitItem>
     180            0 : +                <SplitItem isFilled>
     181            0 : +                    {active?.iface &&
     182            0 : +                        <NetworkInterfacePage
     183            0 : +                            privileged={privileged}
     184            0 : +                            operationInProgress={operationInProgress}
     185            0 : +                            usage_monitor={usage_monitor}
     186            0 : +                            plot_state={undefined}
     187            0 : +                            interfaces={interfaces}
     188            0 : +                            iface={active.iface} />
     189              : +                    }
     190            0 : +                </SplitItem>
     191            0 : +            </Split>
     192              : +
     193            0 : +        </Page>
     194              : +    );
     195            0 : +};
     196              : +
     197              : +export const AnacondaWirelessDetail = ({active}: {active: AnacondaActiveNetwork}) => {
     198              : +    return <>
     199              : +        <Content component="h2">{_("Wireless")}</Content>
     200              : +        Interface
     201              : +        {active.iface.Name}
     202              : +        Status
     203              : +        Network joined
     204              : +        Security type
     205              : +    </>
     206              : +}
     207              : +
     208              : +export const AnacondaWiredDetail = ({active}: {active: AnacondaActiveNetwork}) => {
     209              : +    return <>
     210              : +        <Content component="h2">{_("Wired")}</Content>
     211              : +        Interface
     212              : +        {active.iface.Name}
     213              : +        Status
     214              : +        IP Settings
     215              : +    </>
     216              : +}
     217              : diff --git a/pkg/networkmanager/interfaces.js b/pkg/networkmanager/interfaces.js
     218              : index 73f641f41..72045084d 100644
     219              : --- a/pkg/networkmanager/interfaces.js
     220              : +++ b/pkg/networkmanager/interfaces.js
     221              : @@ -2105,6 +2105,24 @@ export function apply_group_member(choices, model, apply_group, group_connection
     222              :      });
     223              :  }
     224              :  
     225           36 : +export function is_loopback(iface) {
     226           36 : +    return iface.Name == "lo" || (iface.Device && iface.Device.DeviceType == "loopback");
     227           36 : +}
     228              : +
     229           36 : +export function has_group(iface) {
     230           36 : +    return (
     231           36 : +        (iface.Device &&
     232           36 : +            iface.Device.ActiveConnection &&
     233           36 : +            iface.Device.ActiveConnection.Group &&
     234            7 : +            iface.Device.ActiveConnection.Group.Members.length > 0) ||
     235           36 : +        (iface.MainConnection && iface.MainConnection.Groups.length > 0)
     236              : +    );
     237           36 : +}
     238              : +
     239            0 : +export function is_wireless(iface) {
     240            0 : +    return iface.Device?.DeviceType === '802-11-wireless';
     241            0 : +}
     242              : +
     243              :  export function init() {
     244              :      cockpit.translate();
     245              :  }
     246              : diff --git a/pkg/networkmanager/network-interface.jsx b/pkg/networkmanager/network-interface.jsx
     247              : index 43aa0dcb1..b241c35d7 100644
     248              : --- a/pkg/networkmanager/network-interface.jsx
     249              : +++ b/pkg/networkmanager/network-interface.jsx
     250              : @@ -21,7 +21,7 @@ import { SearchInput } from "@patternfly/react-core/dist/esm/components/SearchIn
     251              :  import { Spinner } from "@patternfly/react-core/dist/esm/components/Spinner/index.js";
     252              :  import { Switch } from "@patternfly/react-core/dist/esm/components/Switch/index.js";
     253              :  import { Tooltip } from "@patternfly/react-core/dist/esm/components/Tooltip/index.js";
     254              : -import { SortByDirection } from '@patternfly/react-table';
     255              : +import { ActionsColumn, SortByDirection } from '@patternfly/react-table';
     256              :  import {
     257              :      ConnectedIcon,
     258              :      DisconnectedIcon,
     259              : @@ -225,6 +225,8 @@ export const NetworkInterfacePage = ({
     260              :      const [prevAPCount, setPrevAPCount] = useState(0);
     261              :      const [networkSearch, setNetworkSearch] = useState("");
     262              :  
     263           31 : +    const anaconda = in_anaconda_mode();
     264              : +
     265              :      const dev_name = iface.Name;
     266              :      const dev = iface.Device;
     267              :      const isManaged = iface && (!dev || is_managed(dev));
     268              : @@ -432,7 +434,14 @@ export const NetworkInterfacePage = ({
     269              :              mac_desc = mac;
     270              :          }
     271              :  
     272              : -        return mac_desc;
     273           31 : +        return (
     274           31 : +            <DescriptionListGroup id="network-interface-mac">
     275           31 : +                <DescriptionListTerm>{_("MAC")}</DescriptionListTerm>
     276           31 : +                <DescriptionListDescription data-label="Carrier">
     277           31 : +                    { mac_desc }
     278           31 : +                </DescriptionListDescription>
     279           31 : +            </DescriptionListGroup>
     280              : +        );
     281              :      }
     282              :  
     283              :      function renderCarrierStatusRow() {
     284              : @@ -980,47 +989,49 @@ export const NetworkInterfacePage = ({
     285              :                  );
     286              :              } else {
     287              :                  actionColumn = (
     288              : -                    <>
     289              : -                        <Privileged allowed={privileged}
     290              : -                                    tooltipId={"wifi-connect-" + index}
     291              : -                                    excuse={_("Not permitted to connect to network")}>
     292              : -                            <Button variant="secondary"
     293              : -                                    size="sm"
     294              : -                                    icon={<ConnectedIcon />}
     295              : -                                    isDisabled={!privileged}
     296              : -                                    onClick={() => connectToAP(ap)}
     297              : -                                    aria-label={_("Connect")}>
     298              : -                                {_("Connect")}
     299              : -                            </Button>
     300              : -                        </Privileged>
     301              : -                        {ap.Connection && (
     302              : -                            <>
     303              : -                                {" "}
     304              : -                                <KebabDropdown
     305              : -                                    toggleButtonId={"wifi-kebab-" + index}
     306              : -                                    isDisabled={!privileged}
     307              : -                                    dropdownItems={[
     308              : -                                        <DropdownItem key="forget"
     309              : -                                                      className="pf-m-danger"
     310              : -                                                      onClick={() => forgetNetwork(ap)}
     311              : -                                                      aria-label={_("Forget")}>
     312              : -                                            {_("Forget")}
     313              : -                                        </DropdownItem>
     314              : -                                    ]} />
     315              : -                            </>
     316              : -                        )}
     317              : -                    </>
     318            2 : +                    <Privileged allowed={privileged}
     319            2 : +                                tooltipId={"wifi-connect-" + index}
     320            2 : +                                excuse={_("Not permitted to connect to network")}>
     321            2 : +                        <Button variant="secondary"
     322            2 : +                                size="sm"
     323            2 : +                                icon={<ConnectedIcon />}
     324            2 : +                                isDisabled={!privileged}
     325            1 : +                                onClick={() => connectToAP(ap)}
     326            2 : +                                aria-label={_("Connect")}>
     327            2 : +                            {_("Connect")}
     328            2 : +                        </Button>
     329            2 : +                    </Privileged>
     330              :                  );
     331              :              }
     332            2 : +            let rowActions = <></>;
     333            2 : +            if (ap.Connection) {
     334            2 : +                rowActions = <ActionsColumn
     335            2 : +                    items={[
     336            2 : +                        {
     337            2 : +                            title: _("Forget"),
     338            2 : +                            onClick: () => forgetNetwork(ap),
     339            2 : +                            isDanger: true,
     340            2 : +                            "aria-label": _("Forget")
     341            2 : +                        }
     342            2 : +                    ]}
     343            2 : +                    isDisabled={!privileged}
     344            2 : +                />
     345            2 : +            }
     346              : +
     347            2 : +            const networkColumns = [
     348            2 : +                { title: nameColumn, sortKey: ap.Ssid, header: true },
     349            2 : +                { title: <>{securityIcon} {ap.Mode}</>, sortKey: ap.Mode },
     350            2 : +                { title: signalColumn, sortKey: String(ap.Strength).padStart(3, '0') },
     351            2 : +            ];
     352            2 : +            if (!anaconda) {
     353            2 : +                networkColumns.push({ title: cockpit.format_bits_per_sec(ap.MaxBitrate * 1000) });
     354            2 : +            }
     355            2 : +            networkColumns.push({ title: <Flex justifyContent={{ default: 'justifyContentFlexEnd' }}><FlexItem>{actionColumn}</FlexItem></Flex>, props: { hasAction: true } });
     356            2 : +            networkColumns.push({ title: rowActions, props: { isActionCell: true } });
     357              : +
     358              :  
     359              :              return {
     360              : -                columns: [
     361              : -                    { title: nameColumn, sortKey: ap.Ssid, header: true },
     362              : -                    { title: <>{securityIcon} {ap.Mode}</>, sortKey: ap.Mode },
     363              : -                    { title: signalColumn, sortKey: String(ap.Strength).padStart(3, '0') },
     364              : -                    { title: cockpit.format_bits_per_sec(ap.MaxBitrate * 1000) },
     365              : -                    { title: actionColumn },
     366              : -                ],
     367            2 : +                columns: networkColumns,
     368              :                  props: { key: ap.HwAddress, "data-ssid": ap.Ssid, "data-known": !!ap.Connection }
     369              :              };
     370              :          });
     371              : @@ -1028,18 +1039,38 @@ export const NetworkInterfacePage = ({
     372              :          // Add aggregated hidden access points row at the bottom
     373              :          if (dev.hiddenAPCount > 0) {
     374              :              const hiddenLabel = cockpit.ngettext("$0 hidden network", "$0 hidden networks", dev.hiddenAPCount);
     375              : +
     376            5 : +            const networkHiddenColumns = [
     377            5 : +                { title: cockpit.format(hiddenLabel, dev.hiddenAPCount), sortKey: "zzz-hidden", header: true },
     378            5 : +                { title: "" },
     379            5 : +                { title: "" },
     380            5 : +                { title: "" },
     381            5 : +                { title: "" },
     382            5 : +            ];
     383              : +
     384            5 : +            if (!anaconda) {
     385            5 : +                networkHiddenColumns.push({ title: "" });
     386            5 : +            }
     387            5 : +            networkHiddenColumns.push({ title: "" });
     388              : +
     389              : +
     390              :              rows.push({
     391              : -                columns: [
     392              : -                    { title: cockpit.format(hiddenLabel, dev.hiddenAPCount), sortKey: "zzz-hidden", header: true },
     393              : -                    { title: "" },
     394              : -                    { title: "" },
     395              : -                    { title: "" },
     396              : -                    { title: "" },
     397              : -                ],
     398            5 : +                columns: networkHiddenColumns,
     399              :                  props: { key: "hidden-networks", "data-hidden": true }
     400              :              });
     401              :          }
     402              :  
     403            5 : +        const listingColumns = [
     404            5 : +            { title: _("Network"), header: true, sortable: true },
     405            5 : +            { title: _("Mode") },
     406            5 : +            { title: _("Signal"), sortable: true },
     407            5 : +        ];
     408              : +
     409            5 : +        if (!anaconda) {
     410            5 : +            listingColumns.push({ title: _("Rate") });
     411            5 : +        }
     412            5 : +        listingColumns.push({ title: "", props: { screenReaderText: _("Actions") } });
     413              : +
     414              :          return (
     415              :              <Card isPlain id="network-interface-wifi-networks">
     416              :                  <CardHeader actions={{
     417              : @@ -1077,13 +1108,7 @@ export const NetworkInterfacePage = ({
     418              :                  </CardHeader>
     419              :                  <ListingTable aria-label={_("Available networks")}
     420              :                                variant='compact'
     421              : -                              columns={[
     422              : -                                  { title: _("Network"), header: true, sortable: true },
     423              : -                                  { title: _("Mode") },
     424              : -                                  { title: _("Signal"), sortable: true },
     425              : -                                  { title: _("Rate") },
     426              : -                                  { title: "", props: { screenReaderText: _("Actions") } },
     427              : -                              ]}
     428           31 : +                              columns={listingColumns}
     429              :                                sortBy={{ index: 2, direction: SortByDirection.asc }}
     430              :                                sortMethod={networkSort}
     431              :                                rows={rows} />
     432              : @@ -1112,7 +1137,7 @@ export const NetworkInterfacePage = ({
     433              :          };
     434              :  
     435              :          const cs = con && connection_settings(con);
     436              : -        if (!con || (cs.type != "bond" && cs.type != "team" && cs.type != "bridge")) {
     437           22 : +        if (plot_state && (!con || (cs.type != "bond" && cs.type != "team" && cs.type != "bridge"))) {
     438              :              plot_state.plot_instances('rx', rx_plot_data, [dev_name], true);
     439              :              plot_state.plot_instances('tx', tx_plot_data, [dev_name], true);
     440              :              return null;
     441              : @@ -1120,7 +1145,7 @@ export const NetworkInterfacePage = ({
     442              :  
     443              :          const plot_ifaces = [];
     444              :  
     445              : -        con.Members.forEach(member_con => {
     446           13 : +        con && con.Members.forEach(member_con => {
     447              :              member_con.Interfaces.forEach(iface => {
     448              :                  if (iface.MainConnection != member_con)
     449              :                      return;
     450              : @@ -1140,8 +1165,10 @@ export const NetworkInterfacePage = ({
     451              :              });
     452              :          });
     453              :  
     454              : -        plot_state.plot_instances('rx', rx_plot_data, plot_ifaces, true);
     455              : -        plot_state.plot_instances('tx', tx_plot_data, plot_ifaces, true);
     456           16 : +        if (plot_state) {
     457           16 : +            plot_state.plot_instances('rx', rx_plot_data, plot_ifaces, true);
     458           16 : +            plot_state.plot_instances('tx', tx_plot_data, plot_ifaces, true);
     459           16 : +        }
     460              :  
     461              :          const sorted_members = Object.keys(members).sort()
     462              :                  .map(name => members[name]);
     463              : @@ -1208,12 +1235,11 @@ export const NetworkInterfacePage = ({
     464              :      const settingsRows = renderConnectionSettingsRows(iface.MainConnection, connectionSettings)
     465              :              .map((component, idx) => <React.Fragment key={idx}>{component}</React.Fragment>);
     466              :  
     467              : -    const anaconda = in_anaconda_mode();
     468              : -
     469              :      return (
     470              :          <Page id="network-interface"
     471              :                data-test-wait={operationInProgress}
     472              :                className={"pf-m-no-sidebar" + (anaconda ? " anaconda" : "")}>
     473           31 : +            { !anaconda ? (
     474              :              <PageBreadcrumb hasBodyWrapper={false} stickyOnBreakpoint={{ default: "top" }}>
     475              :                  <Breadcrumb>
     476              :                      <BreadcrumbItem to='#/'>
     477              : @@ -1224,9 +1250,12 @@ export const NetworkInterfacePage = ({
     478              :                      </BreadcrumbItem>
     479              :                  </Breadcrumb>
     480              :              </PageBreadcrumb>
     481              : -            <PageSection hasBodyWrapper={false}>
     482              : -                <NetworkPlots plot_state={plot_state} />
     483              : -            </PageSection>
     484            4 : +            ) : <></> }
     485           31 : +            { plot_state &&
     486           31 : +                <PageSection hasBodyWrapper={false}>
     487           31 : +                    <NetworkPlots plot_state={plot_state} />
     488           31 : +                </PageSection>
     489              : +            }
     490              :              <PageSection hasBodyWrapper={false}>
     491              :                  <Gallery hasGutter>
     492              :                      <Card isPlain className="network-interface-details">
     493              : @@ -1246,12 +1275,12 @@ export const NetworkInterfacePage = ({
     494              :                              <CardTitle className="network-interface-details-title">
     495              :                                  <span id="network-interface-name">{dev_name}</span>
     496              :                                  <span id="network-interface-hw">{renderDesc()}</span>
     497              : -                                <span id="network-interface-mac">{renderMac()}</span>
     498              :                              </CardTitle>
     499              :                          </CardHeader>
     500              :                          <CardBody>
     501              :                              <DescriptionList id="network-interface-settings" className="network-interface-settings pf-m-horizontal-on-sm">
     502              :                                  {renderActiveStatusRow()}
     503           31 : +                                {renderMac()}
     504              :                                  {renderCarrierStatusRow()}
     505              :                                  {settingsRows}
     506              :                              </DescriptionList>
     507              : @@ -1264,7 +1293,7 @@ export const NetworkInterfacePage = ({
     508              :                          }
     509              :                      </Card>
     510              :                      {renderWiFiNetworks()}
     511              : -                    {renderConnectionMembers(iface.MainConnection)}
     512           31 : +                    { !anaconda && renderConnectionMembers(iface.MainConnection)}
     513              :                  </Gallery>
     514              :              </PageSection>
     515              :          </Page>
     516              : diff --git a/pkg/networkmanager/network-main.jsx b/pkg/networkmanager/network-main.jsx
     517              : index d2870f684..1b75ec3f4 100644
     518              : --- a/pkg/networkmanager/network-main.jsx
     519              : +++ b/pkg/networkmanager/network-main.jsx
     520              : @@ -25,6 +25,8 @@ import { in_anaconda_mode } from "utils";
     521              :  import firewall from './firewall-client.js';
     522              :  import {
     523              :      device_state_text,
     524              : +    has_group,
     525              : +    is_loopback,
     526              :      is_managed,
     527              :      render_active_connection,
     528              :  } from './interfaces.js';
     529              : @@ -39,23 +41,17 @@ export const NetworkPage = ({ privileged, operationInProgress, usage_monitor, pl
     530              :      const unmanaged = [];
     531              :      const plot_ifaces = [];
     532              :      let hasDetails = false;
     533           36 : +    const anaconda_mode = JSON.parse(window.sessionStorage.getItem("cockpit_anaconda"));
     534              : +
     535              :  
     536              :      interfaces.forEach(iface => {
     537              : -        function hasGroup(iface) {
     538              : -            return ((iface.Device &&
     539              : -                     iface.Device.ActiveConnection &&
     540              : -                     iface.Device.ActiveConnection.Group &&
     541              : -                     iface.Device.ActiveConnection.Group.Members.length > 0) ||
     542              : -                    (iface.MainConnection &&
     543              : -                     iface.MainConnection.Groups.length > 0));
     544              : -        }
     545              :  
     546              :          // Skip loopback
     547              : -        if (iface.Name == "lo" || (iface.Device && iface.Device.DeviceType == 'loopback'))
     548           36 : +        if (is_loopback(iface))
     549              :              return;
     550              :  
     551              :          // Skip members
     552              : -        if (hasGroup(iface))
     553           36 : +        else if (has_group(iface))
     554              :              return;
     555              :  
     556              :          const dev = iface.Device;
     557              : diff --git a/pkg/networkmanager/networking.scss b/pkg/networkmanager/networking.scss
     558              : index bf340a515..823e383d2 100644
     559              : --- a/pkg/networkmanager/networking.scss
     560              : +++ b/pkg/networkmanager/networking.scss
     561              : @@ -323,3 +323,10 @@ th {
     562              :  }
     563              :  
     564              :  /* End Firewall specific CSS */
     565              : +
     566              : +#networking.anaconda .pf-v6-c-page__main-container {
     567              : +  border-radius: 0;
     568              : +  margin: 0;
     569              : +  border: 0;
     570              : +  max-block-size: 100%;
     571              : +}
     572              : diff --git a/pkg/networkmanager/networkmanager.jsx b/pkg/networkmanager/networkmanager.jsx
     573              : index 56bf87476..4dee79f4c 100644
     574              : --- a/pkg/networkmanager/networkmanager.jsx
     575              : +++ b/pkg/networkmanager/networkmanager.jsx
     576              : @@ -25,6 +25,7 @@ import { PlotState } from 'plot';
     577              :  
     578              :  import { useObject, useEvent, usePageLocation } from "hooks";
     579              :  import { WithDialogs } from "dialogs.jsx";
     580              : +import { AnacondaNetworkPage } from './anaconda-main';
     581              :  
     582              :  const _ = cockpit.gettext;
     583              :  
     584              : @@ -99,11 +100,29 @@ const App = () => {
     585              :  
     586              :      const interfaces = model.list_interfaces();
     587              :  
     588           37 : +    const anaconda_mode = JSON.parse(window.sessionStorage.getItem("cockpit_anaconda"));
     589              : +
     590            7 : +    if (anaconda_mode) {
     591            0 : +        const iface = path.length == 1 ? interfaces.find(iface => iface.Name == path[0]) : undefined;
     592              : +
     593            7 : +        return (
     594            7 : +            <ModelContext.Provider value={model}>
     595            7 : +                <WithDialogs key="networking-anaconda">
     596            7 : +                    <AnacondaNetworkPage privileged={superuser.allowed}
     597            7 : +                                         operationInProgress={model.operationInProgress}
     598            7 : +                                         usage_monitor={usage_monitor}
     599            7 : +                                         interfaces={interfaces}
     600            7 : +                                         iface={iface} />
     601            7 : +                </WithDialogs>
     602            7 : +            </ModelContext.Provider>
     603              : +        );
     604            7 : +    }
     605              : +
     606              :      /* At this point NM is running and the model is ready */
     607              :      if (path.length == 0) {
     608              :          return (
     609              :              <ModelContext.Provider value={model}>
     610              : -                <WithDialogs key="1">
     611           36 : +                <WithDialogs key="networking">
     612              :                      <NetworkPage privileged={superuser.allowed}
     613              :                                   operationInProgress={model.operationInProgress}
     614              :                                   usage_monitor={usage_monitor}
     615              : @@ -118,7 +137,7 @@ const App = () => {
     616              :          if (iface) {
     617              :              return (
     618              :                  <ModelContext.Provider value={model}>
     619              : -                    <WithDialogs key="2">
     620           34 : +                    <WithDialogs key="networking-interface">
     621              :                          <NetworkInterfacePage privileged={superuser.allowed}
     622              :                                                operationInProgress={model.operationInProgress}
     623              :                                                usage_monitor={usage_monitor}
     624              : diff --git a/pkg/packagekit/updates.jsx b/pkg/packagekit/updates.jsx
     625              : index 68b40fcf1..3d3cceebd 100644
     626              : --- a/pkg/packagekit/updates.jsx
     627              : +++ b/pkg/packagekit/updates.jsx
     628              : @@ -1212,12 +1212,17 @@ class OsUpdates extends React.Component {
     629              :          try {
     630              :              const seconds = await this.state.packageManager.get_last_refresh_time();
     631              :              this.setState({ timeSinceRefresh: seconds });
     632           17 : +            console.log("seconds", seconds)
     633              :  
     634              :              // automatically trigger refresh for ≥ 1 day or if never refreshed
     635              : -            if (seconds >= 24 * 3600 || seconds < 0)
     636            2 : +            if (seconds >= 60 * 5 || seconds < 0) {
     637            7 : +                console.log("refreshing")
     638              :                  this.handleRefresh();
     639              : -            else if (always_load)
     640            7 : +            }
     641            9 : +            else if (always_load) {
     642            9 : +                console.log("always refresh")
     643              :                  this.loadUpdates();
     644            9 : +            }
     645              :          } catch (exc) {
     646              :              this.handleLoadError(exc);
     647              :          }
     648              : diff --git a/test/verify/check-networkmanager-wifi b/test/verify/check-networkmanager-wifi
     649              : index d104e1b10..8322e5a63 100755
     650              : --- a/test/verify/check-networkmanager-wifi
     651              : +++ b/test/verify/check-networkmanager-wifi
     652              : @@ -108,6 +108,7 @@ wpa_passphrase=hidden99""")
     653              :          # detects our three visible wifis (plus one hidden)
     654              :          b.wait_in_text("table[aria-label='Available networks'] tbody:first-of-type", "ZE WIFI!")
     655              :          b.wait_text("tr[data-ssid='ZE WIFI!'] th", "ZE WIFI!")
     656              : +        testlib.sit()
     657              :          self.assertEqual(b.get_pf_progress_value("tr[data-ssid='ZE WIFI!'] [data-label='Signal']"), 100)
     658              :          # sadly, mac80211_hwsim's data rate reporting is flaky, so just ensure at least one of our networks is correct
     659              :          b.wait_in_text("table[aria-label='Available networks']", "54 Mbps")
        

Generated by: LCOV version 2.0-1