Line data Source code
1 : /*
2 : * Copyright (C) 2021 Red Hat, Inc.
3 : * SPDX-License-Identifier: LGPL-2.1-or-later
4 : */
5 :
6 35 : import React, { useState, useContext } from 'react';
7 35 : 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 35 : const _ = cockpit.gettext;
26 :
27 35 : export const bond_mode_choices =
28 35 : [
29 35 : { choice: 'balance-rr', title: _("Round robin") },
30 35 : { choice: 'active-backup', title: _("Active backup") },
31 35 : { choice: 'balance-xor', title: _("XOR") },
32 35 : { choice: 'broadcast', title: _("Broadcast") },
33 35 : { choice: '802.3ad', title: _("802.3ad") },
34 35 : { choice: 'balance-tlb', title: _("Adaptive transmit load balancing") },
35 35 : { choice: 'balance-alb', title: _("Adaptive load balancing") }
36 35 : ];
37 :
38 35 : const bond_monitoring_choices =
39 35 : [
40 35 : { choice: 'mii', title: _("MII (recommended)") },
41 35 : { choice: 'arp', title: _("ARP") }
42 35 : ];
43 :
44 35 : const modes_with_primary = [
45 35 : 'active-backup',
46 35 : 'balance-tlb',
47 35 : 'balance-alb'
48 35 : ];
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 33 : export const getGhostSettings = ({ newIfaceName }) => {
207 33 : return (
208 33 : {
209 33 : connection: {
210 33 : id: newIfaceName,
211 33 : autoconnect: true,
212 33 : type: "bond",
213 33 : uuid: uuidv4(),
214 33 : interface_name: newIfaceName,
215 33 : },
216 33 : bond: {
217 33 : options: {
218 33 : mode: "active-backup"
219 33 : },
220 33 : interface_name: newIfaceName
221 33 : }
222 33 : }
223 : );
224 33 : };
|