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                      &nbsp;
    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  };