1
import {Checkbox, DropDown, Duration, NotificationType, Ticker} from 'argo-ui';
2
import * as moment from 'moment';
3
import * as PropTypes from 'prop-types';
4
import * as React from 'react';
6
import {ErrorNotification, Revision, Timestamp} from '../../../shared/components';
7
import {AppContext} from '../../../shared/context';
8
import * as models from '../../../shared/models';
9
import {services} from '../../../shared/services';
10
import * as utils from '../utils';
12
import './application-operation-state.scss';
15
application: models.Application;
16
operationState: models.OperationState;
19
const Filter = (props: {filters: string[]; setFilters: (f: string[]) => void; options: string[]; title: string; style?: React.CSSProperties}) => {
20
const {filters, setFilters, options, title, style} = props;
25
<div title='Filter' style={style}>
26
<button className='argo-button argo-button--base'>
27
{title} <i className='argo-icon-filter' aria-hidden='true' />
32
<div key={f} style={{minWidth: '150px', lineHeight: '2em', padding: '5px'}}>
34
checked={filters.includes(f)}
35
onChange={checked => {
36
const selectedValues = [...filters];
37
const idx = selectedValues.indexOf(f);
38
if (idx > -1 && !checked) {
39
selectedValues.splice(idx, 1);
41
selectedValues.push(f);
43
setFilters(selectedValues);
46
<label htmlFor={`filter__${f}`}>{f}</label>
53
export const ApplicationOperationState: React.StatelessComponent<Props> = ({application, operationState}, ctx: AppContext) => {
54
const operationAttributes = [
55
{title: 'OPERATION', value: utils.getOperationType(application)},
56
{title: 'PHASE', value: operationState.phase},
57
...(operationState.message ? [{title: 'MESSAGE', value: operationState.message}] : []),
58
{title: 'STARTED AT', value: <Timestamp date={operationState.startedAt} />},
63
{time => <Duration durationMs={((operationState.finishedAt && moment(operationState.finishedAt)) || time).diff(moment(operationState.startedAt)) / 1000} />}
69
if (operationState.finishedAt && operationState.phase !== 'Running') {
70
operationAttributes.push({title: 'FINISHED AT', value: <Timestamp date={operationState.finishedAt} />});
71
} else if (operationState.phase !== 'Terminating') {
72
operationAttributes.push({
76
className='argo-button argo-button--base'
77
onClick={async () => {
78
const confirmed = await ctx.apis.popup.confirm('Terminate operation', 'Are you sure you want to terminate operation?');
81
await services.applications.terminateOperation(application.metadata.name, application.metadata.namespace);
83
ctx.apis.notifications.show({
84
content: <ErrorNotification title='Unable to terminate operation' e={e} />,
85
type: NotificationType.Error
95
if (operationState.syncResult) {
96
operationAttributes.push({title: 'REVISION', value: <Revision repoUrl={utils.getAppDefaultSource(application).repoURL} revision={operationState.syncResult.revision} />});
99
if (operationState.operation.initiatedBy) {
100
if (operationState.operation.initiatedBy.automated) {
101
initiator = 'automated sync policy';
103
initiator = operationState.operation.initiatedBy.username;
106
operationAttributes.push({title: 'INITIATED BY', value: initiator || 'Unknown'});
108
const resultAttributes: {title: string; value: string}[] = [];
109
const syncResult = operationState.syncResult;
110
if (operationState.finishedAt) {
112
(syncResult.resources || []).forEach(res => {
113
resultAttributes.push({
114
title: `${res.namespace}/${res.kind}:${res.name}`,
120
const [filters, setFilters] = React.useState([]);
122
const Statuses = Object.keys(models.ResultCodes);
123
const OperationPhases = Object.keys(models.OperationPhases);
124
// const syncPhases = ['PreSync', 'Sync', 'PostSync', 'SyncFail'];
125
// const hookPhases = ['Running', 'Terminating', 'Failed', 'Error', 'Succeeded'];
127
let filtered: models.ResourceResult[] = [];
129
if (syncResult.resources && syncResult.resources.length > 0) {
130
filtered = syncResult.resources.filter(r => filters.length === 0 || filters.includes(getStatus(r)));
135
<div className='white-box'>
136
<div className='white-box__details'>
137
{operationAttributes.map(attr => (
138
<div className='row white-box__details-row' key={attr.title}>
139
<div className='columns small-3'>{attr.title}</div>
140
<div className='columns small-9'>{attr.value}</div>
145
{syncResult && syncResult.resources && syncResult.resources.length > 0 && (
147
<div style={{display: 'flex'}}>
148
<label style={{display: 'block', marginBottom: '1em'}}>RESULT</label>
149
<div style={{marginLeft: 'auto'}}>
150
<Filter options={Statuses} filters={filters} setFilters={setFilters} title='STATUS' style={{marginRight: '5px'}} />
151
<Filter options={OperationPhases} filters={filters} setFilters={setFilters} title='HOOK' />
154
<div className='argo-table-list'>
155
<div className='argo-table-list__head'>
156
<div className='row'>
157
<div className='columns large-1 show-for-large application-operation-state__icons_container_padding'>KIND</div>
158
<div className='columns large-2 show-for-large'>NAMESPACE</div>
159
<div className='columns large-2 small-2'>NAME</div>
160
<div className='columns large-1 small-2'>STATUS</div>
161
<div className='columns large-1 show-for-large'>HOOK</div>
162
<div className='columns large-4 small-8'>MESSAGE</div>
165
{filtered.length > 0 ? (
166
filtered.map((resource, i) => (
167
<div className='argo-table-list__row' key={i}>
168
<div className='row'>
169
<div className='columns large-1 show-for-large application-operation-state__icons_container_padding'>
170
<div className='application-operation-state__icons_container'>
171
{resource.hookType && <i title='Resource lifecycle hook' className='fa fa-anchor' />}
173
<span title={getKind(resource)}>{getKind(resource)}</span>
175
<div className='columns large-2 show-for-large' title={resource.namespace}>
178
<div className='columns large-2 small-2' title={resource.name}>
181
<div className='columns large-1 small-2' title={getStatus(resource)}>
182
<utils.ResourceResultIcon resource={resource} /> {getStatus(resource)}
184
<div className='columns large-1 show-for-large' title={resource.hookType}>
187
<div className='columns large-4 small-8' title={resource.message}>
188
<div className='application-operation-state__message'>{resource.message}</div>
194
<div style={{textAlign: 'center', marginTop: '2em', fontSize: '20px'}}>No Sync Results match filter</div>
203
const getKind = (resource: models.ResourceResult): string => {
204
return (resource.group ? `${resource.group}/${resource.version}` : resource.version) + `/${resource.kind}`;
207
const getStatus = (resource: models.ResourceResult): string => {
208
return resource.hookType ? resource.hookPhase : resource.status;
211
ApplicationOperationState.contextTypes = {
212
apis: PropTypes.object