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-06-17 06:28:00

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

Generated by: LCOV version 2.0-1