* Copyright (C) 2010 Red Hat, Inc.
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
import React from "react";
import { Button } from "@patternfly/react-core/dist/esm/components/Button/index.js";
import { Flex, FlexItem } from "@patternfly/react-core/dist/esm/layouts/Flex/index.js";
import { Tooltip, TooltipPosition } from "@patternfly/react-core/dist/esm/components/Tooltip/index.js";
import { Badge } from "@patternfly/react-core/dist/esm/components/Badge/index.js";
import { ListingTable } from 'cockpit-components-table.jsx';
import { ExclamationCircleIcon, SearchIcon, ThumbtackIcon } from '@patternfly/react-icons';
import { EmptyStatePanel } from "cockpit-components-empty-state.jsx";
import cockpit from "cockpit";
const _ = cockpit.gettext;
export const ServicesList = ({ units, isTimer, onClearAllFilters }) => {
let columns;
if (!isTimer) {
columns = [
{ title: _("Unit"), header: true },
{ title: _("State") },
];
} else {
columns = [
{ title: _("Unit"), header: true },
{ title: _("Trigger"), props: { width: 20 } },
{ title: _("State") },
];
}
return (
<ListingTable aria-label={_("Systemd units")}
columns={columns}
gridBreakPoint={isTimer ? "grid-xl" : "grid-lg"}
showHeader={false}
id="services-list"
rows={ units.map(unit => getServicesRow({ key: unit[0], isTimer, shortId: unit[0], ...unit[1] })) }
emptyComponent={<EmptyStatePanel icon={SearchIcon}
paragraph={_("No results match the filter criteria. Clear all filters to show results.")}
action={<Button id="clear-all-filters" onClick={onClearAllFilters} isInline variant='link'>{_("Clear all filters")}</Button>}
title={_("No matching results")} /> }
className="services-list" />
);
};
const getServicesRow = ({ Id, shortId, AutomaticStartup, UnitFileState, LoadState, HasFailed, IsPinned, CombinedState, LastTriggerTime, NextRunTime, Description, isTimer }) => {
let displayName = shortId;
if (shortId.endsWith(".service"))
displayName = shortId.substring(0, shortId.length - 8);
const props = { displayName, Description };
const enabled = UnitFileState && UnitFileState.includes("enabled");
const disabled = UnitFileState && UnitFileState.includes("disabled");
const isStatic = UnitFileState && UnitFileState === "static";
const masked = LoadState && LoadState.includes("masked");
let unitFileState;
if (enabled || disabled)
unitFileState = <Badge className="service-unit-file-state" isRead={!enabled}>{AutomaticStartup}</Badge>;
else
unitFileState = <span className="service-unit-file-state service-unit-file-state-non-badge">{AutomaticStartup}</span>;
let tooltipMessage = "";
if (enabled)
tooltipMessage = _("Automatically starts");
else if (disabled)
tooltipMessage = _("Does not automatically start");
else if (masked)
tooltipMessage = _("Forbidden from running");
else if (isStatic)
tooltipMessage = _("Cannot be enabled");
const columns = [
{
title: (
<div className='service-unit-first-column'>
<Flex spaceItems={{ default: 'spaceItemsSm' }} alignItems={{ default: 'alignItemsCenter' }}>
<Button className='service-unit-id'
isInline
component="a"
onClick={() => cockpit.location.go([shortId], cockpit.location.options)}
variant='link'>
{props.displayName}
</Button>
{IsPinned &&
<Tooltip content={_("Pinned unit")}>
<ThumbtackIcon className='service-thumbtack-icon-color' />
</Tooltip>}
</Flex>
{props.Description !== shortId && <div className='service-unit-description'>{props.Description}</div>}
</div>
)
},
{
title: (
<Flex
id={cockpit.format("$0-service-unit-state", Id)}
flexWrap={{ default: 'wrap', md: 'nowrap' }}
justifyContent={{ default: 'justifyContentFlexStart', [isTimer ? 'xl' : 'lg']: 'justifyContentFlexEnd' }}
className='service-unit-status-flex-container'>
<FlexItem className={"service-unit-status" + (HasFailed ? " service-unit-status-failed" : "")}>
{HasFailed && <ExclamationCircleIcon className='ct-exclamation-circle' />}
{CombinedState}
</FlexItem>
{tooltipMessage ? <Tooltip id="switch-unit-state" content={tooltipMessage} position={TooltipPosition.left}>{unitFileState}</Tooltip> : unitFileState}
</Flex>
),
props: {
className: 'service-unit-second-column'
}
},
];
if (isTimer) {
columns.splice(
1, 0,
{
title: (
<div className="service-unit-triggers">
{NextRunTime && <div className="service-unit-next-trigger">{cockpit.format("Next run: $0", NextRunTime)}</div>}
{LastTriggerTime && <div className="service-unit-last-trigger">{cockpit.format("Last trigger: $0", LastTriggerTime)}</div>}
</div>
)
}
);
}
return {
props: {
'data-goto-unit': shortId,
id: shortId,
key: shortId,
className: HasFailed ? "service-unit-failed" : "",
},
columns
};
};