import React from 'react'; import { Modal } from 'react-bootstrap'; import type { Application, IJob, IModalComponentProps } from '@spinnaker/core'; import { confirmNotManaged, ModalClose, ReactModal, TaskExecutor, TaskMonitor, TaskMonitorWrapper, } from '@spinnaker/core'; import { AwsModalFooter } from '../../../common/AwsModalFooter'; import type { IAmazonServerGroupView } from '../../../domain'; export interface IScheduledActionValues { recurrence?: string | number; minSize?: number; maxSize?: number; desiredCapacity?: number; } export interface IEditScheduledActionsModalProps extends IModalComponentProps { application: Application; serverGroup: IAmazonServerGroupView; } interface IEditScheduledActionsModalState { scheduledActions: IScheduledActionValues[]; taskMonitor: TaskMonitor; } function projectScheduledAction(action: IScheduledActionValues): IScheduledActionValues { return { recurrence: action.recurrence, minSize: action.minSize, maxSize: action.maxSize, desiredCapacity: action.desiredCapacity, }; } export function isScheduledActionValid(action: IScheduledActionValues): boolean { const capacities = [action.minSize, action.maxSize, action.desiredCapacity]; if (!String(action.recurrence ?? '').trim() || capacities.every((capacity) => capacity === undefined)) { return false; } if ( capacities.some( (capacity) => capacity !== undefined && (!Number.isFinite(capacity) || !Number.isInteger(capacity) || capacity < 0), ) ) { return false; } if (action.minSize !== undefined && action.maxSize !== undefined && action.minSize > action.maxSize) { return false; } if (action.desiredCapacity !== undefined && action.minSize !== undefined && action.desiredCapacity < action.minSize) { return false; } if (action.desiredCapacity !== undefined && action.maxSize !== undefined && action.desiredCapacity > action.maxSize) { return false; } return true; } export function buildScheduledActionsJob( serverGroup: IAmazonServerGroupView, scheduledActions: IScheduledActionValues[], ): IJob { return { type: 'upsertAsgScheduledActions', asgs: [{ asgName: serverGroup.name, region: serverGroup.region }], scheduledActions: scheduledActions.map(projectScheduledAction), credentials: serverGroup.account, }; } export class EditScheduledActionsModal extends React.Component< IEditScheduledActionsModalProps, IEditScheduledActionsModalState > { public static show(props: IEditScheduledActionsModalProps) { return confirmNotManaged(props.serverGroup, props.application).then( (notManaged) => notManaged && ReactModal.show(EditScheduledActionsModal, props, { dialogClassName: 'modal-lg' }), ); } public state: IEditScheduledActionsModalState = { scheduledActions: (this.props.serverGroup.scheduledActions || []).map(projectScheduledAction), taskMonitor: new TaskMonitor({ application: this.props.application, title: `Update Scheduled Actions for ${this.props.serverGroup.name}`, onDismiss: this.props.dismissModal, onTaskComplete: () => this.props.application.serverGroups.refresh(), }), }; private addScheduledAction = () => { this.setState(({ scheduledActions }) => ({ scheduledActions: [...scheduledActions, {}] })); }; private removeScheduledAction = (index: number) => { this.setState(({ scheduledActions }) => ({ scheduledActions: scheduledActions.filter((_action, candidateIndex) => candidateIndex !== index), })); }; private updateScheduledAction = (index: number, field: keyof IScheduledActionValues, value: string | number) => { this.setState(({ scheduledActions }) => ({ scheduledActions: scheduledActions.map((action, candidateIndex) => candidateIndex === index ? { ...action, [field]: value === '' ? undefined : value } : action, ), })); }; private submit = () => { const { application, serverGroup } = this.props; this.state.taskMonitor.submit(() => TaskExecutor.executeTask({ application, description: `Update Scheduled Actions for ${serverGroup.name}`, job: [buildScheduledActionsJob(serverGroup, this.state.scheduledActions)], }), ); }; private renderNumberInput(index: number, field: keyof IScheduledActionValues, value?: number) { return ( this.updateScheduledAction(index, field, event.target.value === '' ? '' : Number(event.target.value)) } type="number" value={value ?? ''} /> ); } public render() { const { dismissModal, serverGroup } = this.props; const valid = this.state.scheduledActions.every(isScheduledActionValid); return ( <> Edit Scheduled Actions for {serverGroup.name}

You must specify at least one of: Min Size, Max Size, Desired Capacity.

Note: CRON expressions are evaluated in UTC.

{this.state.scheduledActions.map((action, index) => ( ))}
Recurrence (CRON) Min Size Max Size Desired Capacity
this.updateScheduledAction(index, 'recurrence', event.target.value)} required={true} type="text" value={action.recurrence ?? ''} /> {this.renderNumberInput(index, 'minSize', action.minSize)} {this.renderNumberInput(index, 'maxSize', action.maxSize)} {this.renderNumberInput(index, 'desiredCapacity', action.desiredCapacity)}
); } }