LCOV - code coverage report
Current view: top level - pkg/networkmanager - bond.jsx Coverage Total Hit
Test: cockpit Lines: 12.8 % 180 23
Test Date: 2026-06-17 06:28:00

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

Generated by: LCOV version 2.0-1