/* * 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, ProgressBar, PageHeader, Breadcrumb, BreadcrumbItem, Alert, ButtonToolbar, Form, FormGroup, ControlLabel, FormControl, HelpBlock, Table } from 'react-bootstrap'; import { LinkContainer } from 'react-router-bootstrap'; import { LOGGING_LEVEL} from '../components/CustomUtil'; // Properties interface IProps { history?: any; location: any; getApiToken: Function; } // States interface IState { token: string; mode: string; step: number; task: Task; isLoading: boolean; error: string; validation: { taskName: any, targetTag: any, cronExpression: any, interval: any, accounts: any, regions: any }; showHelp: { taskName: boolean, targetTag: boolean, cronExpression: boolean, interval: boolean, accounts: boolean, regions: boolean }; } // TaskParameter interface interface TaskParameter { Name: string; Type: string; Description: string; Value?: string; DefaultValue?: string; } // Task interface interface Task { taskId: string; name: string; description: string; targetTag: string; taskParameters: TaskParameter[]; accounts: string; regions: string; actionName: string; triggerType: TriggerType; scheduledType?: ScheduledType; scheduledFixedRateInterval?: string; scheduledFixedRateType?: ScheduledFixedRateType; scheduledCronExpression?: string; eventPattern?: string; } // Action interface interface Action { name: string; owner: string; description: string; parameters: TaskParameter[]; } // Trigger type enum enum TriggerType { Schedule = 'Schedule', Event = 'Event' } // Scheduled type enum enum ScheduledType { CronExpression = 'CronExpression', FixedRate = 'FixedRate' } // Scheduled fixed rate type enum enum ScheduledFixedRateType { minutes = 'minutes', hours = 'hours', days = 'days' } // External variables const LOGGER = new Logger('TaskCreate', LOGGING_LEVEL); const API_NAME = 'operations-conductor-api'; const EVENT_PLACEHOLDER = `{ "source": [ "aws.ec2" ], "detail-type": [ "EC2 Instance State-change Notification" ], "detail": { "state": [ "running" ] } }`; class TaskCreate extends React.Component { // class properties TriggerForm: Function; hiddenParameters: string[]; constructor(props: Readonly) { super(props); this.state = { token: '', mode: '', step: 1, task: { taskId: '', name: '', description: '', targetTag: '', taskParameters: [], accounts: '', regions: '', actionName: '', triggerType: TriggerType.Schedule, scheduledType: ScheduledType.CronExpression, scheduledFixedRateType: ScheduledFixedRateType.minutes }, isLoading: false, error: '', validation: { taskName: null, targetTag: null, cronExpression: null, interval: null, accounts: null, regions: null }, showHelp: { taskName: false, targetTag: false, cronExpression: false, interval: false, accounts: false, regions: false } }; this.TriggerForm = () => { if (this.state.task.triggerType === TriggerType.Schedule) { return (
Schedule Type { event.persist(); this.handleScheduledTypeChange(event); }} > { (this.state.task.scheduledType === ScheduledType.CronExpression || !this.state.task.scheduledType) && Cron Expression { event.persist(); this.handleCronExpressionChange(event); }} /> Learn more about CloudWatch Events schedules. } { this.state.task.scheduledType === ScheduledType.FixedRate && Fixed Rate of { event.persist(); this.handleScheduledFixedRateIntervalChange(event); }} /> { this.state.showHelp.interval && Invalid interval (1 <= interval, integer number). } Learn more about CloudWatch Events schedules. { event.persist(); this.handleScheduledFixedRateTypeChange(event); }} > }
); } else if (this.state.task.triggerType === TriggerType.Event) { return (
Event Pattern { event.persist(); this.handleEventPatternChange(event); }} className="textarea" /> Learn more about event pattens  and event examples from supported services.
); } else { return (null); } }; this.hiddenParameters = ['SQSMsgBody', 'SQSMsgReceiptHandle', 'TargetResourceType']; } componentDidMount() { if (this.props.location.state === undefined) { this.props.history.push('/tasks/actions'); } this.setApiToken().then(() => { let actionName = ''; if (this.props.location.state.task) { actionName = this.props.location.state.task.actionName; let task = this.props.location.state.task; this.setState({ task: { ...task, accounts: task.accounts!.join(','), regions: task.regions!.join(','), scheduledFixedRateInterval: task.scheduledFixedRateInterval ? `${task.scheduledFixedRateInterval}` : undefined, scheduledFixedRateType: task.scheduledFixedRateType ? this.getScheduledFixedRateType(task.scheduledFixedRateType) : ScheduledFixedRateType.minutes }, mode: 'Edit' }); } else { actionName = this.props.location.state.actionName; this.setState((prevState) => ({ task: { ...prevState.task, actionName }, mode: 'Create' })); } this.getActionDocument(actionName); }).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 scheduled fixed rate type getScheduledFixedRateType = (scheduledFixedRateType: string) => { return scheduledFixedRateType.endsWith('s') ? scheduledFixedRateType : `${scheduledFixedRateType}s` } // Gets an action document getActionDocument = async (actionId: string) => { this.setState({ isLoading: true, error: '' }); let path = `/actions/${actionId}`; let params = { headers: { 'Authorization': this.state.token } }; try { let action: Action = await API.get(API_NAME, path, params); if (this.state.mode === 'Create') { this.setState((prevState) => ({ task: { ...prevState.task, taskParameters: action.parameters, targetParameter: action.parameters[0].Name } })); } } catch (error) { this.handleError('Error occurred while getting an action document.', error); } finally { this.setState({ isLoading: false }); } }; // Gets a default value of a parameter getParameterValue = (name: string): string => { let parameters = this.state.task.taskParameters; for (let parameter of parameters) { if (parameter.Name === name) { if (parameter.Value !== undefined) { return parameter.Value; } else { return ''; } } } return ''; }; // Checks validation of values checkValidation = () => { let isPass: boolean = true; let step: number = this.state.step; let task: Task = this.state.task; switch (step) { // Define Task case 1: // Checks task name is valid isPass = task.name.trim()!== ''; this.setState((prevState) => ({ validation: { ...prevState.validation, taskName: isPass ? null : 'error' }, showHelp: { ...prevState.validation, taskName: !isPass } })); break; // Target Tag case 2: isPass = task.targetTag.trim() !== ''; this.setState((prevState) => ({ validation: { ...prevState.validation, targetTag: isPass ? null : 'error' }, showHelp: { ...prevState.validation, targetTag: !isPass } })); break; // Task Parameters case 3: // Required parameters are going to be checked at the backend. break; // Task Trigger case 4: /** * At this stage, only interval value is going to be validated. * Cron expression and event validation are going to be done at the backend. */ if (task.triggerType === TriggerType.Schedule) { if (task.scheduledType === ScheduledType.FixedRate) { let interval = task.scheduledFixedRateInterval; if (interval === undefined) { isPass = false; } else { isPass = /^[1-9]\d*$/.test(interval); } this.setState((prevState) => ({ validation: { ...prevState.validation, cronExpression: null, interval: isPass ? null : 'error' }, showHelp: { ...prevState.validation, cronExpression: false, interval: !isPass } })); } } break; // Task Scrope case 5: // Backend is going to validate again. let isAccountValid: boolean = task.accounts.trim() !== ''; let isRegionValid: boolean = task.regions.trim() !== ''; this.setState((prevState) => ({ validation: { ...prevState.validation, accounts: isAccountValid ? null : 'error', regions: isRegionValid ? null : 'error' }, showHelp: { ...prevState.validation, accounts: !isAccountValid, regions: !isRegionValid } })); isPass = isAccountValid && isRegionValid; break; } if (isPass) { this.setState({ step: step + 1 }); } } // Creates a task createTask = async () => { this.setState({ isLoading: true, error: '' }); let path = '/tasks'; let params = { headers: { 'Authorization': this.state.token }, body: { ...this.state.task } }; try { let task: Task = await API.post(API_NAME, path, params); LOGGER.info(`Task created: ${JSON.stringify(task)}`); this.setState({ isLoading: false }); this.props.history.push({ pathname: `/tasks/${task.taskId}`, state: {new: true} }); } catch (error) { this.setState({ isLoading: false }); this.handleError('Error occurred while creating a task.', error); } }; // Edits a task editTask = async () => { this.setState({ isLoading: true, error: '' }); let path = `/tasks/${this.state.task.taskId}`; let params = { headers: { 'Authorization': this.state.token }, body: { ...this.state.task } }; try { let task: Task = await API.put(API_NAME, path, params); LOGGER.info(`Task created: ${JSON.stringify(task)}`); this.setState({ isLoading: false }); this.props.history.push({ pathname: `/tasks/${task.taskId}`, state: {new: true} }); } catch (error) { this.setState({ isLoading: false }); this.handleError('Error occurred while creating a task.', error); } }; // Sets a parameter value setParameterValue = (name: string, value: string) => { let parameters = this.state.task.taskParameters; for (let parameter of parameters) { if (parameter.Name === name) { parameter.Value = value; break; } } this.setState((prevState) => ({ task: { ...prevState.task, taskParameters: parameters } })); }; // Handles value changes handleTaskNameChange = (event: any) => { this.setState((prevState) => ({ task: { ...prevState.task, name: event.target.value } })); }; handleTaskDescriptionChange = (event: any) => { this.setState((prevState) => ({ task: { ...prevState.task, description: event.target.value } })); }; handleTargetParameterChange = (event: any) => { this.setState((prevState) => ({ task: { ...prevState.task, targetParameter: event.target.value } })); } handleTargetTagChange = (event: any) => { this.setState((prevState) => ({ task: { ...prevState.task, targetTag: event.target.value } })); }; handleParameterChange = (event: any, name: string) => { this.setParameterValue(name, event.target.value); }; handleTriggerTypeChange = (event: any) => { this.setState((prevState) => ({ task: { ...prevState.task, triggerType: event.target.value } })); }; handleEventPatternChange = (event: any) => { this.setState((prevState) => ({ task: { ...prevState.task, eventPattern: event.target.value } })); }; handleScheduledTypeChange = (event: any) => { this.setState((prevState) => ({ task: { ...prevState.task, scheduledType: event.target.value } })); }; handleCronExpressionChange = (event: any) => { this.setState((prevState) => ({ task: { ...prevState.task, scheduledCronExpression: event.target.value } })); }; handleScheduledFixedRateIntervalChange = (event: any) => { this.setState((prevState) => ({ task: { ...prevState.task, scheduledFixedRateInterval: event.target.value } })); }; handleScheduledFixedRateTypeChange = (event: any) => { this.setState((prevState) => ({ task: { ...prevState.task, scheduledFixedRateType: event.target.value } })); }; handleAccountsChange = (event: any) => { this.setState((prevState) => ({ task: { ...prevState.task, accounts: event.target.value } })); }; handleRegionsChange = (event: any) => { this.setState((prevState) => ({ task: { ...prevState.task, regions: event.target.value } })); }; handleConcurrencyChange = (event: any) => { this.setState((prevState) => ({ task: { ...prevState.task, concurrency: event.target.value } })); }; // Handles error handleError = (message: string, error: any) => { if (error.response !== undefined) { LOGGER.error(message, error.response.data.message); this.setState({ error: error.response.data.message }); } else { LOGGER.error(message, error.message); this.setState({ error: error.message }); } }; render() { return ( Tasks { this.state.mode === 'Create' && Actions } { this.state.mode === 'Edit' && Update Task } {this.state.mode} Task {this.state.mode} a task - {this.state.task.actionName} { !this.state.isLoading && this.state.step === 1 &&
Task Name { event.persist(); this.handleTaskNameChange(event); }} disabled={this.state.mode === 'Edit'} /> { this.state.showHelp.taskName && Task name cannot be empty. } Task Description { event.persist(); this.handleTaskDescriptionChange(event); }} />
} { this.state.step === 2 &&
Target Tag { event.persist(); this.handleTargetTagChange(event); }} /> The task will select resources tagged with the target tag. { this.state.showHelp.targetTag && Target tag cannot be empty. }
} { this.state.step === 3 &&
{ this.state.task.taskParameters.map((parameter: TaskParameter) => { return ( this.hiddenParameters.indexOf(parameter.Name) < 0 && {parameter.Name} this.handleParameterChange(event, parameter.Name)} /> {parameter.Description} ); }) }
} { this.state.step === 4 &&
Trigger Type { event.persist(); this.handleTriggerTypeChange(event); }} >
} { this.state.step === 5 &&
Accounts { event.persist(); this.handleAccountsChange(event); }} /> List of accounts in which Operations Conductor on AWS should operate. Requires 12 digit account IDs { this.state.showHelp.accounts && Accounts cannot be empty. } Regions { event.persist(); this.handleRegionsChange(event); }} /> List of regions in which Operations Conductor on AWS should operate. Requires region codes (e.g. us-east-1). See more about the available region codes. { this.state.showHelp.accounts && Regions cannot be empty. }
} { this.state.step === 6 &&

Define Task

Task Name {this.state.task.name}
Task Definition {this.state.task.description}

Target Tag

Target Tag {this.state.task.targetTag}

Parameters

{ this.state.task.taskParameters.map((parameter: TaskParameter) => { return ( this.hiddenParameters.indexOf(parameter.Name) < 0 && ); }) }
{parameter.Name} {parameter.Value}

Task Trigger

{ this.state.task.triggerType === TriggerType.Event && } { this.state.task.triggerType === TriggerType.Schedule && } { this.state.task.triggerType === TriggerType.Schedule && this.state.task.scheduledType === ScheduledType.CronExpression && } { this.state.task.triggerType === TriggerType.Schedule && this.state.task.scheduledType === ScheduledType.FixedRate && }
Trigger Type {this.state.task.triggerType}
Event Pattern
{this.state.task.eventPattern}
Schedule Type {this.state.task.scheduledType}
Scheduled Cron Expression {this.state.task.scheduledCronExpression}
Scheduled Fixed Rate of {this.state.task.scheduledFixedRateInterval} {this.state.task.scheduledFixedRateType}

Task Scope

Accounts {this.state.task.accounts}
Regions {this.state.task.regions}

}   { this.state.error && Error:
{this.state.error}
} { this.state.isLoading && }
); } } export default TaskCreate;