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