diff --git a/awx/ui/client/features/projects/projectsList.controller.js b/awx/ui/client/features/projects/projectsList.controller.js index a295d6e52b..66605f8f13 100644 --- a/awx/ui/client/features/projects/projectsList.controller.js +++ b/awx/ui/client/features/projects/projectsList.controller.js @@ -353,7 +353,7 @@ function projectsListController ( }; function buildTooltips (project) { - project.statusIcon = getStatusIcon(project); + project.statusIcon = getJobStatusIcon(project); project.statusTip = getStatusTooltip(project); project.scm_update_tooltip = vm.strings.get('update.GET_LATEST'); project.scm_update_disabled = false; @@ -408,7 +408,7 @@ function projectsListController ( }; } - function getStatusIcon (project) { + function getJobStatusIcon (project) { let icon = 'none'; switch (project.status) { case 'n/a': diff --git a/awx/ui_next/src/components/Sparkline/JobStatusIcon.jsx b/awx/ui_next/src/components/Sparkline/JobStatusIcon.jsx new file mode 100644 index 0000000000..609a4939ed --- /dev/null +++ b/awx/ui_next/src/components/Sparkline/JobStatusIcon.jsx @@ -0,0 +1,128 @@ +import React, { Fragment } from 'react'; +import { node, object, string } from 'prop-types'; +import { Link } from 'react-router-dom'; +import styled, { keyframes } from 'styled-components'; +import { Tooltip } from '@patternfly/react-core'; + +const Pulse = keyframes` + from { + -webkit-transform:scale(1); + } + to { + -webkit-transform:scale(0); + } +`; + +const Wrapper = styled.div` + width: 14px; + height: 14px; +`; + +const RunningJob = styled(Wrapper)` + background-color: #5cb85c; + padding-right: 0px; + text-shadow: + -1px -1px 0 #ffffff, + 1px -1px 0 #ffffff, + -1px 1px 0 #ffffff, + 1px 1px 0 #ffffff; + animation: ${Pulse} 1.5s linear infinite alternate; +`; + +const WaitingJob = styled(Wrapper)` + border: 1px solid #d7d7d7; +`; + +const FinishedJob = styled(Wrapper)` + flex: 0 1 auto; + > * { + width: 14px; + height: 7px; + } +`; + +const SuccessfulTop = styled.div` + background-color: #5cb85c; +`; + +const SuccessfulBottom = styled.div` + border: 1px solid #b7b7b7; + border-top: 0; + background: #ffffff; +`; + +const FailedTop = styled.div` + border: 1px solid #b7b7b7; + border-bottom: 0; + background: #ffffff; +`; + +const FailedBottom = styled.div` + background-color: #D9534F; +`; + +const JobStatusIcon = ({ job, link, tooltip, ...props }) => { + let Icon = ( + + { job.status === 'running' && } + { (job.status === 'new' + || job.status === 'pending' + || job.status === 'waiting') + && + } + { (job.status === 'failed' + || job.status === 'error' + || job.status === 'canceled') + && ( + + + + + ) + } + { job.status === 'successful' && ( + + + + + )} + + ); + + if (link) { + Icon = ( + + {Icon} + + ); + } + + if (tooltip) { + return ( +
+ + {Icon} + +
+ ); + } + + return ( +
+ {Icon} +
+ ); +} + +JobStatusIcon.propTypes = { + job: object.isRequired, + link: string, + tooltip: node +}; + +JobStatusIcon.defaultProps = { + link: null, + tooltip: null, +}; + +export default JobStatusIcon; diff --git a/awx/ui_next/src/components/Sparkline/Sparkline.jsx b/awx/ui_next/src/components/Sparkline/Sparkline.jsx new file mode 100644 index 0000000000..4c9a97448f --- /dev/null +++ b/awx/ui_next/src/components/Sparkline/Sparkline.jsx @@ -0,0 +1,41 @@ +import React, { Fragment } from 'react'; +import { arrayOf, object } from 'prop-types'; +import { withI18n } from '@lingui/react'; +import { JobStatusIcon as _JobStatusIcon } from '@components/Sparkline'; +import styled from 'styled-components'; +import { t } from '@lingui/macro'; +import { JOB_TYPE_URL_SEGMENTS } from '../../constants'; + +const JobStatusIcon = styled(props => <_JobStatusIcon {...props} />)` + margin-right: 5px; +`; + +const Sparkline = ({ i18n, jobs }) => { + const generateTooltip = (job) => ( + +
{i18n._(t`JOB ID:`)} {job.id}
+
{i18n._(t`STATUS:`)} {job.status.toUpperCase()}
+
{i18n._(t`FINISHED:`)} {job.finished}
+
+ ); + + return ( + (jobs.map(job => ( + + ))) + ) +}; + +Sparkline.propTypes = { + jobs: arrayOf(object), +}; +Sparkline.defaultProps = { + jobs: [], +}; + +export default withI18n()(Sparkline); diff --git a/awx/ui_next/src/components/Sparkline/index.js b/awx/ui_next/src/components/Sparkline/index.js new file mode 100644 index 0000000000..6c37734e51 --- /dev/null +++ b/awx/ui_next/src/components/Sparkline/index.js @@ -0,0 +1,2 @@ +export { default as Sparkline } from './Sparkline'; +export { default as JobStatusIcon } from './JobStatusIcon'; \ No newline at end of file diff --git a/awx/ui_next/src/screens/Job/constants.js b/awx/ui_next/src/constants.js similarity index 100% rename from awx/ui_next/src/screens/Job/constants.js rename to awx/ui_next/src/constants.js diff --git a/awx/ui_next/src/screens/Job/Job.jsx b/awx/ui_next/src/screens/Job/Job.jsx index 0352a7b260..313f051cad 100644 --- a/awx/ui_next/src/screens/Job/Job.jsx +++ b/awx/ui_next/src/screens/Job/Job.jsx @@ -15,7 +15,7 @@ import RoutedTabs from '@components/RoutedTabs'; import JobDetail from './JobDetail'; import JobOutput from './JobOutput'; -import { JOB_TYPE_URL_SEGMENTS } from './constants'; +import { JOB_TYPE_URL_SEGMENTS } from '../../constants'; class Job extends Component { constructor(props) { diff --git a/awx/ui_next/src/screens/Job/JobList/JobListItem.jsx b/awx/ui_next/src/screens/Job/JobList/JobListItem.jsx index 7cfc4dd5ac..93c67e7070 100644 --- a/awx/ui_next/src/screens/Job/JobList/JobListItem.jsx +++ b/awx/ui_next/src/screens/Job/JobList/JobListItem.jsx @@ -9,7 +9,7 @@ import { import DataListCell from '@components/DataListCell'; import VerticalSeparator from '@components/VerticalSeparator'; import { toTitleCase } from '@util/strings'; -import { JOB_TYPE_URL_SEGMENTS } from '../constants'; +import { JOB_TYPE_URL_SEGMENTS } from '../../../constants'; class JobListItem extends Component { render() { diff --git a/awx/ui_next/src/screens/Job/JobTypeRedirect.jsx b/awx/ui_next/src/screens/Job/JobTypeRedirect.jsx index 87738c6ce8..20ba8b1bc0 100644 --- a/awx/ui_next/src/screens/Job/JobTypeRedirect.jsx +++ b/awx/ui_next/src/screens/Job/JobTypeRedirect.jsx @@ -1,7 +1,7 @@ import React, { Component } from 'react'; import { Redirect } from 'react-router-dom'; import { UnifiedJobsAPI } from '@api'; -import { JOB_TYPE_URL_SEGMENTS } from './constants'; +import { JOB_TYPE_URL_SEGMENTS } from '../../constants'; class JobTypeRedirect extends Component { static defaultProps = { diff --git a/awx/ui_next/src/screens/Job/Jobs.jsx b/awx/ui_next/src/screens/Job/Jobs.jsx index 9279a9f511..8c86472a10 100644 --- a/awx/ui_next/src/screens/Job/Jobs.jsx +++ b/awx/ui_next/src/screens/Job/Jobs.jsx @@ -6,7 +6,7 @@ import Breadcrumbs from '@components/Breadcrumbs/Breadcrumbs'; import Job from './Job'; import JobTypeRedirect from './JobTypeRedirect'; import JobList from './JobList/JobList'; -import { JOB_TYPE_URL_SEGMENTS } from './constants'; +import { JOB_TYPE_URL_SEGMENTS } from '../../constants'; class Jobs extends Component { constructor(props) { diff --git a/awx/ui_next/src/screens/Template/TemplateList/TemplateListItem.jsx b/awx/ui_next/src/screens/Template/TemplateList/TemplateListItem.jsx index bb31945c46..947a04a72e 100644 --- a/awx/ui_next/src/screens/Template/TemplateList/TemplateListItem.jsx +++ b/awx/ui_next/src/screens/Template/TemplateList/TemplateListItem.jsx @@ -16,6 +16,7 @@ import styled from 'styled-components'; import DataListCell from '@components/DataListCell'; import LaunchButton from '@components/LaunchButton'; import VerticalSeparator from '@components/VerticalSeparator'; +import { Sparkline } from '@components/Sparkline'; import { toTitleCase } from '@util/strings'; const StyledButton = styled(PFButton)` @@ -56,6 +57,11 @@ class TemplateListItem extends Component { {toTitleCase(template.type)} , + + + , {canLaunch && template.type === 'job_template' && (