LCOV - code coverage report
Current view: top level - pkg/networkmanager - network-main.jsx Coverage Total Hit
Test: cockpit Lines: 99.4 % 178 177
Test Date: 2026-07-17 14:32:59

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

Generated by: LCOV version 2.0-1