Line data Source code
1 : /*
2 : * Copyright (C) 2010 Red Hat, Inc.
3 : * SPDX-License-Identifier: LGPL-2.1-or-later
4 : */
5 :
6 33 : 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 33 : import cockpit from "cockpit";
18 :
19 33 : 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 8 : <FlexItem className={"service-unit-status" + (HasFailed ? " service-unit-status-failed" : "")}>
105 8 : {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 8 : className: HasFailed ? "service-unit-failed" : "",
136 26 : },
137 26 : columns
138 26 : };
139 26 : };
|