/* * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0 */ import * as React from 'react'; import API from '@aws-amplify/api'; import { Logger } from '@aws-amplify/core'; import { Grid, Row, Col, Button, PageHeader, Breadcrumb, BreadcrumbItem, Alert, ProgressBar, Modal, Tabs, Tab, Table, Label, ButtonToolbar, Glyphicon, Pager, Form, FormGroup, ControlLabel, FormControl } from 'react-bootstrap'; import { LinkContainer } from 'react-router-bootstrap'; import { LOGGING_LEVEL} from '../components/CustomUtil'; // Properties interface IProps { history?: any; match?: any; location?: any; getApiToken: Function; } // States interface IState { token: string; task: Task; taskId: string; taskExecutions: TaskExecution[]; sortIcon: string; isLoading: boolean; isChangingStatus: boolean; isExecutionsLoading: boolean; error: string; showModal: boolean; modalAction: string; modalError: string; isModalInProgress: boolean; defaultActiveTab: string; sortType: string; itemsPerPage: number; queryKeys: object[]; queryCurrentPosition: number; copyButtonName: string; executeResult: string; } // Task export interface Task { taskId?: string; name?: string; description?: string; targetTag?: string; targetParameter?: string; taskParameters?: TaskParameter[]; accounts?: string[]; regions?: string[]; actionName?: string; triggerType?: string; scheduledType?: ScheduledType; scheduledFixedRateInterval?: number; scheduledFixedRateType?: ScheduledFixedRateType; scheduledCronExpression?: string; eventPattern?: string; enabled?: boolean; templateUrl?: string; } // TaskParameter interface interface TaskParameter { Name: string; Type: string; Description: string; Value?: string; DefaultValue?: string; } // TaskExecution interface interface TaskExecution { parentExecutionId: string; status: TaskExecutionStatus; totalResourceCount: Number; completedResourceCount: Number; startTime: string; lastUpdateTime: string; } // Trigger type enum enum TriggerType { Schedule = 'Schedule', Event = 'Event' } // Scheduled type enum export enum ScheduledType { CronExpression = 'CronExpression', FixedRate = 'FixedRate' } // Scheduled fixed rate type enum enum ScheduledFixedRateType { minutes = 'minutes', minute = 'minute', hours = 'hours', hour = 'hour', days = 'days', day = 'day' } // Task execution status enum enum TaskExecutionStatus { pending = 'Pending', inProgress = 'InProgress', waiting = 'Waiting', success = 'Success', timedOut = 'TimedOut', cancelling = 'Cancelling', cancelled = 'Cancelled', failed = 'Failed' } // Sort type enum enum SortType { asc = 'ASC', desc = 'DESC' } // Query type enum enum QueryType { prev, new, next } // External variables const LOGGER = new Logger('TaskDetail', LOGGING_LEVEL); const API_NAME = 'operations-conductor-api'; class TaskDetail extends React.Component { // class properties hiddenParameters: string[]; constructor(props: Readonly) { super(props); this.state = { token: '', task: {}, taskId: '', taskExecutions: [], sortIcon: 'sort-by-attributes', isLoading: false, isChangingStatus: false, isExecutionsLoading: false, error: '', modalAction: '', showModal: false, modalError: '', isModalInProgress: false, defaultActiveTab: 'overview', sortType: 'DESC', itemsPerPage: 10, queryKeys: [{}], queryCurrentPosition: 0, copyButtonName: 'Copy URL', executeResult: '' }; this.hiddenParameters = ['SQSMsgBody', 'SQSMsgReceiptHandle', 'TargetResourceType']; } componentDidMount() { this.setApiToken().then(() => { if (this.props.location.state) { this.setState({ modalAction: 'acknowledgement', showModal: true }); } this.getTask(); this.getTaskExecutions(QueryType.new); }).catch((error) => { this.handleError('Error occurred while setting API token', error); }); } // Sets API token setApiToken = async () => { let token = await this.props.getApiToken(); this.setState({ token }); }; // Gets a task getTask = async () => { const { taskId } = this.props.match.params; this.setState({ isLoading: true, error: '', task: {}, taskId }); let path = `/tasks/${taskId}`; let params = { headers: { 'Authorization': this.state.token } }; try { let task: Task = await API.get(API_NAME, path, params); this.setState({ task }); } catch (error) { this.handleError('Error occurred while getting a task detail.', error); } finally { this.setState({ isLoading: false }); } }; // Edits a task editTask = async () => { // Going to task create page with the current information this.props.history.push({ pathname: '/tasks/edit', state: { task: this.state.task } }); }; // Changes a task status changeStatus = async () => { const task = this.state.task; this.setState({ isChangingStatus: true, error: '', }); let path = `/tasks/${task.taskId}`; let params = { headers: { 'Authorization': this.state.token }, body: { ...task, enabled: !task.enabled, accounts: task.accounts!.join(','), regions: task.regions!.join(','), scheduledFixedRateInterval: task.scheduledFixedRateInterval ? `${task.scheduledFixedRateInterval}` : undefined, } }; try { let task: Task = await API.put(API_NAME, path, params); this.setState({ task }); } catch (error) { this.handleError('Error occurred while changing a task status.', error); } finally { this.setState({ isChangingStatus: false }); } }; // Executes a task executeTask = async (taskId: string) => { this.setState({ isModalInProgress: true, modalError: '' }); let path = `/tasks/${taskId}/execute`; let params = { headers: { 'Authorization': this.state.token } }; try { let executeResult = await API.put(API_NAME, path, params); LOGGER.info(`Task executed, taskId: ${taskId}, taskName: ${this.state.task.name}`); if (executeResult['errorMessage']) { executeResult = `Error: ${executeResult['errorMessage']}`; } this.setState({ modalAction: 'executeTaskResult', executeResult, defaultActiveTab: 'logs' }, async () => { await this.getTaskExecutions(QueryType.new); }); } catch (error) { this.handleError('Error occurred while executing a task.', error, 'modal'); } finally { this.setState({ isModalInProgress: false }); } } // Deletes a task deleteTask = async (taskId: string) => { this.setState({ isModalInProgress: true, modalError: '' }); let path = `/tasks/${taskId}`; let params = { headers: { 'Authorization': this.state.token } }; try { await API.del(API_NAME, path, params); LOGGER.info(`Task deleted: ${taskId} - ${this.state.task.name}`); this.setState({ modalAction: 'deleteTaskConfirm', isModalInProgress: false }); } catch (error) { this.handleError('Error occurred while deleting a task.', error, 'modal'); this.setState({ isModalInProgress: false }); } }; // Gets task executions getTaskExecutions = async (queryType: QueryType) => { const { taskId } = this.props.match.params; this.setState({ isExecutionsLoading: true, error: '', taskExecutions: [], taskId }); let path = `/tasks/${taskId}/executions`; let params = { headers: { 'Authorization': this.state.token }, body: { sortType: this.state.sortType, itemsPerPage: this.state.itemsPerPage, lastKey: {} } }; let { queryCurrentPosition, queryKeys } = this.state; switch (queryType) { case QueryType.prev: params.body.lastKey = queryKeys[queryCurrentPosition - 1]; break; case QueryType.next: params.body.lastKey = queryKeys[queryCurrentPosition + 1]; break; default: break; } try { let taskExecutions = await API.post(API_NAME, path, params); switch (queryType) { case QueryType.prev: this.setState((prevState) => ({ queryCurrentPosition: prevState.queryCurrentPosition - 1 })); break; case QueryType.next: if (taskExecutions.LastEvaluatedKey) { queryKeys.push(taskExecutions.LastEvaluatedKey); } this.setState((prevState) => ({ queryCurrentPosition: prevState.queryCurrentPosition + 1, queryKeys })); break; default: queryKeys = [{}]; if (taskExecutions.LastEvaluatedKey) { queryKeys.push(taskExecutions.LastEvaluatedKey); } this.setState({ queryCurrentPosition: 0, queryKeys }); break; } this.setState({ taskExecutions: taskExecutions.Items }); } catch (error) { this.handleError('Error occurred while getting task executions.', error); } finally { this.setState({ isExecutionsLoading: false }); } }; // Handles modal close handleModalClose = () => { this.setState({ showModal: false, modalError: '' }); }; // Handles error handleError = (message: string, error: any, type?: string) => { if (error.response !== undefined) { LOGGER.error(message, error.response.data.message); if (type === 'modal') { this.setState({ modalError: error.response.data.message }); } else { if (error.response.data.statusCode === 404) { this.props.history.push('/tasks'); } this.setState({ error: error.response.data.message }); } } else { LOGGER.error(message, error.message); if (type === 'modal') { this.setState({ modalError: error.message }); } else { this.setState({ error: error.message }); } } }; // Handles executions sort handleSort = () => { let sortType = this.state.sortType === SortType.asc ? 'DESC' : 'ASC'; this.setState({ sortType }, () => { this.getTaskExecutions(QueryType.new); }); }; // Handles value changes handleItemsPerChange = (event: any) => { this.setState({ itemsPerPage: parseInt(event.target.value) }, () => { this.getTaskExecutions(QueryType.new); }); }; // Copies CloudFormation template URL to clipboard copyUrlToClipboard = () => { let url = this.state.task.templateUrl; if (url) { let tempInput = document.createElement('input'); tempInput.value = url; document.body.appendChild(tempInput); tempInput.select(); document.execCommand('copy'); document.body.removeChild(tempInput); this.setState({ copyButtonName: 'URL Copied!' }); } } render() { return (
Tasks {`Task Detail${this.state.isLoading ? '' : ': ' + this.state.task.name}`} { !this.state.isLoading && [ Task Detail - {this.state.task.name} , { // Event type does not require to execute a task manually. this.state.task.triggerType === TriggerType.Schedule && } ,   , {/* General Tab */}  
General Information
Action Name {this.state.task.actionName}
Task Name {this.state.task.name}
Description {this.state.task.description}
Trigger Type {this.state.task.triggerType}
Target Tag {this.state.task.targetTag}
Accounts { this.state.task.accounts !== undefined && this.state.task.accounts.map((account) => { return ( [ ,   ] ); }) }
Regions { this.state.task.regions !== undefined && this.state.task.regions.map((region) => { return ( [ ,   ] ); }) }
CloudFormation Template
{ this.state.task.triggerType !== undefined && { this.state.task.triggerType === TriggerType.Schedule && [ , { this.state.task.scheduledType === ScheduledType.CronExpression && [ , ] } { this.state.task.scheduledType === ScheduledType.FixedRate && [ , ] } ] } { this.state.task.triggerType === TriggerType.Event && } }
Trigger
Trigger Type {this.state.task.triggerType}
Scheduled Type {this.state.task.scheduledType}
Scheduled Cron Expression{this.state.task.scheduledCronExpression}Scheduled Fixed Rate{this.state.task.scheduledFixedRateInterval} {this.state.task.scheduledFixedRateType}
Event Pattern
{this.state.task.eventPattern}
{ this.state.task.taskParameters !== undefined && this.state.task.taskParameters.map((parameter: TaskParameter) => { return ( this.hiddenParameters.indexOf(parameter.Name) < 0 && [ , , ] ); }) }
Parameters
Name Key Value
{parameter.Name} Value {parameter.Value}
Type {parameter.Type}
Description {parameter.Description}
{/* Trigger Tab */}   { this.state.task.triggerType !== undefined && { this.state.task.triggerType === TriggerType.Schedule && [ , { this.state.task.scheduledType === ScheduledType.CronExpression && [ , ] } { this.state.task.scheduledType === ScheduledType.FixedRate && [ , ] } ] } { this.state.task.triggerType === TriggerType.Event && } }
Trigger
Trigger Type {this.state.task.triggerType}
Scheduled Type {this.state.task.scheduledType}
Scheduled Cron Expression{this.state.task.scheduledCronExpression}Scheduled Fixed Rate{this.state.task.scheduledFixedRateInterval} {this.state.task.scheduledFixedRateType}
Event Pattern
{this.state.task.eventPattern}
{/* Logs Tab */}  
Items
  { this.state.isExecutionsLoading && } { this.state.taskExecutions.length === 0 && !this.state.isExecutionsLoading && } { this.state.taskExecutions.map((taskExecution: TaskExecution) => { return ( ); }) }
Task Execution ID Status Total Resources Completed Resources Start Time   Last Update Time
Loading...
No task execution found.
{taskExecution.parentExecutionId} { [ TaskExecutionStatus.pending, TaskExecutionStatus.inProgress, TaskExecutionStatus.waiting, TaskExecutionStatus.cancelling ].indexOf(taskExecution.status) > -1 && {taskExecution.status} } { taskExecution.status === TaskExecutionStatus.success && {taskExecution.status} } { [ TaskExecutionStatus.timedOut, TaskExecutionStatus.cancelled, TaskExecutionStatus.failed ].indexOf(taskExecution.status) > -1 && {taskExecution.status} } {taskExecution.totalResourceCount} {taskExecution.completedResourceCount} {taskExecution.startTime} {taskExecution.lastUpdateTime.replace('_', ' ').replace(/\./g, ':')}
this.getTaskExecutions(QueryType.prev)}> Previous Page { !this.state.isExecutionsLoading && { this.state.taskExecutions.length === 0 && No Task Execution } { this.state.taskExecutions.length > 0 && Task Executions {this.state.itemsPerPage * this.state.queryCurrentPosition + 1} - {this.state.itemsPerPage * this.state.queryCurrentPosition + this.state.taskExecutions.length} } } this.getTaskExecutions(QueryType.next)}> Next Page { this.state.isExecutionsLoading && } ] } { this.state.error && Error:
{this.state.error}
} { this.state.isLoading && } { this.state.modalAction === 'deleteTask' && [ Delete Task , Are you sure to delete the task {this.state.task.name}?, ] } { this.state.modalAction === 'deleteTaskConfirm' && [ Task Deleted , Task {this.state.task.name} has been deleted. , ] } { this.state.modalAction === 'executeTask' && [ Execute Task , Are you sure to execute the task {this.state.task.name} manually?, ] } { this.state.modalAction === 'executeTaskResult' && [ Execute Task Result , {this.state.executeResult}, ] } { this.state.modalAction === 'acknowledgement' && [ Launch the secondary stack ,

You need to launch the secondary CloudFormation stacks in every region in every account.
To copy the CloudFormation template URL, click Copy URL button in the task detail page.

, ] } { this.state.isModalInProgress && } { this.state.modalError && Error:
{this.state.modalError}
}
); } } export default TaskDetail;