LCOV - code coverage report
Current view: top level - pkg/networkmanager - bond.jsx Coverage Total Hit
Test: cockpit Lines: 97.8 % 180 176
Test Date: 2026-06-25 11:17:56

            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 React, { useState, useContext } from 'react';
       7           37 : import cockpit from 'cockpit';
       8              : import { Button } from "@patternfly/react-core/dist/esm/components/Button/index.js";
       9              : import { FormGroup } from "@patternfly/react-core/dist/esm/components/Form/index.js";
      10              : import { FormSelect, FormSelectOption } from "@patternfly/react-core/dist/esm/components/FormSelect/index.js";
      11              : import { Popover } from "@patternfly/react-core/dist/esm/components/Popover/index.js";
      12              : import { TextInput } from "@patternfly/react-core/dist/esm/components/TextInput/index.js";
      13              : import { ExternalLinkSquareAltIcon, HelpIcon } from '@patternfly/react-icons';
      14              : 
      15              : import { MacMenu, MemberInterfaceChoices, NetworkModal, Name, dialogSave } from './dialogs-common.jsx';
      16              : import { ModelContext } from './model-context.jsx';
      17              : import { useDialogs } from "dialogs.jsx";
      18              : 
      19              : import { v4 as uuidv4 } from 'uuid';
      20              : import {
      21              :     member_connection_for_interface,
      22              :     member_interface_choices,
      23              : } from './interfaces.js';
      24              : 
      25           37 : const _ = cockpit.gettext;
      26              : 
      27           37 : export const bond_mode_choices =
      28           37 :     [
      29           37 :         { choice: 'balance-rr', title: _("Round robin") },
      30           37 :         { choice: 'active-backup', title: _("Active backup") },
      31           37 :         { choice: 'balance-xor', title: _("XOR") },
      32           37 :         { choice: 'broadcast', title: _("Broadcast") },
      33           37 :         { choice: '802.3ad', title: _("802.3ad") },
      34           37 :         { choice: 'balance-tlb', title: _("Adaptive transmit load balancing") },
      35           37 :         { choice: 'balance-alb', title: _("Adaptive load balancing") }
      36           37 :     ];
      37              : 
      38           37 : const bond_monitoring_choices =
      39           37 :     [
      40           37 :         { choice: 'mii', title: _("MII (recommended)") },
      41           37 :         { choice: 'arp', title: _("ARP") }
      42           37 :     ];
      43              : 
      44           37 : const modes_with_primary = [
      45           37 :     'active-backup',
      46           37 :     'balance-tlb',
      47           37 :     'balance-alb'
      48           37 : ];
      49              : 
      50            7 : export const BondDialog = ({ connection, dev, settings }) => {
      51            7 :     const Dialogs = useDialogs();
      52            7 :     const idPrefix = "network-bond-settings";
      53            7 :     const model = useContext(ModelContext);
      54            7 :     const options = settings.bond.options;
      55              : 
      56            7 :     const memberChoicesInit = {};
      57              : 
      58            7 :     member_interface_choices(model, connection).forEach((iface) => {
      59            7 :         memberChoicesInit[iface.Name] = !!member_connection_for_interface(connection, iface);
      60            7 :     });
      61              : 
      62            7 :     const [dialogError, setDialogError] = useState(undefined);
      63            7 :     const [iface, setIface] = useState(settings.connection.interface_name);
      64            7 :     const [linkDownDelay, setLinkDownDelay] = useState(options.downdelay || "0");
      65            1 :     const [linkMonitoring, setLinkMonitoring] = useState(options.arp_interval ? "arp" : "mii");
      66            7 :     const [linkMonitoringInterval, setLinkMonitoringInterval] = useState(options.miimon || options.arp_interval || "100");
      67            7 :     const [linkUpDelay, setLinkUpDelay] = useState(options.updelay || "0");
      68            2 :     const [mac, setMAC] = useState((settings.ethernet && settings.ethernet.assigned_mac_address) || "");
      69            7 :     const [memberChoices, setMemberChoices] = useState(memberChoicesInit);
      70            7 :     const [mode, setMode] = useState(options.mode);
      71            7 :     const [monitoringTargets, setMonitoringTargets] = useState(options.arp_ip_target);
      72            7 :     const [primary, setPrimary] = useState(options.primary);
      73              : 
      74            7 :     const onSubmit = (ev) => {
      75            7 :         const options = settings.bond.options;
      76            7 :         delete options.primary;
      77              : 
      78            7 :         const createSettingsObj = () => ({
      79            7 :             ...settings,
      80            7 :             connection: {
      81            7 :                 ...settings.connection,
      82            7 :                 id: iface,
      83            7 :                 interface_name: iface,
      84            7 :             },
      85            7 :             ethernet: {
      86            7 :                 assigned_mac_address: mac
      87            7 :             },
      88            7 :             bond: {
      89            7 :                 ...settings.bond,
      90            7 :                 interface_name: iface,
      91            7 :                 options: {
      92            7 :                     ...options,
      93            7 :                     mode,
      94            6 :                     ...(linkMonitoring == 'mii' && {
      95            6 :                         miimon: linkMonitoringInterval,
      96            6 :                         updelay: linkUpDelay,
      97            6 :                         downdelay: linkDownDelay,
      98            6 :                     }),
      99            1 :                     ...(linkMonitoring === 'arp' && {
     100            1 :                         arp_interval: linkMonitoringInterval,
     101            1 :                         arp_ip_target: monitoringTargets,
     102            1 :                     }),
     103            1 :                     ...(modes_with_primary.includes(mode) && primary && { primary })
     104            7 :                 }
     105            7 :             }
     106            7 :         });
     107              : 
     108            7 :         dialogSave({
     109            7 :             model,
     110            7 :             dev,
     111            7 :             connection,
     112            7 :             members: memberChoices,
     113            7 :             membersInit: memberChoicesInit,
     114            7 :             settings: createSettingsObj(),
     115            7 :             setDialogError,
     116            7 :             onClose: Dialogs.close,
     117            7 :         });
     118              : 
     119              :         // Prevent dialog from closing because of <form> onsubmit event
     120            7 :         if (ev)
     121            7 :             ev.preventDefault();
     122              : 
     123            7 :         return false;
     124            7 :     };
     125              : 
     126            7 :     return (
     127            7 :         <NetworkModal dialogError={dialogError}
     128            7 :                       idPrefix={idPrefix}
     129            7 :                       onSubmit={onSubmit}
     130            2 :                       title={!connection ? _("Add bond") : _("Edit bond settings")}
     131            7 :                       help={
     132            7 :                           <Popover
     133            7 :                               headerContent={_("Network bond")}
     134            7 :                               id="bond-help"
     135            7 :                               bodyContent={
     136            7 :                                   <div>
     137            7 :                                       {_("A network bond combines multiple network interfaces into one logical interface with higher throughput or redundancy.")}
     138            7 :                                   </div>
     139              :                               }
     140            7 :                               footerContent={
     141            7 :                                   <Button component='a'
     142            7 :                                           rel="noopener noreferrer" target="_blank"
     143            7 :                                           variant='link'
     144            7 :                                           isInline
     145            7 :                                           icon={<ExternalLinkSquareAltIcon />} iconPosition="right"
     146            7 :                                           href="https://docs.redhat.com/en/documentation/red_hat_enterprise_linux/9/html/configuring_and_managing_networking/configuring-network-bonding_configuring-and-managing-networking#proc_configuring-a-network-bond-by-using-the-rhel-web-console_configuring-network-bonding">
     147            7 :                                       {_("Learn more")}
     148            7 :                                   </Button>
     149              :                               }
     150              :                           >
     151            7 :                               <Button icon={<HelpIcon />} id="bond-help-popup-button" variant="plain" aria-label="Help" />
     152            7 :                           </Popover>
     153              :                       }
     154            7 :                       isCreateDialog={!connection}
     155              :         >
     156            7 :             <>
     157            7 :                 <Name idPrefix={idPrefix} iface={iface} setIface={setIface} />
     158            7 :                 <FormGroup label={_("Interfaces")} fieldId={idPrefix + "-interface-members-list"} hasNoPaddingTop>
     159            7 :                     <MemberInterfaceChoices idPrefix={idPrefix} memberChoices={memberChoices} setMemberChoices={setMemberChoices} model={model} group={connection} />
     160            7 :                 </FormGroup>
     161            7 :                 <FormGroup fieldId={idPrefix + "-mac-input"} label={_("MAC")}>
     162            7 :                     <MacMenu idPrefix={idPrefix} model={model} mac={mac} setMAC={setMAC} />
     163            7 :                 </FormGroup>
     164            7 :                 <FormGroup fieldId={idPrefix + "-mode-select"} label={_("Mode")}>
     165            0 :                     <FormSelect id={idPrefix + "-mode-select"} onChange={(_, val) => setMode(val)}
     166            7 :                                 value={mode}>
     167            7 :                         {bond_mode_choices.map(choice => <FormSelectOption value={choice.choice} label={choice.title} key={choice.choice} />)}
     168            7 :                     </FormSelect>
     169            7 :                 </FormGroup>
     170            7 :                 {modes_with_primary.includes(mode) && <FormGroup fieldId={idPrefix + "-primary-select"} label={_("Primary")}>
     171            1 :                     <FormSelect id={idPrefix + "-primary-select"} onChange={(_, val) => setPrimary(val)}
     172            7 :                                 value={primary}>
     173            7 :                         <>
     174            7 :                             <FormSelectOption key='-' value='' label='-' />
     175            7 :                             {Object.keys(memberChoices)
     176            7 :                                     .filter(iface => memberChoices[iface])
     177            7 :                                     .map(iface => <FormSelectOption key={iface} label={iface} value={iface} />)}
     178            7 :                         </>
     179            7 :                     </FormSelect>
     180            7 :                 </FormGroup>}
     181            7 :                 <FormGroup fieldId={idPrefix + "-link-monitoring-select"} label={_("Link monitoring")}>
     182            1 :                     <FormSelect id={idPrefix + "-link-monitoring-select"} onChange={(_, val) => setLinkMonitoring(val)}
     183            7 :                                 value={linkMonitoring}>
     184            7 :                         {bond_monitoring_choices.map(choice => <FormSelectOption value={choice.choice} label={choice.title} key={choice.choice} />)}
     185            7 :                     </FormSelect>
     186            7 :                 </FormGroup>
     187            7 :                 <FormGroup fieldId={idPrefix + "-link-monitoring-interval-input"} label={_("Monitoring interval")}>
     188            0 :                     <TextInput id={idPrefix + "-link-monitoring-interval-input"} className="network-number-field" value={linkMonitoringInterval} onChange={(_event, value) => setLinkMonitoringInterval(value)} />
     189            7 :                 </FormGroup>
     190            7 :                 {linkMonitoring == 'mii' && <>
     191            7 :                     <FormGroup fieldId={idPrefix + "-link-up-delay-input"} label={_("Link up delay")}>
     192            0 :                         <TextInput id={idPrefix + "-link-up-delay-input"} className="network-number-field" value={linkUpDelay} onChange={(_event, value) => setLinkUpDelay(value)} />
     193            7 :                     </FormGroup>
     194            7 :                     <FormGroup fieldId={idPrefix + "-link-down-delay-input"} label={_("Link down delay")}>
     195            0 :                         <TextInput id={idPrefix + "-link-down-delay-input"} className="network-number-field" value={linkDownDelay} onChange={(_event, value) => setLinkDownDelay(value)} />
     196            7 :                     </FormGroup>
     197            7 :                 </>}
     198            1 :                 {linkMonitoring == 'arp' && <FormGroup fieldId={idPrefix + "-monitoring-targets-input"} label={_("Monitoring targets")}>
     199            1 :                     <TextInput id={idPrefix + "-monitoring-targets-input"} value={monitoringTargets} onChange={(_event, value) => setMonitoringTargets(value)} />
     200            1 :                 </FormGroup>}
     201            7 :             </>
     202            7 :         </NetworkModal>
     203              :     );
     204            7 : };
     205              : 
     206           35 : export const getGhostSettings = ({ newIfaceName }) => {
     207           35 :     return (
     208           35 :         {
     209           35 :             connection: {
     210           35 :                 id: newIfaceName,
     211           35 :                 autoconnect: true,
     212           35 :                 type: "bond",
     213           35 :                 uuid: uuidv4(),
     214           35 :                 interface_name: newIfaceName,
     215           35 :             },
     216           35 :             bond: {
     217           35 :                 options: {
     218           35 :                     mode: "active-backup"
     219           35 :                 },
     220           35 :                 interface_name: newIfaceName
     221           35 :             }
     222           35 :         }
     223              :     );
     224           35 : };
        

Generated by: LCOV version 2.0-1