* Copyright (C) 2019 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 { Card, CardBody, CardHeader, CardTitle } from "@patternfly/react-core/dist/esm/components/Card/index.js";
import { DataList, DataListCell, DataListItem, DataListItemCells, DataListItemRow } from "@patternfly/react-core/dist/esm/components/DataList/index.js";
import {
Modal, ModalBody, ModalFooter, ModalHeader
} from '@patternfly/react-core/dist/esm/components/Modal/index.js';
import { Tab, Tabs } from "@patternfly/react-core/dist/esm/components/Tabs/index.js";
import { TextArea } from "@patternfly/react-core/dist/esm/components/TextArea/index.js";
import { Icon } from "@patternfly/react-core/dist/esm/components/Icon/index.js";
import { CheckIcon, CopyIcon, ExternalLinkAltIcon, OutlinedQuestionCircleIcon } from '@patternfly/react-icons';
import cockpit from "cockpit";
import 'cockpit-components-modifications.css';
const _ = cockpit.gettext;
*
* Enables showing shell and ansible script. Shell one is mandatory and ansible one can be omitted.
*
*/
export const ModificationsExportDialog = ({ onClose, shell, ansible }: {
onClose: () => void;
shell: string;
ansible?: string | undefined;
}) => {
const [active_tab, setActiveTab] = React.useState<string | number>(ansible ? "ansible" : "shell");
const [copied, setCopied] = React.useState(false);
const [timeoutId, setTimeoutId] = React.useState<number | null>(null);
const handleSelect = (_event: React.MouseEvent, active_tab: string | number) => {
setCopied(false);
setActiveTab(active_tab);
if (timeoutId !== null) {
clearTimeout(timeoutId);
setTimeoutId(null);
}
};
const copyToClipboard = () => {
try {
navigator.clipboard.writeText((active_tab === "ansible" ? ansible! : shell).trim())
.then(() => {
setCopied(true);
setTimeoutId(setTimeout(() => {
setCopied(false);
setTimeoutId(null);
}, 3000));
})
.catch(e => console.error('Text could not be copied: ', e ? e.toString() : ""));
} catch (error) {
console.error('Text could not be copied: ', String(error));
}
};
const footer = (
<ModalFooter>
<Button variant='secondary' className="btn-clipboard" onClick={copyToClipboard} icon={copied ? <Icon status="success"><CheckIcon /></Icon> : <CopyIcon />}>
{ _("Copy to clipboard") }
</Button>
<Button variant='secondary' className='btn-cancel' onClick={onClose}>
{ _("Close") }
</Button>
</ModalFooter>
);
return (
<Modal isOpen className="automation-script-modal"
position="top" variant="medium"
onClose={onClose}>
<ModalHeader title={_("Automation script") } />
<ModalBody>
<Tabs activeKey={active_tab} onSelect={handleSelect}>
{ ansible
? <Tab eventKey="ansible" title={_("Ansible")}>
<TextArea resizeOrientation='vertical' readOnlyVariant="default" defaultValue={ansible.trim()} />
<div className="ansible-docs-link">
<OutlinedQuestionCircleIcon />
{ _("Create new task file with this content.") }
<Button variant="link" component="a" href="https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_reuse_roles.html"
target="_blank" rel="noopener noreferrer"
icon={<ExternalLinkAltIcon />}>
{ _("Ansible roles documentation") }
</Button>
</div>
</Tab>
: undefined
}
<Tab eventKey="shell" title={_("Shell script")}>
<TextArea resizeOrientation='vertical' readOnlyVariant="default" defaultValue={shell.trim()} />
</Tab>
</Tabs>
</ModalBody>
{footer}
</Modal>
);
};
*
* Also show `View automation script` button which opens dialog in which different
* scripts are available. With these scripts it is possible to apply the same
* configurations to other machines.
*
* Pass array `entries` to show human readable messages.
* Pass string `shell` and `ansible` with scripts.
*
*/
export const Modifications = ({ entries, failed, permitted, title, shell, ansible }: {
entries: string[] | null;
failed?: string;
permitted: boolean;
title: string;
shell: string;
ansible?: string;
}) => {
const [showDialog, setShowDialog] = React.useState(false);
let emptyRow = null;
let fail_message = permitted ? _("No system modifications") : _("The logged in user is not permitted to view system modifications");
fail_message = failed || fail_message;
if (entries === null) {
emptyRow = <DataListItem>
<DataListItemRow>
<DataListItemCells dataListCells={[<DataListCell key="loading">{_("Loading system modifications...")}</DataListCell>]} />
</DataListItemRow>
</DataListItem>;
}
if (entries?.length === 0) {
emptyRow = <DataListItem>
<DataListItemRow>
<DataListItemCells dataListCells={[<DataListCell key={fail_message}>{fail_message}</DataListCell>]} />
</DataListItemRow>
</DataListItem>;
}
return (
<>
{ showDialog &&
<ModificationsExportDialog shell={shell} ansible={ansible}
onClose={() => setShowDialog(false)} />
}
<Card isPlain className="modifications-table">
<CardHeader>
<CardTitle component="h2">{title}</CardTitle>
{ !emptyRow &&
<Button variant="secondary" onClick={() => setShowDialog(true)}>
{_("View automation script")}
</Button>
}
</CardHeader>
<CardBody className="contains-list">
<DataList aria-label={title} isCompact>
{ emptyRow ||
entries?.map(entry => <DataListItem key={entry}>
<DataListItemRow>
<DataListItemCells dataListCells={[<DataListCell key={entry}>{entry}</DataListCell>]} />
</DataListItemRow>
</DataListItem>
)
}
</DataList>
</CardBody>
</Card>
</>
);
};