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

/*
 * Copyright (C) 2021 Red Hat, Inc.
 * SPDX-License-Identifier: LGPL-2.1-or-later
 */
import React, { useState } from 'react';
import { Alert, AlertActionCloseButton, AlertActionLink } from "@patternfly/react-core/dist/esm/components/Alert/index.js";
import { Button } from "@patternfly/react-core/dist/esm/components/Button/index.js";
import { Toolbar, ToolbarContent, ToolbarGroup, ToolbarItem } from "@patternfly/react-core/dist/esm/components/Toolbar/index.js";
import { PageSection } from "@patternfly/react-core/dist/esm/components/Page/index.js";

import { SimpleSelect } from "cockpit-components-simple-select";

import cockpit from 'cockpit';

const _ = cockpit.gettext;
const firewalld = cockpit.dbus('org.fedoraproject.FirewallD1', { superuser: "try" });

function debug() {
    if (window.debugging === "all" || window.debugging?.includes("firewall"))
        console.debug.apply(console, arguments);
}

/* React component for an info alert to enable some new service in firewalld.
 * Use this when enabling some network-facing service. The alert will only be shown
 * if firewalld is running, has at least one active zone, and the service is not enabled
 * in any zone yet. It will allow the user to enable the service in any active zone,
 * or go to the firewall page  for more fine-grained configuration.
 *
 * Properties:
 *   - service (string, required): firewalld service name
 *   - title (string, required): Human readable/translated alert title
 *   - pageSection (bool, optional, default false): Render the alert inside a <PageSection>
 */
export const FirewalldRequest = ({ service, title, pageSection }) => {
    const [zones, setZones] = useState(null);
    const [selectedZone, setSelectedZone] = useState(null);
    const [enabledAnywhere, setEnabledAnywhere] = useState(null);
    const [enableError, setEnableError] = useState(null);
    debug("FirewalldRequest", service, "zones", JSON.stringify(zones), "selected zone", selectedZone, "enabledAnywhere", enabledAnywhere);

    if (!service)
        return null;

    // query zones on component initialization
    if (zones === null) {
        firewalld.call("/org/fedoraproject/FirewallD1", "org.fedoraproject.FirewallD1.zone", "getActiveZones")
                .then(([info]) => {
                    const names = Object.keys(info);
                    Promise.all(names.map(name => firewalld.call("/org/fedoraproject/FirewallD1", "org.fedoraproject.FirewallD1.zone", "getZoneSettings2", [name])))
                            .then(zoneInfos => {
                                setEnabledAnywhere(!!zoneInfos.find(zoneInfo => ((zoneInfo[0].services || {}).v || []).indexOf(service) >= 0));
                                setZones(names);
                            })
                            .catch(ex => {
                                console.warn("FirewalldRequest: getZoneSettings failed:", JSON.stringify(ex));
                                setZones([]);
                            });

                    firewalld.call("/org/fedoraproject/FirewallD1", "org.fedoraproject.FirewallD1", "getDefaultZone")
                            .then(([zone]) => setSelectedZone(zone))
                            .catch(ex => console.warn("FirewalldRequest: getDefaultZone failed:", JSON.stringify(ex)));
                })
                .catch(ex => {
                    // firewalld not running
                    debug("FirewalldRequest: getActiveZones failed, considering firewall inactive:", JSON.stringify(ex));
                    setZones([]);
                });
    }

    const onAddService = () => {
        firewalld.call("/org/fedoraproject/FirewallD1", "org.fedoraproject.FirewallD1.zone", "addService",
                       [selectedZone, service, 0])
                // permanent config
                .then(() => firewalld.call("/org/fedoraproject/FirewallD1/config",
                                           "org.fedoraproject.FirewallD1.config",
                                           "getZoneByName", [selectedZone]))
                .then(([path]) => firewalld.call(path, "org.fedoraproject.FirewallD1.config.zone", "addService", [service]))
                // all successful, hide alert
                .then(() => setEnabledAnywhere(true))
                .catch(ex => {
                    // may already be enabled in permanent config, that's ok
                    if (ex.message && ex.message.indexOf("ALREADY_ENABLED") >= 0) {
                        setEnabledAnywhere(true);
                        return;
                    }

                    setEnableError(ex.toString());
                    setEnabledAnywhere(true);
                    console.error("Failed to enable", service, "in firewalld:", JSON.stringify(ex));
                });
    };

    let alert;

    if (enableError) {
        alert = (
            <Alert isInline variant="warning"
                   title={ cockpit.format(_("Failed to enable $0 in firewalld"), service) }
                   actionClose={ <AlertActionCloseButton onClose={ () => setEnableError(null) } /> }
                   actionLinks={
                       <AlertActionLink onClick={() => cockpit.jump("/network/firewall")}>
                           { _("Visit firewall") }
                       </AlertActionLink>
                   }>
                {enableError}
            </Alert>
        );
    // don't show anything if firewalld is not active, or service is already enabled somewhere
    } else if (!zones || zones.length === 0 || !selectedZone || enabledAnywhere) {
        return null;
    } else {
        alert = (
            <Alert id="firewalld-request-alert" isInline variant="info" title={title} className="pf-v6-u-box-shadow-sm">
                <Toolbar>
                    <ToolbarContent>
                        <ToolbarGroup>
                            <ToolbarItem variant="label">{ _("Zone") }</ToolbarItem>
                            <ToolbarItem>
                                <SimpleSelect
                                    onSelect={setSelectedZone}
                                    selected={selectedZone}
                                    options={zones.map(zone => ({ value: zone, content: zone }))}
                                    toggleProps={{
                                        id: "firewalld-request-" + service,
                                        "aria-label": _("Zone"),
                                    }} />
                            </ToolbarItem>

                            <ToolbarItem>
                                <Button variant="primary" onClick={onAddService}>{ cockpit.format(_("Add $0"), service) }</Button>
                            </ToolbarItem>
                        </ToolbarGroup>

                        <ToolbarItem variant="separator" />

                        <ToolbarItem>
                            <Button variant="link" onClick={() => cockpit.jump("/network/firewall")}>
                                { _("Visit firewall") }
                            </Button>
                        </ToolbarItem>
                    </ToolbarContent>
                </Toolbar>
            </Alert>
        );
    }

    if (pageSection)
        return <PageSection hasBodyWrapper={false} className="ct-no-bottom-padding">{alert}</PageSection>;
    else
        return alert;
};