LCOV - code coverage report
Current view: top level - pkg/lib - cockpit-components-firewalld-request.jsx Coverage Total Hit
Test: cockpit Lines: 89.5 % 105 94
Test Date: 2026-07-17 14:32:59

            Line data    Source code
       1              : /*
       2              :  * Copyright (C) 2021 Red Hat, Inc.
       3              :  * SPDX-License-Identifier: LGPL-2.1-or-later
       4              :  */
       5           11 : import React, { useState } from 'react';
       6              : import { Alert, AlertActionCloseButton, AlertActionLink } from "@patternfly/react-core/dist/esm/components/Alert/index.js";
       7              : import { Button } from "@patternfly/react-core/dist/esm/components/Button/index.js";
       8              : import { Toolbar, ToolbarContent, ToolbarGroup, ToolbarItem } from "@patternfly/react-core/dist/esm/components/Toolbar/index.js";
       9              : import { PageSection } from "@patternfly/react-core/dist/esm/components/Page/index.js";
      10              : 
      11              : import { SimpleSelect } from "cockpit-components-simple-select";
      12              : 
      13           11 : import cockpit from 'cockpit';
      14              : import './cockpit-components-firewalld-request.scss';
      15              : 
      16           11 : const _ = cockpit.gettext;
      17           11 : const firewalld = cockpit.dbus('org.fedoraproject.FirewallD1', { superuser: "try" });
      18              : 
      19            1 : function debug() {
      20            0 :     if (window.debugging == "all" || window.debugging?.includes("firewall"))
      21            0 :         console.debug.apply(console, arguments);
      22            1 : }
      23              : 
      24              : /* React component for an info alert to enable some new service in firewalld.
      25              :  * Use this when enabling some network-facing service. The alert will only be shown
      26              :  * if firewalld is running, has at least one active zone, and the service is not enabled
      27              :  * in any zone yet. It will allow the user to enable the service in any active zone,
      28              :  * or go to the firewall page  for more fine-grained configuration.
      29              :  *
      30              :  * Properties:
      31              :  *   - service (string, required): firewalld service name
      32              :  *   - title (string, required): Human readable/translated alert title
      33              :  *   - pageSection (bool, optional, default false): Render the alert inside a <PageSection>
      34              :  */
      35            1 : export const FirewalldRequest = ({ service, title, pageSection }) => {
      36            1 :     const [zones, setZones] = useState(null);
      37            1 :     const [selectedZone, setSelectedZone] = useState(null);
      38            1 :     const [enabledAnywhere, setEnabledAnywhere] = useState(null);
      39            1 :     const [enableError, setEnableError] = useState(null);
      40            1 :     debug("FirewalldRequest", service, "zones", JSON.stringify(zones), "selected zone", selectedZone, "enabledAnywhere", enabledAnywhere);
      41              : 
      42            1 :     if (!service)
      43            0 :         return null;
      44              : 
      45              :     // query zones on component initialization
      46            1 :     if (zones === null) {
      47            1 :         firewalld.call("/org/fedoraproject/FirewallD1", "org.fedoraproject.FirewallD1.zone", "getActiveZones")
      48            1 :                 .then(([info]) => {
      49            1 :                     const names = Object.keys(info);
      50            1 :                     Promise.all(names.map(name => firewalld.call("/org/fedoraproject/FirewallD1", "org.fedoraproject.FirewallD1.zone", "getZoneSettings2", [name])))
      51            1 :                             .then(zoneInfos => {
      52            1 :                                 setEnabledAnywhere(!!zoneInfos.find(zoneInfo => ((zoneInfo[0].services || {}).v || []).indexOf(service) >= 0));
      53            1 :                                 setZones(names);
      54            1 :                             })
      55            0 :                             .catch(ex => {
      56            0 :                                 console.warn("FirewalldRequest: getZoneSettings failed:", JSON.stringify(ex));
      57            0 :                                 setZones([]);
      58            0 :                             });
      59              : 
      60            1 :                     firewalld.call("/org/fedoraproject/FirewallD1", "org.fedoraproject.FirewallD1", "getDefaultZone")
      61            1 :                             .then(([zone]) => setSelectedZone(zone))
      62            0 :                             .catch(ex => console.warn("FirewalldRequest: getDefaultZone failed:", JSON.stringify(ex)));
      63            1 :                 })
      64            1 :                 .catch(ex => {
      65              :                     // firewalld not running
      66            1 :                     debug("FirewalldRequest: getActiveZones failed, considering firewall inactive:", JSON.stringify(ex));
      67            1 :                     setZones([]);
      68            1 :                 });
      69            1 :     }
      70              : 
      71            1 :     const onAddService = () => {
      72            1 :         firewalld.call("/org/fedoraproject/FirewallD1", "org.fedoraproject.FirewallD1.zone", "addService",
      73            1 :                        [selectedZone, service, 0])
      74              :                 // permanent config
      75            1 :                 .then(() => firewalld.call("/org/fedoraproject/FirewallD1/config",
      76            1 :                                            "org.fedoraproject.FirewallD1.config",
      77            1 :                                            "getZoneByName", [selectedZone]))
      78            1 :                 .then(([path]) => firewalld.call(path, "org.fedoraproject.FirewallD1.config.zone", "addService", [service]))
      79              :                 // all successful, hide alert
      80            1 :                 .then(() => setEnabledAnywhere(true))
      81            1 :                 .catch(ex => {
      82              :                     // may already be enabled in permanent config, that's ok
      83            1 :                     if (ex.message && ex.message.indexOf("ALREADY_ENABLED") >= 0) {
      84            1 :                         setEnabledAnywhere(true);
      85            1 :                         return;
      86            1 :                     }
      87              : 
      88            1 :                     setEnableError(ex.toString());
      89            1 :                     setEnabledAnywhere(true);
      90            1 :                     console.error("Failed to enable", service, "in firewalld:", JSON.stringify(ex));
      91            1 :                 });
      92            1 :     };
      93              : 
      94            1 :     let alert;
      95              : 
      96            1 :     if (enableError) {
      97            1 :         alert = (
      98            1 :             <Alert isInline variant="warning"
      99            1 :                    title={ cockpit.format(_("Failed to enable $0 in firewalld"), service) }
     100            1 :                    actionClose={ <AlertActionCloseButton onClose={ () => setEnableError(null) } /> }
     101            1 :                    actionLinks={
     102            0 :                        <AlertActionLink onClick={() => cockpit.jump("/network/firewall")}>
     103            1 :                            { _("Visit firewall") }
     104            1 :                        </AlertActionLink>
     105              :                    }>
     106            1 :                 {enableError}
     107            1 :             </Alert>
     108              :         );
     109              :     // don't show anything if firewalld is not active, or service is already enabled somewhere
     110            1 :     } else if (!zones || zones.length === 0 || !selectedZone || enabledAnywhere) {
     111            1 :         return null;
     112            1 :     } else {
     113            1 :         alert = (
     114            1 :             <Alert id="firewalld-request-alert" isInline variant="info" title={title} className="pf-v6-u-box-shadow-sm">
     115            1 :                 <Toolbar>
     116            1 :                     <ToolbarContent>
     117            1 :                         <ToolbarGroup>
     118            1 :                             <ToolbarItem variant="label">{ _("Zone") }</ToolbarItem>
     119            1 :                             <ToolbarItem>
     120            1 :                                 <SimpleSelect
     121            1 :                                     onSelect={setSelectedZone}
     122            1 :                                     selected={selectedZone}
     123            1 :                                     options={zones.map(zone => ({ value: zone, content: zone }))}
     124            1 :                                     toggleProps={{
     125            1 :                                         id: "firewalld-request-" + service,
     126            1 :                                         "aria-label": _("Zone"),
     127            1 :                                     }} />
     128            1 :                             </ToolbarItem>
     129              : 
     130            1 :                             <ToolbarItem>
     131            1 :                                 <Button variant="primary" onClick={onAddService}>{ cockpit.format(_("Add $0"), service) }</Button>
     132            1 :                             </ToolbarItem>
     133            1 :                         </ToolbarGroup>
     134              : 
     135            1 :                         <ToolbarItem variant="separator" />
     136              : 
     137            1 :                         <ToolbarItem>
     138            1 :                             <Button variant="link" onClick={() => cockpit.jump("/network/firewall")}>
     139            1 :                                 { _("Visit firewall") }
     140            1 :                             </Button>
     141            1 :                         </ToolbarItem>
     142            1 :                     </ToolbarContent>
     143            1 :                 </Toolbar>
     144            1 :             </Alert>
     145              :         );
     146            1 :     }
     147              : 
     148            1 :     if (pageSection)
     149            0 :         return <PageSection hasBodyWrapper={false} className="ct-no-bottom-padding">{alert}</PageSection>;
     150              :     else
     151            0 :         return alert;
     152            1 : };
        

Generated by: LCOV version 2.0-1