🏡 index : quine-global-labs/mirror-cockpit-project-cockpit.git

/*
 * Copyright (C) 2021 Red Hat, Inc.
 * SPDX-License-Identifier: LGPL-2.1-or-later
 */
import cockpit from "cockpit";
import React, { useState, useContext } from "react";
import { Button } from "@patternfly/react-core/dist/esm/components/Button/index.js";
import { Card, CardHeader, CardTitle } from '@patternfly/react-core/dist/esm/components/Card/index.js';
import { Dropdown, DropdownItem, DropdownList } from '@patternfly/react-core/dist/esm/components/Dropdown/index.js';
import { MenuToggle } from "@patternfly/react-core/dist/esm/components/MenuToggle";
import { Switch } from "@patternfly/react-core/dist/esm/components/Switch/index.js";
import { MinusIcon } from '@patternfly/react-icons';

import { ListingTable } from "cockpit-components-table.jsx";
import { ModelContext } from './model-context.jsx';
import { useEvent } from "hooks";

import {
    connection_settings,
    device_state_text,
    free_member_connection,
    is_interesting_interface,
    set_member,
    show_unexpected_error,
    syn_click,
    with_checkpoint,
    is_managed,
} from './interfaces.js';
import { fmt_to_fragments } from 'utils.jsx';

const _ = cockpit.gettext;

export const NetworkInterfaceMembers = ({
    members,
    memberIfaces,
    interfaces,
    iface,
    usage_monitor,
    privileged
}) => {
    const model = useContext(ModelContext);
    const [isOpen, setIsOpen] = useState(false);
    useEvent(usage_monitor.grid, "notify");

    function renderMemberRows() {
        const rows = [];

        members.forEach(iface => {
            const member_con = iface.MainConnection;
            const dev = iface.Device;
            const isActive = (dev && dev.State === 100 && dev.Carrier === true);
            const onoff = (
                <Switch
                    aria-label={cockpit.format(_("Switch off $0"), iface.Name)}
                    isDisabled={!privileged}
                    isChecked={!!(dev && dev.ActiveConnection)}
                    onChange={(_event, val) => {
                        if (val) {
                            with_checkpoint(
                                model,
                                () => member_con.activate(dev).catch(show_unexpected_error),
                                {
                                    devices: dev ? [dev] : [],
                                    fail_text: fmt_to_fragments(_("Switching on $0 will break the connection to the server, and will make the administration UI unavailable."), <b>{iface.Name}</b>),
                                    anyway_text: cockpit.format(_("Switch on $0"), iface.Name)
                                });
                        } else if (dev) {
                            with_checkpoint(
                                model,
                                () => dev.disconnect().catch(show_unexpected_error),
                                {
                                    devices: [dev],
                                    fail_text: fmt_to_fragments(_("Switching off $0 will break the connection to the server, and will make the administration UI unavailable."), <b>{iface.Name}</b>),
                                    anyway_text: cockpit.format(_("Switch off $0"), iface.Name)
                                });
                        }
                    } } />
            );

            const row = ({
                columns: [
                    { title: (!dev || is_managed(dev)) ? <Button variant="link" isInline onClick={() => cockpit.location.go([iface.Name])}>{iface.Name}</Button> : iface.Name },
                    // Will add traffic info right after
                    {
                        title: (
                            <div className="btn-group">
                                {onoff}
                                {privileged && <Button icon={<MinusIcon />} variant="secondary"
                                    size="sm"
                                    onClick={syn_click(model, () => {
                                        with_checkpoint(
                                            model,
                                            () => free_member_connection(member_con).catch(show_unexpected_error),
                                            {
                                                devices: dev ? [dev] : [],
                                                fail_text: fmt_to_fragments(_("Removing $0 will break the connection to the server, and will make the administration UI unavailable."), <b>{iface.Name}</b>),
                                                anyway_text: cockpit.format(_("Remove $0"), iface.Name),
                                                hack_does_add_or_remove: true
                                            });
                                        return false;
                                    })} />}
                            </div>
                        ),
                        props: { className: "pf-v6-c-table__action" }
                    },
                ],
                props: {
                    key: iface.Name,
                    "data-interface": encodeURIComponent(iface.Name),
                    "data-sample-id": isActive ? encodeURIComponent(iface.Name) : null,
                    "data-row-id": iface.Name,
                }
            });

            if (isActive) {
                const samples = usage_monitor.samples[iface.Name];
                row.columns.splice(1, 0, { title: samples ? cockpit.format_bits_per_sec(samples[1][0] * 8) : "" });
                row.columns.splice(2, 0, { title: samples ? cockpit.format_bits_per_sec(samples[0][0] * 8) : "" });
            } else {
                row.columns.splice(1, 0, { title: device_state_text(dev) });
                row.columns.splice(1, 0, { title: "" });
            }

            rows.push(row);
        });
        return rows;
    }

    const main_connection = iface.MainConnection;
    const cs = iface.MainConnection && connection_settings(iface.MainConnection);
    const dropdownItems = [];

    interfaces.forEach(i => {
        if (is_interesting_interface(i) && !memberIfaces[i.Name] && i !== iface) {
            const onClick = () => {
                with_checkpoint(
                    model,
                    () => {
                        return set_member(model, main_connection, main_connection.Settings,
                                          cs.type, i.Name, true)
                                .catch(show_unexpected_error);
                    },
                    {
                        devices: i.Device ? [i.Device] : [],
                        fail_text: fmt_to_fragments(_("Adding $0 will break the connection to the server, and will make the administration UI unavailable."), <b>{i.Name}</b>),
                        anyway_text: cockpit.format(_("Add $0"), i.Name),
                        hack_does_add_or_remove: true
                    }
                );
            };

            dropdownItems.push(
                <DropdownItem onClick={syn_click(model, onClick)}
                              key={"add-member-" + i.Name}
                              component="button">
                    {i.Name}
                </DropdownItem>
            );
        }
    });

    // We want to show a Tooltip but that isn't supported by PatternFly.
    // https://github.com/patternfly/patternfly-react/issues/12500
    if (dropdownItems.length === 0) {
        dropdownItems.push(
            <DropdownItem isDisabled isAriaDisabled>
                {_("No interfaces available")}
            </DropdownItem>
        );
    }

    const add_btn = (
        <Dropdown onSelect={() => setIsOpen(false)}
                  toggle={(toggleRef) => (
                      <MenuToggle id="add-member" ref={toggleRef} onClick={() => setIsOpen(!isOpen)}>
                          {_("Add member")}
                      </MenuToggle>
                  )}
                  isOpen={isOpen}
                  popperProps={{ position: "right" }}
        >
            <DropdownList>
                {dropdownItems}
            </DropdownList>
        </Dropdown>
    );

    return (
        <Card isPlain id="network-interface-members" className="network-interface-members">
            <CardHeader actions={{ actions: add_btn }}>
                <CardTitle component="h2">{_("Interface members")}</CardTitle>
            </CardHeader>
            <ListingTable aria-label={_("Interface members")}
                          className="networking-interface-members"
                          variant='compact'
                          columns={[
                              { title: (cs && cs.type === "bond") ? _("Interfaces") : _("Ports"), props: { width: 25 } },
                              { title: _("Sending"), props: { width: 25 } },
                              { title: _("Receiving"), props: { width: 25 } },
                              { title: "" },
                          ]}
                          rows={renderMemberRows()} />
        </Card>
    );
};