github.com/argoproj/argo-cd/v2@v2.10.9/ui/src/app/applications/components/application-status-panel/application-status-panel.tsx (about) 1 import {HelpIcon} from 'argo-ui'; 2 import * as React from 'react'; 3 import {ARGO_GRAY6_COLOR, DataLoader} from '../../../shared/components'; 4 import {Revision} from '../../../shared/components/revision'; 5 import {Timestamp} from '../../../shared/components/timestamp'; 6 import * as models from '../../../shared/models'; 7 import {services} from '../../../shared/services'; 8 import {ApplicationSyncWindowStatusIcon, ComparisonStatusIcon, getAppDefaultSource, getAppOperationState} from '../utils'; 9 import {getConditionCategory, HealthStatusIcon, OperationState, syncStatusMessage, helpTip} from '../utils'; 10 import {RevisionMetadataPanel} from './revision-metadata-panel'; 11 12 import './application-status-panel.scss'; 13 14 interface Props { 15 application: models.Application; 16 showDiff?: () => any; 17 showOperation?: () => any; 18 showConditions?: () => any; 19 showExtension?: (id: string) => any; 20 showMetadataInfo?: (revision: string) => any; 21 } 22 23 interface SectionInfo { 24 title: string; 25 helpContent?: string; 26 } 27 28 const sectionLabel = (info: SectionInfo) => ( 29 <label style={{fontSize: '12px', fontWeight: 600, color: ARGO_GRAY6_COLOR}}> 30 {info.title} 31 {info.helpContent && <HelpIcon title={info.helpContent} />} 32 </label> 33 ); 34 35 const sectionHeader = (info: SectionInfo, hasMultipleSources: boolean, onClick?: () => any) => { 36 return ( 37 <div style={{display: 'flex', alignItems: 'center', marginBottom: '0.5em'}}> 38 {sectionLabel(info)} 39 {onClick && ( 40 <button className='application-status-panel__more-button' onClick={onClick} disabled={hasMultipleSources}> 41 {hasMultipleSources && helpTip('More details are not supported for apps with multiple sources')} 42 <i className='fa fa-ellipsis-h' /> 43 </button> 44 )} 45 </div> 46 ); 47 }; 48 49 export const ApplicationStatusPanel = ({application, showDiff, showOperation, showConditions, showExtension, showMetadataInfo}: Props) => { 50 const today = new Date(); 51 52 let daysSinceLastSynchronized = 0; 53 const history = application.status.history || []; 54 if (history.length > 0) { 55 const deployDate = new Date(history[history.length - 1].deployedAt); 56 daysSinceLastSynchronized = Math.round(Math.abs((today.getTime() - deployDate.getTime()) / (24 * 60 * 60 * 1000))); 57 } 58 const cntByCategory = (application.status.conditions || []).reduce( 59 (map, next) => map.set(getConditionCategory(next), (map.get(getConditionCategory(next)) || 0) + 1), 60 new Map<string, number>() 61 ); 62 const appOperationState = getAppOperationState(application); 63 if (application.metadata.deletionTimestamp && !appOperationState) { 64 showOperation = null; 65 } 66 67 const statusExtensions = services.extensions.getStatusPanelExtensions(); 68 69 const infos = cntByCategory.get('info'); 70 const warnings = cntByCategory.get('warning'); 71 const errors = cntByCategory.get('error'); 72 const source = getAppDefaultSource(application); 73 const hasMultipleSources = application.spec.sources && application.spec.sources.length > 0; 74 return ( 75 <div className='application-status-panel row'> 76 <div className='application-status-panel__item'> 77 <div style={{lineHeight: '19.5px', marginBottom: '0.3em'}}>{sectionLabel({title: 'APP HEALTH', helpContent: 'The health status of your app'})}</div> 78 <div className='application-status-panel__item-value'> 79 <HealthStatusIcon state={application.status.health} /> 80 81 {application.status.health.status} 82 </div> 83 {application.status.health.message && <div className='application-status-panel__item-name'>{application.status.health.message}</div>} 84 </div> 85 <div className='application-status-panel__item'> 86 <React.Fragment> 87 {sectionHeader( 88 { 89 title: 'SYNC STATUS', 90 helpContent: 'Whether or not the version of your app is up to date with your repo. You may wish to sync your app if it is out-of-sync.' 91 }, 92 hasMultipleSources, 93 () => showMetadataInfo(application.status.sync ? application.status.sync.revision : '') 94 )} 95 <div className={`application-status-panel__item-value${appOperationState?.phase ? ` application-status-panel__item-value--${appOperationState.phase}` : ''}`}> 96 <div> 97 {application.status.sync.status === models.SyncStatuses.OutOfSync ? ( 98 <a onClick={() => showDiff && showDiff()}> 99 <ComparisonStatusIcon status={application.status.sync.status} label={true} /> 100 </a> 101 ) : ( 102 <ComparisonStatusIcon status={application.status.sync.status} label={true} /> 103 )} 104 </div> 105 <div className='application-status-panel__item-value__revision show-for-large'>{syncStatusMessage(application)}</div> 106 </div> 107 <div className='application-status-panel__item-name' style={{marginBottom: '0.5em'}}> 108 {application.spec.syncPolicy?.automated ? 'Auto sync is enabled.' : 'Auto sync is not enabled.'} 109 </div> 110 {application.status && application.status.sync && application.status.sync.revision && !application.spec.source.chart && ( 111 <div className='application-status-panel__item-name'> 112 <RevisionMetadataPanel 113 appName={application.metadata.name} 114 appNamespace={application.metadata.namespace} 115 type={source.chart && 'helm'} 116 revision={application.status.sync.revision} 117 /> 118 </div> 119 )} 120 </React.Fragment> 121 </div> 122 {appOperationState && ( 123 <div className='application-status-panel__item'> 124 <React.Fragment> 125 {sectionHeader( 126 { 127 title: 'LAST SYNC', 128 helpContent: 129 'Whether or not your last app sync was successful. It has been ' + 130 daysSinceLastSynchronized + 131 ' days since last sync. Click for the status of that sync.' 132 }, 133 hasMultipleSources, 134 () => showMetadataInfo(appOperationState.syncResult ? appOperationState.syncResult.revision : '') 135 )} 136 <div className={`application-status-panel__item-value application-status-panel__item-value--${appOperationState.phase}`}> 137 <a onClick={() => showOperation && showOperation()}> 138 <OperationState app={application} />{' '} 139 </a> 140 {appOperationState.syncResult && appOperationState.syncResult.revision && ( 141 <div className='application-status-panel__item-value__revision show-for-large'> 142 to <Revision repoUrl={source.repoURL} revision={appOperationState.syncResult.revision} /> 143 </div> 144 )} 145 </div> 146 147 <div className='application-status-panel__item-name' style={{marginBottom: '0.5em'}}> 148 {appOperationState.phase} <Timestamp date={appOperationState.finishedAt || appOperationState.startedAt} /> 149 </div> 150 {(appOperationState.syncResult && appOperationState.syncResult.revision && ( 151 <RevisionMetadataPanel 152 appName={application.metadata.name} 153 appNamespace={application.metadata.namespace} 154 type={source.chart && 'helm'} 155 revision={appOperationState.syncResult.revision} 156 /> 157 )) || <div className='application-status-panel__item-name'>{appOperationState.message}</div>} 158 </React.Fragment> 159 </div> 160 )} 161 {application.status.conditions && ( 162 <div className={`application-status-panel__item`}> 163 {sectionLabel({title: 'APP CONDITIONS'})} 164 <div className='application-status-panel__item-value application-status-panel__conditions' onClick={() => showConditions && showConditions()}> 165 {infos && ( 166 <a className='info'> 167 <i className='fa fa-info-circle' /> {infos} Info 168 </a> 169 )} 170 {warnings && ( 171 <a className='warning'> 172 <i className='fa fa-exclamation-triangle' /> {warnings} Warning{warnings !== 1 && 's'} 173 </a> 174 )} 175 {errors && ( 176 <a className='error'> 177 <i className='fa fa-exclamation-circle' /> {errors} Error{errors !== 1 && 's'} 178 </a> 179 )} 180 </div> 181 </div> 182 )} 183 <DataLoader 184 noLoaderOnInputChange={true} 185 input={application} 186 load={async app => { 187 return await services.applications.getApplicationSyncWindowState(app.metadata.name, app.metadata.namespace); 188 }}> 189 {(data: models.ApplicationSyncWindowState) => ( 190 <React.Fragment> 191 {data.assignedWindows && ( 192 <div className='application-status-panel__item' style={{position: 'relative'}}> 193 {sectionLabel({ 194 title: 'SYNC WINDOWS', 195 helpContent: 196 'The aggregate state of sync windows for this app. ' + 197 'Red: no syncs allowed. ' + 198 'Yellow: manual syncs allowed. ' + 199 'Green: all syncs allowed' 200 })} 201 <div className='application-status-panel__item-value' style={{margin: 'auto 0'}}> 202 <ApplicationSyncWindowStatusIcon project={application.spec.project} state={data} /> 203 </div> 204 </div> 205 )} 206 </React.Fragment> 207 )} 208 </DataLoader> 209 {statusExtensions && statusExtensions.map(ext => <ext.component key={ext.title} application={application} openFlyout={() => showExtension && showExtension(ext.id)} />)} 210 </div> 211 ); 212 };