diff --git a/src/components/molecules/MainListItem/MainListItem.jsx b/src/components/molecules/MainListItem/MainListItem.jsx index 9576678c..3d349844 100644 --- a/src/components/molecules/MainListItem/MainListItem.jsx +++ b/src/components/molecules/MainListItem/MainListItem.jsx @@ -28,6 +28,7 @@ import type { MainItem } from '../../../types/MainItem' import type { Execution } from '../../../types/Execution' import arrowImage from './images/arrow.svg' +import scheduleImage from './images/schedule.svg' const CheckboxStyled = styled(Checkbox)` opacity: ${props => props.checked ? 1 : 0}; @@ -79,6 +80,14 @@ const TitleLabel = styled.div` white-space: nowrap; text-overflow: ellipsis; ` +const StatusWrapper = styled.div` + display: flex; + margin-top: 8px; +` +const ScheduleImage = styled.div` + ${StyleProps.exactSize('16px')} + background: url('${scheduleImage}') center no-repeat; +` const EndpointsImages = styled.div` display: flex; align-items: center; @@ -111,6 +120,7 @@ type Props = { selected: boolean, useTasksRemaining?: boolean, image: string, + showScheduleIcon?: boolean, endpointType: (endpointId: string) => string, onSelectedChange: (value: boolean) => void, } @@ -205,7 +215,14 @@ class MainListItem extends React.Component { <TitleLabel>{this.props.item.instances[0]}</TitleLabel> - {status ? <StatusPill data-test-id={`mainListItem-statusPill-${status}`} status={status} /> : null} + <StatusWrapper> + {status ? <StatusPill + status={status} + style={{ marginRight: '8px' }} + data-test-id={`mainListItem-statusPill-${status}`} + /> : null} + {this.props.showScheduleIcon ? <ScheduleImage /> : null} + </StatusWrapper> {endpointImages} {this.renderLastExecution()} diff --git a/src/components/molecules/MainListItem/images/schedule.svg b/src/components/molecules/MainListItem/images/schedule.svg new file mode 100644 index 00000000..359ea5c3 --- /dev/null +++ b/src/components/molecules/MainListItem/images/schedule.svg @@ -0,0 +1,17 @@ + + + + Icons/Pending/Blue + Created with Sketch. + + + + + + + + + + + + \ No newline at end of file diff --git a/src/components/pages/ReplicasPage/ReplicasPage.jsx b/src/components/pages/ReplicasPage/ReplicasPage.jsx index c3fac4db..f93c5526 100644 --- a/src/components/pages/ReplicasPage/ReplicasPage.jsx +++ b/src/components/pages/ReplicasPage/ReplicasPage.jsx @@ -31,12 +31,15 @@ import replicaLargeImage from './images/replica-large.svg' import projectStore from '../../../stores/ProjectStore' import replicaStore from '../../../stores/ReplicaStore' +import scheduleStore from '../../../stores/ScheduleStore' import endpointStore from '../../../stores/EndpointStore' import notificationStore from '../../../stores/NotificationStore' import configLoader from '../../../utils/Config' const Wrapper = styled.div`` +const SCHEDULE_POLL_TIMEOUT = 10000 + const BulkActions = [ { label: 'Execute', value: 'execute' }, { label: 'Delete', value: 'delete' }, @@ -57,6 +60,8 @@ class ReplicasPage extends React.Component<{ history: any }, State> { pollTimeout: TimeoutID stopPolling: boolean + schedulePolling: boolean + schedulePollTimeout: TimeoutID componentDidMount() { document.title = 'Coriolis Replicas' @@ -70,6 +75,7 @@ class ReplicasPage extends React.Component<{ history: any }, State> { componentWillUnmount() { clearTimeout(this.pollTimeout) + clearTimeout(this.schedulePollTimeout) this.stopPolling = true } @@ -164,10 +170,25 @@ class ReplicasPage extends React.Component<{ history: any }, State> { } Promise.all([replicaStore.getReplicas(), endpointStore.getEndpoints()]).then(() => { + if (!this.schedulePolling) { + this.pollSchedule() + } this.pollTimeout = setTimeout(() => { this.pollData() }, configLoader.config.requestPollTimeout) }) } + pollSchedule() { + if (this.state.modalIsOpen || this.stopPolling || replicaStore.replicas.length === 0) { + return + } + this.schedulePolling = true + scheduleStore.getSchedulesBulk(replicaStore.replicas.map(r => r.id)).then(() => { + this.schedulePollTimeout = setTimeout(() => { + this.pollSchedule() + }, SCHEDULE_POLL_TIMEOUT) + }) + } + searchText(item: MainItem, text: ?string) { let result = false if (item.instances[0].toLowerCase().indexOf(text || '') > -1) { @@ -196,6 +217,14 @@ class ReplicasPage extends React.Component<{ history: any }, State> { return true } + isReplicaScheduled(replicaId: string): boolean { + let bulkScheduleItem = scheduleStore.bulkSchedules.find(b => b.replicaId === replicaId) + if (!bulkScheduleItem) { + return false + } + return Boolean(bulkScheduleItem.schedules.find(s => s.enabled)) + } + render() { return ( @@ -216,6 +245,7 @@ class ReplicasPage extends React.Component<{ history: any }, State> { ( { let endpoint = this.getEndpoint(id) if (endpoint) { diff --git a/src/sources/ScheduleSource.js b/src/sources/ScheduleSource.js index bf2c5370..e40e0187 100644 --- a/src/sources/ScheduleSource.js +++ b/src/sources/ScheduleSource.js @@ -56,8 +56,11 @@ class ScheduleSource { })) } - static getSchedules(replicaId: string): Promise { - return Api.get(`${servicesUrl.coriolis}/${Api.projectId}/replicas/${replicaId}/schedules`).then(response => { + static getSchedules(replicaId: string, opts?: { skipLog?: boolean }): Promise { + return Api.send({ + url: `${servicesUrl.coriolis}/${Api.projectId}/replicas/${replicaId}/schedules`, + skipLog: opts && opts.skipLog, + }).then(response => { let schedules = [...response.data.schedules] schedules.forEach(s => { if (s.expiration_date) { diff --git a/src/stores/ScheduleStore.js b/src/stores/ScheduleStore.js index 1ab6a5af..db29f13a 100644 --- a/src/stores/ScheduleStore.js +++ b/src/stores/ScheduleStore.js @@ -14,9 +14,9 @@ along with this program. If not, see . // @flow -import { observable, action } from 'mobx' +import { observable, action, runInAction } from 'mobx' -import type { Schedule } from '../types/Schedule' +import type { Schedule, ScheduleBulkItem } from '../types/Schedule' import Source from '../sources/ScheduleSource' const updateSchedule = (schedules, id, data) => { @@ -36,6 +36,7 @@ const updateSchedule = (schedules, id, data) => { class ScheduleStore { @observable loading: boolean = false @observable schedules: Schedule[] = [] + @observable bulkSchedules: ScheduleBulkItem[] = [] @observable unsavedSchedules: Schedule[] = [] @observable scheduling: boolean = false @observable adding: boolean = false @@ -62,6 +63,16 @@ class ScheduleStore { }) } + getSchedulesBulk(replicaIds: string[]): Promise { + return Promise.all(replicaIds.map(replicaId => { + return Source.getSchedules(replicaId, { skipLog: true }).then(schedules => { + return { replicaId, schedules } + }) + })).then(bulkSchedules => { + runInAction(() => { this.bulkSchedules = bulkSchedules }) + }) + } + @action addSchedule(replicaId: string, schedule: Schedule): Promise { this.adding = true diff --git a/src/types/Schedule.js b/src/types/Schedule.js index 7ddc81fe..5e49a9c4 100644 --- a/src/types/Schedule.js +++ b/src/types/Schedule.js @@ -29,3 +29,8 @@ export type Schedule = { expiration_date?: Date, shutdown_instances?: boolean, } + +export type ScheduleBulkItem = { + replicaId: string, + schedules: Schedule[], +}