github.com/argoproj/argo-cd@v1.8.7/ui/src/app/applications/components/application-operation-state/application-operation-state.tsx (about) 1 import {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'; 5 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'; 11 12 require('./application-operation-state.scss'); 13 14 interface Props { 15 application: models.Application; 16 operationState: models.OperationState; 17 } 18 19 export const ApplicationOperationState: React.StatelessComponent<Props> = ({application, operationState}, ctx: AppContext) => { 20 const operationAttributes = [ 21 {title: 'OPERATION', value: utils.getOperationType(application)}, 22 {title: 'PHASE', value: operationState.phase}, 23 ...(operationState.message ? [{title: 'MESSAGE', value: operationState.message}] : []), 24 {title: 'STARTED AT', value: <Timestamp date={operationState.startedAt} />}, 25 { 26 title: 'DURATION', 27 value: ( 28 <Ticker> 29 {time => <Duration durationMs={((operationState.finishedAt && moment(operationState.finishedAt)) || time).diff(moment(operationState.startedAt)) / 1000} />} 30 </Ticker> 31 ) 32 } 33 ]; 34 35 if (operationState.finishedAt && operationState.phase !== 'Running') { 36 operationAttributes.push({title: 'FINISHED AT', value: <Timestamp date={operationState.finishedAt} />}); 37 } else if (operationState.phase !== 'Terminating') { 38 operationAttributes.push({ 39 title: '', 40 value: ( 41 <button 42 className='argo-button argo-button--base' 43 onClick={async () => { 44 const confirmed = await ctx.apis.popup.confirm('Terminate operation', 'Are you sure you want to terminate operation?'); 45 if (confirmed) { 46 try { 47 await services.applications.terminateOperation(application.metadata.name); 48 } catch (e) { 49 ctx.apis.notifications.show({ 50 content: <ErrorNotification title='Unable to terminate operation' e={e} />, 51 type: NotificationType.Error 52 }); 53 } 54 } 55 }}> 56 Terminate 57 </button> 58 ) 59 }); 60 } 61 if (operationState.syncResult) { 62 operationAttributes.push({title: 'REVISION', value: <Revision repoUrl={application.spec.source.repoURL} revision={operationState.syncResult.revision} />}); 63 } 64 let initiator = ''; 65 if (operationState.operation.initiatedBy) { 66 if (operationState.operation.initiatedBy.automated) { 67 initiator = 'automated sync policy'; 68 } else { 69 initiator = operationState.operation.initiatedBy.username; 70 } 71 } 72 operationAttributes.push({title: 'INITIATED BY', value: initiator || 'Unknown'}); 73 74 const resultAttributes: {title: string; value: string}[] = []; 75 const syncResult = operationState.syncResult; 76 if (operationState.finishedAt) { 77 if (syncResult) { 78 (syncResult.resources || []).forEach(res => { 79 resultAttributes.push({ 80 title: `${res.namespace}/${res.kind}:${res.name}`, 81 value: res.message 82 }); 83 }); 84 } 85 } 86 87 return ( 88 <div> 89 <div className='white-box'> 90 <div className='white-box__details'> 91 {operationAttributes.map(attr => ( 92 <div className='row white-box__details-row' key={attr.title}> 93 <div className='columns small-3'>{attr.title}</div> 94 <div className='columns small-9'>{attr.value}</div> 95 </div> 96 ))} 97 </div> 98 </div> 99 {syncResult && syncResult.resources && syncResult.resources.length > 0 && ( 100 <React.Fragment> 101 <h4>Result:</h4> 102 <div className='argo-table-list'> 103 <div className='argo-table-list__head'> 104 <div className='row'> 105 <div className='columns large-1 show-for-large application-operation-state__icons_container_padding'>KIND</div> 106 <div className='columns large-2 show-for-large'>NAMESPACE</div> 107 <div className='columns large-2 small-2'>NAME</div> 108 <div className='columns large-1 small-2'>STATUS</div> 109 <div className='columns large-1 show-for-large'>HOOK</div> 110 <div className='columns large-4 small-8'>MESSAGE</div> 111 </div> 112 </div> 113 {syncResult.resources.map((resource, i) => ( 114 <div className='argo-table-list__row' key={i}> 115 <div className='row'> 116 <div className='columns large-1 show-for-large application-operation-state__icons_container_padding'> 117 <div className='application-operation-state__icons_container'> 118 {resource.hookType && <i title='Resource lifecycle hook' className='fa fa-anchor' />} 119 </div> 120 <span title={getKind(resource)}>{getKind(resource)}</span> 121 </div> 122 <div className='columns large-2 show-for-large' title={resource.namespace}> 123 {resource.namespace} 124 </div> 125 <div className='columns large-2 small-2' title={resource.name}> 126 {resource.name} 127 </div> 128 <div className='columns large-1 small-2' title={getStatus(resource)}> 129 <utils.ResourceResultIcon resource={resource} /> {getStatus(resource)} 130 </div> 131 <div className='columns large-1 show-for-large' title={resource.hookType}> 132 {resource.hookType} 133 </div> 134 <div className='columns large-4 small-8' title={resource.message}> 135 <div className='application-operation-state__message'>{resource.message}</div> 136 </div> 137 </div> 138 </div> 139 ))} 140 </div> 141 </React.Fragment> 142 )} 143 </div> 144 ); 145 }; 146 147 const getKind = (resource: models.ResourceResult): string => { 148 return (resource.group ? `${resource.group}/${resource.version}` : resource.version) + `/${resource.kind}`; 149 }; 150 151 const getStatus = (resource: models.ResourceResult): string => { 152 return resource.hookType ? resource.hookPhase : resource.status; 153 }; 154 155 ApplicationOperationState.contextTypes = { 156 apis: PropTypes.object 157 };