* Copyright (C) 2021 Red Hat, Inc.
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
import React, { useState, useContext } from 'react';
import cockpit from 'cockpit';
import { Button } from "@patternfly/react-core/dist/esm/components/Button/index.js";
import { FormGroup } from "@patternfly/react-core/dist/esm/components/Form/index.js";
import { FormSelect, FormSelectOption } from "@patternfly/react-core/dist/esm/components/FormSelect/index.js";
import { Popover } from "@patternfly/react-core/dist/esm/components/Popover/index.js";
import { TextInput } from "@patternfly/react-core/dist/esm/components/TextInput/index.js";
import { ExternalLinkSquareAltIcon, HelpIcon } from '@patternfly/react-icons';
import { MacMenu, MemberInterfaceChoices, NetworkModal, Name, dialogSave } from './dialogs-common.jsx';
import { ModelContext } from './model-context.jsx';
import { useDialogs } from "dialogs.jsx";
import { v4 as uuidv4 } from 'uuid';
import {
member_connection_for_interface,
member_interface_choices,
} from './interfaces.js';
const _ = cockpit.gettext;
export const bond_mode_choices =
[
{ choice: 'balance-rr', title: _("Round robin") },
{ choice: 'active-backup', title: _("Active backup") },
{ choice: 'balance-xor', title: _("XOR") },
{ choice: 'broadcast', title: _("Broadcast") },
{ choice: '802.3ad', title: _("802.3ad") },
{ choice: 'balance-tlb', title: _("Adaptive transmit load balancing") },
{ choice: 'balance-alb', title: _("Adaptive load balancing") }
];
const bond_monitoring_choices =
[
{ choice: 'mii', title: _("MII (recommended)") },
{ choice: 'arp', title: _("ARP") }
];
const modes_with_primary = [
'active-backup',
'balance-tlb',
'balance-alb'
];
export const BondDialog = ({ connection, dev, settings }) => {
const Dialogs = useDialogs();
const idPrefix = "network-bond-settings";
const model = useContext(ModelContext);
const options = settings.bond.options;
const memberChoicesInit = {};
member_interface_choices(model, connection).forEach((iface) => {
memberChoicesInit[iface.Name] = !!member_connection_for_interface(connection, iface);
});
const [dialogError, setDialogError] = useState(undefined);
const [iface, setIface] = useState(settings.connection.interface_name);
const [linkDownDelay, setLinkDownDelay] = useState(options.downdelay || "0");
const [linkMonitoring, setLinkMonitoring] = useState(options.arp_interval ? "arp" : "mii");
const [linkMonitoringInterval, setLinkMonitoringInterval] = useState(options.miimon || options.arp_interval || "100");
const [linkUpDelay, setLinkUpDelay] = useState(options.updelay || "0");
const [mac, setMAC] = useState((settings.ethernet && settings.ethernet.assigned_mac_address) || "");
const [memberChoices, setMemberChoices] = useState(memberChoicesInit);
const [mode, setMode] = useState(options.mode);
const [monitoringTargets, setMonitoringTargets] = useState(options.arp_ip_target);
const [primary, setPrimary] = useState(options.primary);
const onSubmit = (ev) => {
const options = settings.bond.options;
delete options.primary;
const createSettingsObj = () => ({
...settings,
connection: {
...settings.connection,
id: iface,
interface_name: iface,
},
ethernet: {
assigned_mac_address: mac
},
bond: {
...settings.bond,
interface_name: iface,
options: {
...options,
mode,
...(linkMonitoring === 'mii' && {
miimon: linkMonitoringInterval,
updelay: linkUpDelay,
downdelay: linkDownDelay,
}),
...(linkMonitoring === 'arp' && {
arp_interval: linkMonitoringInterval,
arp_ip_target: monitoringTargets,
}),
...(modes_with_primary.includes(mode) && primary && { primary })
}
}
});
dialogSave({
model,
dev,
connection,
members: memberChoices,
membersInit: memberChoicesInit,
settings: createSettingsObj(),
setDialogError,
onClose: Dialogs.close,
});
if (ev)
ev.preventDefault();
return false;
};
return (
<NetworkModal dialogError={dialogError}
idPrefix={idPrefix}
onSubmit={onSubmit}
title={!connection ? _("Add bond") : _("Edit bond settings")}
help={
<Popover
headerContent={_("Network bond")}
id="bond-help"
bodyContent={
<div>
{_("A network bond combines multiple network interfaces into one logical interface with higher throughput or redundancy.")}
</div>
}
footerContent={
<Button component='a'
rel="noopener noreferrer" target="_blank"
variant='link'
isInline
icon={<ExternalLinkSquareAltIcon />} iconPosition="right"
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">
{_("Learn more")}
</Button>
}
>
<Button icon={<HelpIcon />} id="bond-help-popup-button" variant="plain" aria-label="Help" />
</Popover>
}
isCreateDialog={!connection}
>
<>
<Name idPrefix={idPrefix} iface={iface} setIface={setIface} />
<FormGroup label={_("Interfaces")} fieldId={idPrefix + "-interface-members-list"} hasNoPaddingTop>
<MemberInterfaceChoices idPrefix={idPrefix} memberChoices={memberChoices} setMemberChoices={setMemberChoices} model={model} group={connection} />
</FormGroup>
<FormGroup fieldId={idPrefix + "-mac-input"} label={_("MAC")}>
<MacMenu idPrefix={idPrefix} model={model} mac={mac} setMAC={setMAC} />
</FormGroup>
<FormGroup fieldId={idPrefix + "-mode-select"} label={_("Mode")}>
<FormSelect id={idPrefix + "-mode-select"} onChange={(_, val) => setMode(val)}
value={mode}>
{bond_mode_choices.map(choice => <FormSelectOption value={choice.choice} label={choice.title} key={choice.choice} />)}
</FormSelect>
</FormGroup>
{modes_with_primary.includes(mode) && <FormGroup fieldId={idPrefix + "-primary-select"} label={_("Primary")}>
<FormSelect id={idPrefix + "-primary-select"} onChange={(_, val) => setPrimary(val)}
value={primary}>
<>
<FormSelectOption key='-' value='' label='-' />
{Object.keys(memberChoices)
.filter(iface => memberChoices[iface])
.map(iface => <FormSelectOption key={iface} label={iface} value={iface} />)}
</>
</FormSelect>
</FormGroup>}
<FormGroup fieldId={idPrefix + "-link-monitoring-select"} label={_("Link monitoring")}>
<FormSelect id={idPrefix + "-link-monitoring-select"} onChange={(_, val) => setLinkMonitoring(val)}
value={linkMonitoring}>
{bond_monitoring_choices.map(choice => <FormSelectOption value={choice.choice} label={choice.title} key={choice.choice} />)}
</FormSelect>
</FormGroup>
<FormGroup fieldId={idPrefix + "-link-monitoring-interval-input"} label={_("Monitoring interval")}>
<TextInput id={idPrefix + "-link-monitoring-interval-input"} className="network-number-field" value={linkMonitoringInterval} onChange={(_event, value) => setLinkMonitoringInterval(value)} />
</FormGroup>
{linkMonitoring === 'mii' && <>
<FormGroup fieldId={idPrefix + "-link-up-delay-input"} label={_("Link up delay")}>
<TextInput id={idPrefix + "-link-up-delay-input"} className="network-number-field" value={linkUpDelay} onChange={(_event, value) => setLinkUpDelay(value)} />
</FormGroup>
<FormGroup fieldId={idPrefix + "-link-down-delay-input"} label={_("Link down delay")}>
<TextInput id={idPrefix + "-link-down-delay-input"} className="network-number-field" value={linkDownDelay} onChange={(_event, value) => setLinkDownDelay(value)} />
</FormGroup>
</>}
{linkMonitoring === 'arp' && <FormGroup fieldId={idPrefix + "-monitoring-targets-input"} label={_("Monitoring targets")}>
<TextInput id={idPrefix + "-monitoring-targets-input"} value={monitoringTargets} onChange={(_event, value) => setMonitoringTargets(value)} />
</FormGroup>}
</>
</NetworkModal>
);
};
export const getGhostSettings = ({ newIfaceName }) => {
return (
{
connection: {
id: newIfaceName,
autoconnect: true,
type: "bond",
uuid: uuidv4(),
interface_name: newIfaceName,
},
bond: {
options: {
mode: "active-backup"
},
interface_name: newIfaceName
}
}
);
};