LCOV - code coverage report
Current view: top level - lcov - github-pr.diff Coverage Total Hit
Test: cockpit Lines: 44.2 % 190 84
Test Date: 2026-06-01 17:00:21

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

Generated by: LCOV version 2.0-1