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 { FormGroup } from "@patternfly/react-core/dist/esm/components/Form/index.js";
9 : import { FormSelect, FormSelectOption } from "@patternfly/react-core/dist/esm/components/FormSelect/index.js";
10 : import { TextInput } from "@patternfly/react-core/dist/esm/components/TextInput/index.js";
11 :
12 : import { MemberInterfaceChoices, NetworkModal, Name, dialogSave } from './dialogs-common.jsx';
13 : import { ModelContext } from './model-context.jsx';
14 : import { useDialogs } from "dialogs.jsx";
15 :
16 : import { v4 as uuidv4 } from 'uuid';
17 : import {
18 : member_connection_for_interface,
19 : member_interface_choices,
20 : } from './interfaces.js';
21 :
22 1 : const _ = cockpit.gettext;
23 :
24 1 : export const team_runner_choices =
25 1 : [
26 1 : { choice: 'roundrobin', title: _("Round robin") },
27 1 : { choice: 'activebackup', title: _("Active backup") },
28 1 : { choice: 'loadbalance', title: _("Load balancing") },
29 1 : { choice: 'broadcast', title: _("Broadcast") },
30 1 : { choice: 'lacp', title: _("802.3ad LACP") },
31 1 : ];
32 :
33 1 : export const team_balancer_choices =
34 1 : [
35 1 : { choice: 'none', title: _("Passive") },
36 1 : { choice: 'basic', title: _("Active") }
37 1 : ];
38 :
39 1 : export const team_watch_choices =
40 1 : [
41 1 : { choice: 'ethtool', title: _("Ethtool") },
42 1 : { choice: 'arp-ping', title: _("ARP ping") },
43 1 : { choice: 'nsna-ping', title: _("NSNA ping") }
44 1 : ];
45 :
46 0 : export const TeamDialog = ({ connection, dev, settings }) => {
47 0 : const Dialogs = useDialogs();
48 0 : const idPrefix = "network-team-settings";
49 0 : const model = useContext(ModelContext);
50 0 : const config = settings.team.config || {};
51 0 : if (!config.runner)
52 0 : config.runner = { };
53 0 : if (!config.runner.name)
54 0 : config.runner.name = "activebackup";
55 0 : if (!config.link_watch)
56 0 : config.link_watch = { };
57 0 : if (!config.link_watch.name)
58 0 : config.link_watch.name = "ethtool";
59 0 : if (config.link_watch.interval === undefined)
60 0 : config.link_watch.interval = 100;
61 0 : if (config.link_watch.delay_up === undefined)
62 0 : config.link_watch.delay_up = 0;
63 0 : if (config.link_watch.delay_down === undefined)
64 0 : config.link_watch.delay_down = 0;
65 :
66 0 : const memberChoicesInit = {};
67 :
68 0 : member_interface_choices(model, connection).forEach((iface) => {
69 0 : memberChoicesInit[iface.Name] = !!member_connection_for_interface(connection, iface);
70 0 : });
71 :
72 0 : const [balancer, setBalancer] = useState(config.balancer);
73 0 : const [dialogError, setDialogError] = useState(undefined);
74 0 : const [iface, setIface] = useState(settings.connection.interface_name);
75 0 : const [linkDownDelay, setLinkDownDelay] = useState(config.link_watch.delay_down);
76 0 : const [linkWatch, setLinkWatch] = useState(config.link_watch.name);
77 0 : const [linkUpDelay, setLinkUpDelay] = useState(config.link_watch.delay_up);
78 0 : const [memberChoices, setMemberChoices] = useState(memberChoicesInit);
79 0 : const [runner, setRunner] = useState(config.runner.name);
80 0 : const [pingTarget, setPingTarget] = useState(config.link_watch.target_host);
81 0 : const [pingInterval, setPingInterval] = useState(config.link_watch.interval);
82 0 : const [primary, setPrimary] = useState(undefined);
83 :
84 0 : const onSubmit = (ev) => {
85 0 : const createSettingsObj = () => ({
86 0 : ...settings,
87 0 : connection: {
88 0 : ...settings.connection,
89 0 : id: iface,
90 0 : interface_name: iface,
91 0 : },
92 0 : team: {
93 0 : ...settings.team,
94 0 : interface_name: iface,
95 0 : config: {
96 0 : ...settings.team.config,
97 0 : link_watch: {
98 0 : name: linkWatch,
99 0 : ...(linkWatch == 'ethtool' && {
100 0 : delay_up: linkUpDelay,
101 0 : delay_down: linkDownDelay,
102 0 : }),
103 0 : ...(linkWatch != 'ethtool' && {
104 0 : interval: pingInterval,
105 0 : target_host: pingTarget,
106 0 : }),
107 0 : },
108 0 : runner: {
109 0 : ...config.runner,
110 0 : name: runner,
111 0 : ...((runner == "loadbalance" || runner == "lacp") && { tx_balancer: balancer == 'none' ? {} : balancer })
112 0 : }
113 0 : }
114 0 : }
115 0 : });
116 :
117 0 : dialogSave({
118 0 : model,
119 0 : dev,
120 0 : connection,
121 0 : members: memberChoices,
122 0 : membersInit: memberChoicesInit,
123 0 : settings: createSettingsObj(),
124 0 : setDialogError,
125 0 : onClose: Dialogs.close,
126 0 : });
127 :
128 : // Prevent dialog from closing because of <form> onsubmit event
129 0 : if (ev)
130 0 : ev.preventDefault();
131 :
132 0 : return false;
133 0 : };
134 :
135 0 : return (
136 0 : <NetworkModal dialogError={dialogError}
137 0 : idPrefix={idPrefix}
138 0 : onSubmit={onSubmit}
139 0 : title={!connection ? _("Add team") : _("Edit team settings")}
140 0 : isCreateDialog={!connection}
141 : >
142 0 : <>
143 0 : <Name idPrefix={idPrefix} iface={iface} setIface={setIface} />
144 0 : <FormGroup label={_("Ports")} fieldId={idPrefix + "-interface-members-list"} hasNoPaddingTop>
145 0 : <MemberInterfaceChoices idPrefix={idPrefix} memberChoices={memberChoices} setMemberChoices={setMemberChoices} model={model} group={connection} />
146 0 : </FormGroup>
147 0 : <FormGroup fieldId={idPrefix + "-runner-select"} label={_("Runner")}>
148 0 : <FormSelect id={idPrefix + "-runner-select"} onChange={(_, val) => setRunner(val)}
149 0 : value={runner}>
150 0 : {team_runner_choices.map(choice => <FormSelectOption value={choice.choice} label={choice.title} key={choice.choice} />)}
151 0 : </FormSelect>
152 0 : </FormGroup>
153 0 : {(runner == "loadbalance" || runner == "lacp") && <FormGroup fieldId={idPrefix + "-balancer-select"} label={_("Balancer")}>
154 0 : <FormSelect id={idPrefix + "-balancer-select"} onChange={(_, val) => setBalancer(val)}
155 0 : value={balancer}>
156 0 : {team_balancer_choices.map(choice => <FormSelectOption value={choice.choice} label={choice.title} key={choice.choice} />)}
157 0 : </FormSelect>
158 0 : </FormGroup>}
159 0 : {runner == "activebackup" && <FormGroup fieldId={idPrefix + "-primary-select"} label={_("Primary")}>
160 0 : <FormSelect id={idPrefix + "-primary-select"} onChange={(_, val) => setPrimary(val)}
161 0 : value={primary}>
162 0 : <>
163 0 : <FormSelectOption key='-' value={null} label='-' />
164 0 : {Object.keys(memberChoices)
165 0 : .filter(iface => memberChoices[iface])
166 0 : .map(iface => <FormSelectOption key={iface} label={iface} value={iface} />)}
167 0 : </>
168 0 : </FormSelect>
169 0 : </FormGroup>}
170 0 : <FormGroup fieldId={idPrefix + "-link-watch-select"} label={_("Link watch")}>
171 0 : <FormSelect id={idPrefix + "-link-watch-select"} onChange={(_, val) => setLinkWatch(val)}
172 0 : value={linkWatch}>
173 0 : {team_watch_choices.map(choice => <FormSelectOption value={choice.choice} label={choice.title} key={choice.choice} />)}
174 0 : </FormSelect>
175 0 : </FormGroup>
176 0 : {linkWatch == 'ethtool' && <>
177 0 : <FormGroup fieldId={idPrefix + "-link-up-delay-input"} label={_("Link up delay")}>
178 0 : <TextInput id={idPrefix + "-link-up-delay-input"} className="network-number-field" value={linkUpDelay} onChange={(_event, value) => setLinkUpDelay(value)} />
179 0 : </FormGroup>
180 0 : <FormGroup fieldId={idPrefix + "-link-down-delay-input"} label={_("Link down delay")}>
181 0 : <TextInput id={idPrefix + "-link-down-delay-input"} className="network-number-field" value={linkDownDelay} onChange={(_event, value) => setLinkDownDelay(value)} />
182 0 : </FormGroup>
183 0 : </>}
184 0 : {linkWatch != 'ethtool' && <>
185 0 : <FormGroup fieldId={idPrefix + "-ping-interval-input"} label={_("Ping interval")}>
186 0 : <TextInput id={idPrefix + "-ping-interval-input"} className="network-number-field" value={pingInterval} onChange={(_event, value) => setPingInterval(value)} />
187 0 : </FormGroup>
188 0 : <FormGroup fieldId={idPrefix + "-ping-target-input"} label={_("Ping target")}>
189 0 : <TextInput id={idPrefix + "-ping-target-input"} value={pingTarget} onChange={(_event, value) => setPingTarget(value)} />
190 0 : </FormGroup>
191 0 : </>}
192 0 : </>
193 0 : </NetworkModal>
194 : );
195 0 : };
196 :
197 0 : export const getGhostSettings = ({ newIfaceName }) => {
198 0 : return (
199 0 : {
200 0 : connection: {
201 0 : id: newIfaceName,
202 0 : autoconnect: true,
203 0 : type: "team",
204 0 : uuid: uuidv4(),
205 0 : interface_name: newIfaceName,
206 0 : },
207 0 : team: {
208 0 : config: {},
209 0 : interface_name: newIfaceName
210 0 : }
211 0 : }
212 : );
213 0 : };
|