LCOV - code coverage report
Current view: top level - pkg/networkmanager - network-main.jsx Coverage Total Hit
Test: cockpit Lines: 1.7 % 178 3
Test Date: 2026-06-16 14:09:37

            Line data    Source code
       1              : /*
       2              :  * Copyright (C) 2021 Red Hat, Inc.
       3              :  * SPDX-License-Identifier: LGPL-2.1-or-later
       4              :  */
       5              : 
       6            1 : import cockpit from "cockpit";
       7            1 : import React from 'react';
       8              : import { useEvent } from "hooks";
       9              : 
      10              : import { Button } from "@patternfly/react-core/dist/esm/components/Button/index.js";
      11              : import { Label } from "@patternfly/react-core/dist/esm/components/Label/index.js";
      12              : import { Card, CardBody, CardHeader, CardTitle } from '@patternfly/react-core/dist/esm/components/Card/index.js';
      13              : import { Flex, FlexItem } from "@patternfly/react-core/dist/esm/layouts/Flex/index.js";
      14              : import { Gallery } from "@patternfly/react-core/dist/esm/layouts/Gallery/index.js";
      15              : import { Page, PageSection, } from "@patternfly/react-core/dist/esm/components/Page/index.js";
      16              : import { ConnectedIcon } from "@patternfly/react-icons";
      17              : 
      18              : import { FirewallSwitch } from "./firewall-switch.jsx";
      19              : import { ListingTable } from "cockpit-components-table.jsx";
      20              : import { NetworkAction } from "./dialogs-common.jsx";
      21              : import { LogsPanel } from "cockpit-components-logs-panel.jsx";
      22              : import { NetworkPlots } from "./plots";
      23              : import { in_anaconda_mode } from "utils";
      24              : 
      25              : import firewall from './firewall-client.js';
      26              : import {
      27              :     device_state_text,
      28              :     is_managed,
      29              :     render_active_connection,
      30              : } from './interfaces.js';
      31              : 
      32            1 : const _ = cockpit.gettext;
      33              : 
      34            0 : export const NetworkPage = ({ privileged, operationInProgress, usage_monitor, plot_state, interfaces }) => {
      35            0 :     useEvent(firewall, "changed");
      36            0 :     useEvent(usage_monitor.grid, "notify");
      37              : 
      38            0 :     const managed = [];
      39            0 :     const unmanaged = [];
      40            0 :     const plot_ifaces = [];
      41            0 :     let hasDetails = false;
      42              : 
      43            0 :     interfaces.forEach(iface => {
      44            0 :         function hasGroup(iface) {
      45            0 :             return ((iface.Device &&
      46            0 :                      iface.Device.ActiveConnection &&
      47            0 :                      iface.Device.ActiveConnection.Group &&
      48            0 :                      iface.Device.ActiveConnection.Group.Members.length > 0) ||
      49            0 :                     (iface.MainConnection &&
      50            0 :                      iface.MainConnection.Groups.length > 0));
      51            0 :         }
      52              : 
      53              :         // Skip loopback
      54            0 :         if (iface.Name == "lo" || (iface.Device && iface.Device.DeviceType == 'loopback'))
      55            0 :             return;
      56              : 
      57              :         // Skip members
      58            0 :         if (hasGroup(iface))
      59            0 :             return;
      60              : 
      61            0 :         const dev = iface.Device;
      62            0 :         const show_traffic = (dev && (dev.State == 100 || dev.State == 10) && dev.Carrier === true);
      63              : 
      64            0 :         plot_ifaces.push(iface.Name);
      65            0 :         usage_monitor.add(iface.Name);
      66              : 
      67            0 :         const activeConnection = render_active_connection(dev, false, true);
      68            0 :         const row = {
      69            0 :             columns: [
      70            0 :                 { title: (!dev || is_managed(dev)) ? <Button variant="link" isInline onClick={() => cockpit.location.go([iface.Name])}>{iface.Name}</Button> : iface.Name },
      71            0 :                 { title: activeConnection },
      72            0 :             ],
      73            0 :             props: {
      74            0 :                 key: iface.Name,
      75            0 :                 "data-interface": encodeURIComponent(iface.Name),
      76            0 :                 "data-sample-id": show_traffic ? encodeURIComponent(iface.Name) : null,
      77            0 :                 "data-row-id": iface.Name,
      78            0 :             }
      79            0 :         };
      80              : 
      81            0 :         if (show_traffic) {
      82            0 :             const samples = usage_monitor.samples[iface.Name];
      83            0 :             row.columns.push({ title: samples ? cockpit.format_bits_per_sec(samples[1][0] * 8) : "" });
      84            0 :             row.columns.push({ title: samples ? cockpit.format_bits_per_sec(samples[0][0] * 8) : "" });
      85            0 :         } else {
      86            0 :             row.columns.push({ title: device_state_text(dev), props: { colSpan: 2 } });
      87            0 :         }
      88              : 
      89              :         // Details column: show type-specific information
      90            0 :         let detailsColumn = null;
      91            0 :         if (dev?.DeviceType === '802-11-wireless') {
      92            0 :             const networkCount = dev.visibleSsids.length;
      93            0 :             if (networkCount > 0 || dev.ActiveAccessPoint?.Ssid) {
      94            0 :                 hasDetails = true;
      95            0 :                 detailsColumn = (
      96            0 :                     <Flex columnGap={{ default: 'columnGapSm' }}>
      97            0 :                         {networkCount > 0 && (
      98            0 :                             <FlexItem>
      99            0 :                                 <Label status="info">
     100            0 :                                     {cockpit.format(cockpit.ngettext("$0 network", "$0 networks", networkCount), networkCount)}
     101            0 :                                 </Label>
     102            0 :                             </FlexItem>
     103              :                         )}
     104            0 :                         {dev.ActiveAccessPoint?.Ssid && (
     105            0 :                             <FlexItem>
     106            0 :                                 <Label status="success" icon={<ConnectedIcon />}>{dev.ActiveAccessPoint?.Ssid}</Label>
     107            0 :                             </FlexItem>
     108              :                         )}
     109            0 :                     </Flex>
     110              :                 );
     111            0 :             }
     112            0 :         }
     113            0 :         row.columns.push({ title: detailsColumn });
     114              : 
     115            0 :         if (!dev || is_managed(dev)) {
     116            0 :             managed.push(row);
     117            0 :         } else {
     118            0 :             unmanaged.push(row);
     119            0 :         }
     120            0 :     });
     121              : 
     122            0 :     const rx_plot_data = {
     123            0 :         direct: "network.interface.in.bytes",
     124            0 :         internal: "network.interface.rx",
     125            0 :         units: "bytes",
     126            0 :         derive: "rate",
     127            0 :         threshold: 200,
     128            0 :         factor: 8
     129            0 :     };
     130              : 
     131            0 :     const tx_plot_data = {
     132            0 :         direct: "network.interface.out.bytes",
     133            0 :         internal: "network.interface.tx",
     134            0 :         units: "bytes",
     135            0 :         derive: "rate",
     136            0 :         threshold: 200,
     137            0 :         factor: 8
     138            0 :     };
     139              : 
     140            0 :     plot_state.plot_instances('rx', rx_plot_data, plot_ifaces);
     141            0 :     plot_state.plot_instances('tx', tx_plot_data, plot_ifaces);
     142              : 
     143              :     /* Start of properties for the LogsPanel component */
     144            0 :     const match = [
     145            0 :         "_SYSTEMD_UNIT=NetworkManager.service", "+",
     146            0 :         "_SYSTEMD_UNIT=firewalld.service"
     147            0 :     ];
     148            0 :     const search_options = {
     149            0 :         prio: "debug",
     150            0 :         _SYSTEMD_UNIT: "NetworkManager.service,firewalld.service"
     151            0 :     };
     152            0 :     const url = "/system/logs/#/?prio=debug&_SYSTEMD_UNIT=NetworkManager.service,firewalld.service";
     153              :     /* End of properties for the LogsPanel component */
     154              : 
     155            0 :     const actions = privileged && (
     156            0 :         <>
     157            0 :             <NetworkAction buttonText={_("Add VPN")} type='wg' />
     158            0 :             <NetworkAction buttonText={_("Add bond")} type='bond' />
     159            0 :             <NetworkAction buttonText={_("Add team")} type='team' />
     160            0 :             <NetworkAction buttonText={_("Add bridge")} type='bridge' />
     161            0 :             <NetworkAction buttonText={_("Add VLAN")} type='vlan' />
     162            0 :         </>
     163              :     );
     164              : 
     165            0 :     const anaconda = in_anaconda_mode();
     166              : 
     167            0 :     return (
     168            0 :         <Page data-test-wait={operationInProgress} id="networking" className={"pf-m-no-sidebar" + (anaconda ? " anaconda" : "")}>
     169            0 :             <PageSection hasBodyWrapper={false} id="networking-graphs" className="networking-graphs">
     170            0 :                 <NetworkPlots plot_state={plot_state} />
     171            0 :             </PageSection>
     172            0 :             <PageSection hasBodyWrapper={false}>
     173            0 :                 <Gallery hasGutter>
     174            0 :                     {firewall.installed && <Card isPlain id="networking-firewall-summary">
     175            0 :                         <CardHeader actions={{
     176            0 :                             actions: <Button variant="secondary" id="networking-firewall-link"
     177            0 :                                         component="a"
     178            0 :                                         onClick={() => cockpit.jump("/network/firewall", cockpit.transport.host)}>
     179            0 :                                 {_("Edit rules and zones")}
     180            0 :                             </Button>,
     181            0 :                         }}>
     182            0 :                             <Flex gap={{ default: 'gapMd' }} alignItems={{ default: 'alignItemsCenter' }}>
     183            0 :                                 <CardTitle component="h2">{_("Firewall")}</CardTitle>
     184            0 :                                 <FirewallSwitch firewall={firewall} />
     185            0 :                             </Flex>
     186            0 :                         </CardHeader>
     187            0 :                         <CardBody>
     188            0 :                             <Button variant="link"
     189            0 :                                     component="a"
     190            0 :                                     isInline
     191            0 :                                     onClick={() => cockpit.jump("/network/firewall", cockpit.transport.host)}>
     192            0 :                                 {cockpit.format(cockpit.ngettext("$0 active zone", "$0 active zones", firewall.activeZones.size), firewall.activeZones.size)}
     193            0 :                             </Button>
     194            0 :                         </CardBody>
     195            0 :                     </Card>}
     196            0 :                     <Card isPlain id="networking-interfaces">
     197            0 :                         <CardHeader actions={{ actions }}>
     198            0 :                             <CardTitle component="h2">{_("Interfaces")}</CardTitle>
     199            0 :                         </CardHeader>
     200            0 :                         <ListingTable aria-label={_("Managed interfaces")}
     201            0 :                                       variant='compact'
     202            0 :                                       columns={[
     203            0 :                                           { title: _("Name"), header: true, props: { width: hasDetails ? 15 : 25 } },
     204            0 :                                           { title: _("IP address"), props: { width: hasDetails ? 35 : 25 } },
     205            0 :                                           { title: _("Sending"), props: { width: hasDetails ? 15 : 25 } },
     206            0 :                                           { title: _("Receiving"), props: { width: hasDetails ? 15 : 25 } },
     207            0 :                                           ...(hasDetails ? [{ title: _("Details"), props: { width: 20 } }] : []),
     208            0 :                                       ]}
     209            0 :                                       rows={managed} />
     210            0 :                     </Card>
     211            0 :                     {unmanaged.length > 0 &&
     212            0 :                     <Card isPlain id="networking-unmanaged-interfaces">
     213            0 :                         <CardHeader>
     214            0 :                             <CardTitle component="h2">{_("Unmanaged interfaces")}</CardTitle>
     215            0 :                         </CardHeader>
     216            0 :                         <ListingTable aria-label={_("Unmanaged interfaces")}
     217            0 :                                       variant='compact'
     218            0 :                                       columns={[
     219            0 :                                           { title: _("Name"), header: true, props: { width: hasDetails ? 15 : 25 } },
     220            0 :                                           { title: _("IP address"), props: { width: hasDetails ? 35 : 25 } },
     221            0 :                                           { title: _("Sending"), props: { width: hasDetails ? 15 : 25 } },
     222            0 :                                           { title: _("Receiving"), props: { width: hasDetails ? 15 : 25 } },
     223            0 :                                           ...(hasDetails ? [{ title: _("Details"), props: { width: 20 } }] : []),
     224            0 :                                       ]}
     225            0 :                                       rows={unmanaged} />
     226            0 :                     </Card>}
     227            0 :                     <LogsPanel title={_("Network logs")} match={match}
     228            0 :                                max={10} search_options={search_options}
     229            0 :                                goto_url={url}
     230            0 :                                className="contains-list" />
     231            0 :                 </Gallery>
     232            0 :             </PageSection>
     233            0 :         </Page>
     234              :     );
     235            0 : };
        

Generated by: LCOV version 2.0-1