Files
awx/awx/ui_next/src/screens/Job/JobOutput/HostEventModal.jsx
2019-09-11 11:48:17 -04:00

255 lines
6.5 KiB
JavaScript

import React, { useEffect, useState } from 'react';
import {
Button,
Modal as PFModal,
Tab,
Tabs as PFTabs,
} from '@patternfly/react-core';
import CodeMirrorInput from '@components/CodeMirrorInput';
import ContentEmpty from '@components/ContentEmpty';
import PropTypes from 'prop-types';
import { DetailList, Detail } from '@components/DetailList';
import { StatusIcon } from '@components/Sparkline';
import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro';
import styled from 'styled-components';
import Entities from 'html-entities';
const entities = new Entities.AllHtmlEntities();
const Modal = styled(PFModal)`
--pf-c-modal-box__footer--MarginTop: 0;
align-self: flex-start;
margin-top: 200px;
.pf-c-modal-box__body {
overflow-y: hidden;
}
.pf-c-tab-content {
padding: 24px 0;
}
`;
const HostNameDetailValue = styled.div`
align-items: center;
display: inline-flex;
> div {
margin-right: 10px;
}
`;
const Tabs = styled(PFTabs)`
--pf-c-tabs__button--PaddingLeft: 20px;
--pf-c-tabs__button--PaddingRight: 20px;
.pf-c-tabs__list {
li:first-of-type .pf-c-tabs__button {
&::after {
margin-left: 0;
}
}
}
&:not(.pf-c-tabs__item)::before {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
content: '';
border-bottom: solid var(--pf-c-tabs__item--BorderColor);
border-width: var(--pf-c-tabs__item--BorderWidth) 0
var(--pf-c-tabs__item--BorderWidth) 0;
}
`;
const processEventStatus = event => {
let status = null;
if (event.event === 'runner_on_unreachable') {
status = 'unreachable';
}
// equiv to 'runner_on_error' && 'runner_on_failed'
if (event.failed) {
status = 'failed';
}
if (
event.event === 'runner_on_ok' ||
event.event === 'runner_on_async_ok' ||
event.event === 'runner_item_on_ok'
) {
status = 'ok';
}
// if 'ok' and 'changed' are both true, show 'changed'
if (event.changed) {
status = 'changed';
}
if (event.event === 'runner_on_skipped') {
status = 'skipped';
}
return status;
};
const processCodeMirrorValue = value => {
let codeMirrorValue;
if (value === undefined) {
codeMirrorValue = false;
} else if (value === '') {
codeMirrorValue = ' ';
} else if (typeof value === 'string') {
codeMirrorValue = entities.encode(value);
} else {
codeMirrorValue = value;
}
return codeMirrorValue;
};
const processStdOutValue = hostEvent => {
const { taskAction, res } = hostEvent.event_data;
let stdOut;
if (taskAction === 'debug' && res.result && res.result.stdout) {
stdOut = res.result.stdout;
} else if (
taskAction === 'yum' &&
res.results &&
Array.isArray(res.results)
) {
[stdOut] = res.results;
} else {
stdOut = res.stdout;
}
return stdOut;
};
function HostEventModal({ onClose, hostEvent = {}, isOpen = false, i18n }) {
const [hostStatus, setHostStatus] = useState(null);
const [activeTabKey, setActiveTabKey] = useState(0);
useEffect(() => {
setHostStatus(processEventStatus(hostEvent));
}, []);
const handleTabClick = (event, tabIndex) => {
setActiveTabKey(tabIndex);
};
const jsonObj = processCodeMirrorValue(hostEvent.event_data.res);
const stdErr = processCodeMirrorValue(hostEvent.event_data.res.stderr);
const stdOut = processCodeMirrorValue(processStdOutValue(hostEvent));
return (
<Modal
isLarge
isOpen={isOpen}
onClose={onClose}
title={i18n._(t`Host Details`)}
actions={[
<Button key="cancel" variant="secondary" onClick={onClose}>
{i18n._(t`Close`)}
</Button>,
]}
>
<Tabs
aria-label={i18n._(t`Tabs`)}
activeKey={activeTabKey}
onSelect={handleTabClick}
>
<Tab
aria-label={i18n._(t`Details tab`)}
eventKey={0}
title={i18n._(t`Details`)}
>
<DetailList style={{ alignItems: 'center' }} gutter="sm">
<Detail
label={i18n._(t`Host Name`)}
value={
<HostNameDetailValue>
{hostStatus ? <StatusIcon status={hostStatus} /> : null}
{hostEvent.host_name}
</HostNameDetailValue>
}
/>
<Detail label={i18n._(t`Play`)} value={hostEvent.play} />
<Detail label={i18n._(t`Task`)} value={hostEvent.task} />
<Detail
label={i18n._(t`Module`)}
value={
hostEvent.event_data.task_action || i18n._(t`No result found`)
}
/>
<Detail
label={i18n._(t`Command`)}
value={hostEvent.event_data.res.cmd}
/>
</DetailList>
</Tab>
<Tab
eventKey={1}
title={i18n._(t`JSON`)}
aria-label={i18n._(t`JSON tab`)}
>
{activeTabKey === 1 && jsonObj ? (
<CodeMirrorInput
mode="javascript"
readOnly
value={JSON.stringify(jsonObj, null, 2)}
onChange={() => {}}
rows={20}
hasErrors={false}
/>
) : (
<ContentEmpty title={i18n._(t`No JSON Available`)} />
)}
</Tab>
<Tab
eventKey={2}
title={i18n._(t`Standard Out`)}
aria-label={i18n._(t`Standard out tab`)}
>
{activeTabKey === 2 && stdOut ? (
<CodeMirrorInput
mode="javascript"
readOnly
value={stdOut}
onChange={() => {}}
rows={20}
hasErrors={false}
/>
) : (
<ContentEmpty title={i18n._(t`No Standard Out Available`)} />
)}
</Tab>
<Tab
eventKey={3}
title={i18n._(t`Standard Error`)}
aria-label={i18n._(t`Standard error tab`)}
>
{activeTabKey === 3 && stdErr ? (
<CodeMirrorInput
mode="javascript"
readOnly
onChange={() => {}}
value={stdErr}
hasErrors={false}
rows={20}
/>
) : (
<ContentEmpty title={i18n._(t`No Standard Error Available`)} />
)}
</Tab>
</Tabs>
</Modal>
);
}
export default withI18n()(HostEventModal);
HostEventModal.propTypes = {
onClose: PropTypes.func.isRequired,
hostEvent: PropTypes.shape({}),
isOpen: PropTypes.bool,
};
HostEventModal.defaultProps = {
hostEvent: null,
isOpen: false,
};