* 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);
}
* 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;
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 => {
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])
.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]))
.then(() => setEnabledAnywhere(true))
.catch(ex => {
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>
);
} 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;
};