LCOV - code coverage report
Current view: top level - pkg/systemd - services-list.jsx Coverage Total Hit
Test: cockpit Lines: 100.0 % 112 112
Test Date: 2026-07-02 14:11:36

            Line data    Source code
       1              : /*
       2              :  * Copyright (C) 2010 Red Hat, Inc.
       3              :  * SPDX-License-Identifier: LGPL-2.1-or-later
       4              :  */
       5              : 
       6           30 : import React from "react";
       7              : 
       8              : import { Button } from "@patternfly/react-core/dist/esm/components/Button/index.js";
       9              : import { Flex, FlexItem } from "@patternfly/react-core/dist/esm/layouts/Flex/index.js";
      10              : import { Tooltip, TooltipPosition } from "@patternfly/react-core/dist/esm/components/Tooltip/index.js";
      11              : import { Badge } from "@patternfly/react-core/dist/esm/components/Badge/index.js";
      12              : import { ListingTable } from 'cockpit-components-table.jsx';
      13              : import { ExclamationCircleIcon, SearchIcon, ThumbtackIcon } from '@patternfly/react-icons';
      14              : 
      15              : import { EmptyStatePanel } from "cockpit-components-empty-state.jsx";
      16              : 
      17           30 : import cockpit from "cockpit";
      18              : 
      19           30 : const _ = cockpit.gettext;
      20              : 
      21           26 : export const ServicesList = ({ units, isTimer, onClearAllFilters }) => {
      22           26 :     let columns;
      23           25 :     if (!isTimer) {
      24           25 :         columns = [
      25           25 :             { title: _("Unit"), header: true },
      26           25 :             { title: _("State") },
      27           25 :         ];
      28            5 :     } else {
      29            6 :         columns = [
      30            6 :             { title: _("Unit"), header: true },
      31            6 :             { title: _("Trigger"), props: { width: 20 } },
      32            6 :             { title: _("State") },
      33            6 :         ];
      34            6 :     }
      35           26 :     return (
      36           26 :         <ListingTable aria-label={_("Systemd units")}
      37           26 :                       columns={columns}
      38            5 :                       gridBreakPoint={isTimer ? "grid-xl" : "grid-lg"}
      39           26 :                       showHeader={false}
      40           26 :                       id="services-list"
      41           26 :                       rows={ units.map(unit => getServicesRow({ key: unit[0], isTimer, shortId: unit[0], ...unit[1] })) }
      42           26 :                       emptyComponent={<EmptyStatePanel icon={SearchIcon}
      43           26 :                                                        paragraph={_("No results match the filter criteria. Clear all filters to show results.")}
      44           26 :                                                        action={<Button id="clear-all-filters" onClick={onClearAllFilters} isInline variant='link'>{_("Clear all filters")}</Button>}
      45           26 :                                                        title={_("No matching results")} /> }
      46           26 :                       className="services-list" />
      47              :     );
      48           26 : };
      49              : 
      50           26 : const getServicesRow = ({ Id, shortId, AutomaticStartup, UnitFileState, LoadState, HasFailed, IsPinned, CombinedState, LastTriggerTime, NextRunTime, Description, isTimer }) => {
      51           26 :     let displayName = shortId;
      52              :     // Remove ".service" from services as this is not necessary
      53           26 :     if (shortId.endsWith(".service"))
      54           25 :         displayName = shortId.substring(0, shortId.length - 8);
      55           26 :     const props = { displayName, Description };
      56              : 
      57           26 :     const enabled = UnitFileState && UnitFileState.includes("enabled");
      58           26 :     const disabled = UnitFileState && UnitFileState.includes("disabled");
      59           26 :     const isStatic = UnitFileState && UnitFileState == "static";
      60           26 :     const masked = LoadState && LoadState.includes("masked");
      61           26 :     let unitFileState;
      62           26 :     if (enabled || disabled)
      63           26 :         unitFileState = <Badge className="service-unit-file-state" isRead={!enabled}>{AutomaticStartup}</Badge>;
      64              :     else
      65           26 :         unitFileState = <span className="service-unit-file-state service-unit-file-state-non-badge">{AutomaticStartup}</span>;
      66           26 :     let tooltipMessage = "";
      67           26 :     if (enabled)
      68           26 :         tooltipMessage = _("Automatically starts");
      69           26 :     else if (disabled)
      70           25 :         tooltipMessage = _("Does not automatically start");
      71           26 :     else if (masked)
      72            3 :         tooltipMessage = _("Forbidden from running");
      73           26 :     else if (isStatic)
      74           25 :         tooltipMessage = _("Cannot be enabled");
      75              : 
      76           26 :     const columns = [
      77           26 :         {
      78           26 :             title: (
      79           26 :                 <div className='service-unit-first-column'>
      80           26 :                     <Flex spaceItems={{ default: 'spaceItemsSm' }} alignItems={{ default: 'alignItemsCenter' }}>
      81           26 :                         <Button className='service-unit-id'
      82           26 :                             isInline
      83           26 :                             component="a"
      84           20 :                             onClick={() => cockpit.location.go([shortId], cockpit.location.options)}
      85           26 :                             variant='link'>
      86           26 :                             {props.displayName}
      87           26 :                         </Button>
      88           26 :                         {IsPinned &&
      89            4 :                             <Tooltip content={_("Pinned unit")}>
      90            4 :                                 <ThumbtackIcon className='service-thumbtack-icon-color' />
      91            4 :                             </Tooltip>}
      92           26 :                     </Flex>
      93           26 :                     {props.Description != shortId && <div className='service-unit-description'>{props.Description}</div>}
      94           26 :                 </div>
      95              :             )
      96           26 :         },
      97           26 :         {
      98           26 :             title: (
      99           26 :                 <Flex
     100           26 :                     id={cockpit.format("$0-service-unit-state", Id)}
     101           26 :                     flexWrap={{ default: 'wrap', md: 'nowrap' }}
     102            5 :                     justifyContent={{ default: 'justifyContentFlexStart', [isTimer ? 'xl' : 'lg']: 'justifyContentFlexEnd' }}
     103           26 :                     className='service-unit-status-flex-container'>
     104            7 :                     <FlexItem className={"service-unit-status" + (HasFailed ? " service-unit-status-failed" : "")}>
     105            7 :                         {HasFailed && <ExclamationCircleIcon className='ct-exclamation-circle' />}
     106           26 :                         {CombinedState}
     107           26 :                     </FlexItem>
     108           26 :                     {tooltipMessage ? <Tooltip id="switch-unit-state" content={tooltipMessage} position={TooltipPosition.left}>{unitFileState}</Tooltip> : unitFileState}
     109           26 :                 </Flex>
     110              :             ),
     111           26 :             props: {
     112           26 :                 className: 'service-unit-second-column'
     113           26 :             }
     114           26 :         },
     115           26 :     ];
     116            6 :     if (isTimer) {
     117            6 :         columns.splice(
     118            6 :             1, 0,
     119            6 :             {
     120            6 :                 title: (
     121            6 :                     <div className="service-unit-triggers">
     122            6 :                         {NextRunTime && <div className="service-unit-next-trigger">{cockpit.format("Next run: $0", NextRunTime)}</div>}
     123            6 :                         {LastTriggerTime && <div className="service-unit-last-trigger">{cockpit.format("Last trigger: $0", LastTriggerTime)}</div>}
     124            6 :                     </div>
     125              :                 )
     126            6 :             }
     127            6 :         );
     128            6 :     }
     129              : 
     130           26 :     return {
     131           26 :         props: {
     132           26 :             'data-goto-unit': shortId,
     133           26 :             id: shortId,
     134           26 :             key: shortId,
     135            7 :             className: HasFailed ? "service-unit-failed" : "",
     136           26 :         },
     137           26 :         columns
     138           26 :     };
     139           26 : };
        

Generated by: LCOV version 2.0-1