diff --git a/awx/ui_next/src/screens/Job/JobOutput/JobOutput.jsx b/awx/ui_next/src/screens/Job/JobOutput/JobOutput.jsx
index 88e32be49f..fbb2dcabd2 100644
--- a/awx/ui_next/src/screens/Job/JobOutput/JobOutput.jsx
+++ b/awx/ui_next/src/screens/Job/JobOutput/JobOutput.jsx
@@ -1,6 +1,6 @@
import React, { Component, Fragment } from 'react';
import { withRouter } from 'react-router-dom';
-import { I18n } from '@lingui/react';
+import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro';
import styled from 'styled-components';
import {
@@ -10,6 +10,7 @@ import {
InfiniteLoader,
List,
} from 'react-virtualized';
+import { Button } from '@patternfly/react-core';
import Ansi from 'ansi-to-html';
import hasAnsi from 'has-ansi';
import { AllHtmlEntities } from 'html-entities';
@@ -225,6 +226,7 @@ class JobOutput extends Component {
this.state = {
contentError: null,
deletionError: null,
+ cancelError: null,
hasContentLoading: true,
results: {},
currentlyLoading: [],
@@ -232,6 +234,9 @@ class JobOutput extends Component {
isHostModalOpen: false,
hostEvent: {},
cssMap: {},
+ jobStatus: props.job.status ?? 'waiting',
+ showCancelPrompt: false,
+ cancelInProgress: false,
};
this.cache = new CellMeasurerCache({
@@ -242,6 +247,9 @@ class JobOutput extends Component {
this._isMounted = false;
this.loadJobEvents = this.loadJobEvents.bind(this);
this.handleDeleteJob = this.handleDeleteJob.bind(this);
+ this.handleCancelOpen = this.handleCancelOpen.bind(this);
+ this.handleCancelConfirm = this.handleCancelConfirm.bind(this);
+ this.handleCancelClose = this.handleCancelClose.bind(this);
this.rowRenderer = this.rowRenderer.bind(this);
this.handleHostEventClick = this.handleHostEventClick.bind(this);
this.handleHostModalClose = this.handleHostModalClose.bind(this);
@@ -262,10 +270,18 @@ class JobOutput extends Component {
this.loadJobEvents();
connectJobSocket(job, data => {
- if (data.counter && data.counter > this.jobSocketCounter) {
- this.jobSocketCounter = data.counter;
- } else if (data.final_counter && data.unified_job_id === job.id) {
- this.jobSocketCounter = data.final_counter;
+ if (data.group_name === 'job_events') {
+ if (data.counter && data.counter > this.jobSocketCounter) {
+ this.jobSocketCounter = data.counter;
+ }
+ }
+ if (data.group_name === 'jobs' && data.unified_job_id === job.id) {
+ if (data.final_counter) {
+ this.jobSocketCounter = data.final_counter;
+ }
+ if (data.status) {
+ this.setState({ jobStatus: data.status });
+ }
}
});
this.interval = setInterval(() => this.monitorJobSocketCounter(), 5000);
@@ -344,6 +360,26 @@ class JobOutput extends Component {
}
}
+ handleCancelOpen() {
+ this.setState({ showCancelPrompt: true });
+ }
+
+ handleCancelClose() {
+ this.setState({ showCancelPrompt: false });
+ }
+
+ async handleCancelConfirm() {
+ const { job, type } = this.props;
+ this.setState({ cancelInProgress: true });
+ try {
+ await JobsAPI.cancel(job.id, type);
+ } catch (cancelError) {
+ this.setState({ cancelError });
+ } finally {
+ this.setState({ showCancelPrompt: false, cancelInProgress: false });
+ }
+ }
+
async handleDeleteJob() {
const { job, history } = this.props;
try {
@@ -518,7 +554,7 @@ class JobOutput extends Component {
}
render() {
- const { job } = this.props;
+ const { job, i18n } = this.props;
const {
contentError,
@@ -528,6 +564,10 @@ class JobOutput extends Component {
isHostModalOpen,
remoteRowCount,
cssMap,
+ jobStatus,
+ showCancelPrompt,
+ cancelError,
+ cancelInProgress,
} = this.state;
if (hasContentLoading) {
@@ -553,7 +593,12 @@ class JobOutput extends Component {