LCOV - code coverage report
Current view: top level - pkg/systemd - service.jsx Coverage Total Hit
Test: cockpit Lines: 99.1 % 109 108
Test Date: 2026-07-17 07:33:01

            Line data    Source code
       1              : /*
       2              :  * Copyright (C) 2020 Red Hat, Inc.
       3              :  * SPDX-License-Identifier: LGPL-2.1-or-later
       4              :  */
       5              : 
       6           33 : import React, { useRef, useState } from "react";
       7              : import { Breadcrumb, BreadcrumbItem } from "@patternfly/react-core/dist/esm/components/Breadcrumb/index.js";
       8              : import { PageBreadcrumb, PageSection } from "@patternfly/react-core/dist/esm/components/Page/index.js";
       9              : import { Stack } from "@patternfly/react-core/dist/esm/layouts/Stack/index.js";
      10              : import { ExclamationCircleIcon } from '@patternfly/react-icons';
      11              : 
      12              : import { EmptyStatePanel } from "cockpit-components-empty-state.jsx";
      13              : import { ServiceDetails } from "./service-details.jsx";
      14              : import { LogsPanel } from "cockpit-components-logs-panel.jsx";
      15              : import { superuser } from 'superuser';
      16              : import { WithDialogs } from "dialogs.jsx";
      17              : 
      18           33 : import cockpit from "cockpit";
      19              : import { useObject } from "hooks";
      20              : 
      21              : import s_bus from "./busnames.js";
      22              : 
      23           33 : const _ = cockpit.gettext;
      24              : 
      25           24 : function debug() {
      26            1 :     if (window.debugging == "all" || window.debugging?.includes("service-details")) // not-covered: debugging
      27            1 :         console.debug.apply(console, arguments); // not-covered: debugging
      28           24 : }
      29              : 
      30           24 : export const Service = ({ dbusClient, owner, unitId, unitIsValid, addTimerProperties, pinnedUnits }) => {
      31           24 :     const _path = useRef(null);
      32           24 :     const [error, setError] = useState(null);
      33           24 :     const [reloading, setReloading] = useState(false);
      34           24 :     const [unitProps, setUnitProps] = useState(null);
      35              : 
      36           24 :     const updateProperties = () => {
      37           24 :         const path = _path.current?.path;
      38           24 :         const promises = [dbusClient.call(path, s_bus.I_PROPS, "GetAll", [s_bus.I_UNIT])];
      39              : 
      40           24 :         if (unitId.endsWith(".timer"))
      41            1 :             promises.push(dbusClient.call(path, s_bus.I_PROPS, "GetAll", [s_bus.I_TIMER]));
      42           21 :         else if (unitId.endsWith(".socket"))
      43            3 :             promises.push(dbusClient.call(path, s_bus.I_PROPS, "GetAll", [s_bus.I_SOCKET]));
      44              : 
      45           24 :         Promise.all(promises)
      46           24 :                 .then(replies => {
      47           24 :                     const unit_props = replies[0][0];
      48              : 
      49              :                     // unwrap variants
      50           24 :                     for (const key in unit_props)
      51           24 :                         unit_props[key] = unit_props[key].v;
      52              : 
      53           24 :                     if (unitId.endsWith(".timer"))
      54            1 :                         addTimerProperties(replies[1][0], unit_props);
      55           21 :                     else if (unitId.endsWith(".socket"))
      56            3 :                         unit_props.Listen = replies[1][0].Listen.v;
      57              : 
      58           24 :                     unit_props.path = path;
      59              : 
      60           24 :                     debug("Service detail", unitId, "updated properties:", JSON.stringify(unit_props));
      61           24 :                     setUnitProps(unit_props);
      62           24 :                     setError(null);
      63           24 :                 })
      64            0 :                 .catch(ex => setError(ex.toString())); // not-covered: unexpected OS error
      65           24 :     };
      66              : 
      67              :     // load object path and set up PropertiesChanged subscription whenever unitId changes
      68           24 :     useObject(
      69           24 :         () => {
      70           24 :             dbusClient.call(s_bus.O_MANAGER, s_bus.I_MANAGER, "LoadUnit", [unitId])
      71           24 :                     .then(([path]) => {
      72           24 :                         debug("Service detail", unitId, "loaded path", path);
      73              : 
      74            1 :                         _path.current?.propSub.remove();
      75           24 :                         const propSub = dbusClient.subscribe(
      76           15 :                             { path, interface: s_bus.I_PROPS, member: "PropertiesChanged" }, () => {
      77           15 :                                 debug("Service detail", unitId, "PropertiesChanged; path", path);
      78           15 :                                 updateProperties();
      79           15 :                             });
      80              : 
      81           24 :                         _path.current = { unitId, path, propSub };
      82              : 
      83              :                         // initial load
      84           24 :                         updateProperties();
      85           24 :                     })
      86            2 :                     .catch(ex => setError(ex.toString())); // not-covered: unexpected OS error
      87           24 :         },
      88           24 :         null, [unitId]);
      89              : 
      90           24 :     useObject(
      91           24 :         () => dbusClient.subscribe(
      92           24 :             { interface: s_bus.I_MANAGER, member: "Reloading" },
      93            8 :             (_, _i, _s, [is_reloading]) => {
      94            8 :                 debug("Service detail", unitId, "Reloading", is_reloading, "current unit", _path.current?.path);
      95            8 :                 setReloading(is_reloading);
      96            8 :                 if (!is_reloading && _path.current)
      97            8 :                     updateProperties();
      98            8 :             }),
      99           10 :         sub => sub.remove(), []);
     100              : 
     101              :     /* We need this *only* to pick up failed Conditions after attempting to start a service, as the unit immediately gets
     102              :     * unloaded again and we don't get a PropertiesChanged signal. */
     103           24 :     useObject(
     104           24 :         () => dbusClient.subscribe(
     105           14 :             { interface: s_bus.I_MANAGER, member: "JobRemoved" }, (_p, _i, _s, [_job_id, _job_path, unit, result]) => {
     106           14 :                 if (result === "done" && unitId === _path.current?.unitId) {
     107           14 :                     debug("Service detail", unitId, "JobRemoved", _path.current?.path);
     108           14 :                     updateProperties();
     109           14 :                 }
     110           14 :             }),
     111           10 :         sub => sub.remove(), []);
     112              : 
     113              :     // render
     114           24 :     if (error)
     115            3 :         return <EmptyStatePanel title={_("Loading unit failed")} icon={ExclamationCircleIcon} paragraph={error} />; // not-covered: unexpected OS error
     116              : 
     117           24 :     if (unitProps === null)
     118           24 :         return <EmptyStatePanel loading title={_("Loading...")} paragraph={unitId} />;
     119              : 
     120              :     // resolve Alias name to primary ID
     121           24 :     const cur_unit_id = unitProps.Id;
     122              : 
     123            2 :     const unit_type = owner == "system" ? "UNIT" : "USER_UNIT";
     124           24 :     const match = [
     125           24 :         "_SYSTEMD_" + unit_type + "=" + cur_unit_id, "+",
     126           24 :         "COREDUMP_" + unit_type + "=" + cur_unit_id, "+",
     127           24 :         unit_type + "=" + cur_unit_id,
     128           24 :     ];
     129            2 :     const service_type = owner == "system" ? "service" : "user-service";
     130           24 :     const url = "/system/logs/#/?prio=debug&" + service_type + "=" + cur_unit_id;
     131           24 :     const load_state = unitProps.LoadState;
     132              : 
     133           24 :     return (
     134           24 :         <WithDialogs>
     135           24 :             <PageBreadcrumb hasBodyWrapper={false} stickyOnBreakpoint={{ default: "top" }}>
     136           24 :                 <Breadcrumb>
     137           24 :                     <BreadcrumbItem to={"#" + cockpit.location.href.replace(/\/[^?]*/, '')}>{_("Services")}</BreadcrumbItem>
     138           24 :                     <BreadcrumbItem isActive>
     139           24 :                         {cur_unit_id}
     140           24 :                     </BreadcrumbItem>
     141           24 :                 </Breadcrumb>
     142           24 :             </PageBreadcrumb>
     143           24 :             <PageSection hasBodyWrapper={false} id="service-details">
     144           24 :                 <Stack hasGutter>
     145           24 :                     <ServiceDetails unit={unitProps}
     146           24 :                                     owner={owner}
     147           24 :                                     permitted={superuser.allowed}
     148           24 :                                     loadingUnits={reloading}
     149           24 :                                     isValid={unitIsValid}
     150           24 :                                     pinnedUnits={pinnedUnits} />
     151            8 :                     {(load_state === "loaded" || load_state === "masked") &&
     152           24 :                     <LogsPanel title={_("Service logs")} match={match} emptyMessage={_("No log entries")} max={10} goto_url={url} search_options={{ prio: "debug", [service_type]: cur_unit_id }} />}
     153           24 :                 </Stack>
     154           24 :             </PageSection>
     155           24 :         </WithDialogs>
     156              :     );
     157           24 : };
        

Generated by: LCOV version 2.0-1