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 : };
|