github.com/argoproj/argo-cd/v3@v3.2.1/ui/src/app/applications/components/application-details/application-resource-list.tsx (about)

     1  import {DropDown, Tooltip} from 'argo-ui';
     2  import * as React from 'react';
     3  import * as classNames from 'classnames';
     4  import * as models from '../../../shared/models';
     5  import {ResourceIcon} from '../resource-icon';
     6  import {ResourceLabel} from '../resource-label';
     7  import {ComparisonStatusIcon, HealthStatusIcon, nodeKey, isSameNode, createdOrNodeKey} from '../utils';
     8  import {AppDetailsPreferences} from '../../../shared/services';
     9  import {Consumer} from '../../../shared/context';
    10  import Moment from 'react-moment';
    11  import {format} from 'date-fns';
    12  import {HealthPriority, ResourceNode, SyncPriority, SyncStatusCode} from '../../../shared/models';
    13  import './application-resource-list.scss';
    14  
    15  export interface ApplicationResourceListProps {
    16      pref: AppDetailsPreferences;
    17      resources: models.ResourceStatus[];
    18      onNodeClick?: (fullName: string) => any;
    19      nodeMenu?: (node: models.ResourceNode) => React.ReactNode;
    20      tree?: models.ApplicationTree;
    21  }
    22  
    23  export const ApplicationResourceList = (props: ApplicationResourceListProps) => {
    24      const nodeByKey = new Map<string, models.ResourceNode>();
    25      props.tree?.nodes?.forEach(res => nodeByKey.set(nodeKey(res), res));
    26  
    27      const [sortConfig, setSortConfig] = React.useState<{key: string; direction: 'asc' | 'desc'}>({key: 'createdAt', direction: 'desc'});
    28  
    29      const handleSort = (key: string) => {
    30          setSortConfig(prevConfig => {
    31              if (prevConfig.key !== key) {
    32                  return {key, direction: 'asc'};
    33              }
    34              return {key, direction: prevConfig.direction === 'asc' ? 'desc' : 'asc'};
    35          });
    36      };
    37  
    38      const getSortArrow = (key: string) => {
    39          if (sortConfig.key !== key) {
    40              return null;
    41          }
    42  
    43          const isAsc = sortConfig.direction === 'asc';
    44          const style: React.CSSProperties = {
    45              position: 'relative',
    46              top: isAsc ? '2px' : '-2px'
    47          };
    48          return (
    49              <span style={style}>
    50                  <i className={isAsc ? 'fa fa-sort-up' : 'fa fa-sort-down'} />
    51              </span>
    52          );
    53      };
    54  
    55      const sortedResources = React.useMemo(() => {
    56          const resourcesToSort = [...props.resources];
    57          resourcesToSort.sort((a, b) => {
    58              let compare = 0;
    59              switch (sortConfig.key) {
    60                  case 'name':
    61                      compare = a.name.localeCompare(b.name);
    62                      break;
    63  
    64                  case 'group-kind':
    65                      {
    66                          const groupKindA = [a.group, a.kind].filter(item => !!item).join('/');
    67                          const groupKindB = [b.group, b.kind].filter(item => !!item).join('/');
    68                          compare = groupKindA.localeCompare(groupKindB);
    69                      }
    70                      break;
    71  
    72                  case 'syncOrder':
    73                      {
    74                          const waveA = a.syncWave ?? 0;
    75                          const waveB = b.syncWave ?? 0;
    76                          compare = waveA - waveB;
    77                      }
    78                      break;
    79                  case 'namespace':
    80                      {
    81                          const namespaceA = a.namespace ?? '';
    82                          const namespaceB = b.namespace ?? '';
    83                          compare = namespaceA.localeCompare(namespaceB);
    84                      }
    85                      break;
    86                  case 'createdAt':
    87                      {
    88                          compare = createdOrNodeKey(a).localeCompare(createdOrNodeKey(b), undefined, {numeric: true});
    89                      }
    90                      break;
    91                  case 'status':
    92                      {
    93                          const healthA = a.health?.status ?? 'Unknown';
    94                          const healthB = b.health?.status ?? 'Unknown';
    95                          const syncA = (a.status as SyncStatusCode) ?? 'Unknown';
    96                          const syncB = (b.status as SyncStatusCode) ?? 'Unknown';
    97  
    98                          compare = HealthPriority[healthA] - HealthPriority[healthB];
    99                          if (compare === 0) {
   100                              compare = SyncPriority[syncA] - SyncPriority[syncB];
   101                          }
   102                      }
   103                      break;
   104              }
   105              return sortConfig.direction === 'asc' ? compare : -compare;
   106          });
   107          return resourcesToSort;
   108      }, [props.resources, sortConfig]);
   109  
   110      const firstParentNode = props.resources.length > 0 && (nodeByKey.get(nodeKey(props.resources[0])) as ResourceNode)?.parentRefs?.[0];
   111      const isSameParent = firstParentNode && props.resources?.every(x => (nodeByKey.get(nodeKey(x)) as ResourceNode)?.parentRefs?.every(p => isSameNode(p, firstParentNode)));
   112      const isSameKind = props.resources?.every(x => x.group === props.resources[0].group && x.kind === props.resources[0].kind);
   113      const view = props.pref.view;
   114  
   115      const ParentRefDetails = () => {
   116          return isSameParent ? (
   117              <div className='resource-parent-node-info-title'>
   118                  <div>Parent Node Info</div>
   119                  <div className='resource-parent-node-info-title__label'>
   120                      <div>Name:</div>
   121                      <div>{firstParentNode.name}</div>
   122                  </div>
   123                  <div className='resource-parent-node-info-title__label'>
   124                      <div>Kind:</div>
   125                      <div>{firstParentNode.kind}</div>
   126                  </div>
   127              </div>
   128          ) : (
   129              <div />
   130          );
   131      };
   132      return (
   133          props.resources.length > 0 && (
   134              <div>
   135                  {/* Display only when the view is set to  or network */}
   136                  {(view === 'tree' || view === 'network') && (
   137                      <div className='resource-details__header' style={{paddingTop: '20px'}}>
   138                          <ParentRefDetails />
   139                      </div>
   140                  )}
   141                  <div className='argo-table-list argo-table-list--clickable'>
   142                      <div className='argo-table-list__head'>
   143                          <div className='row'>
   144                              <div className='columns small-1 xxxlarge-1' />
   145                              <div className='columns small-2 xxxlarge-2' onClick={() => handleSort('name')} style={{cursor: 'pointer'}}>
   146                                  NAME {getSortArrow('name')}
   147                              </div>
   148                              <div className='columns small-1 xxxlarge-1' onClick={() => handleSort('group-kind')} style={{cursor: 'pointer'}}>
   149                                  GROUP/KIND {getSortArrow('group-kind')}
   150                              </div>
   151                              <div className='columns small-1 xxxlarge-1' onClick={() => handleSort('syncOrder')} style={{cursor: 'pointer'}}>
   152                                  SYNC ORDER {getSortArrow('syncOrder')}
   153                              </div>
   154                              <div className='columns small-2 xxxlarge-1' onClick={() => handleSort('namespace')} style={{cursor: 'pointer'}}>
   155                                  NAMESPACE {getSortArrow('namespace')}
   156                              </div>
   157                              {isSameKind && props.resources[0].kind === 'ReplicaSet' && <div className='columns small-1 xxxlarge-1'>REVISION</div>}
   158                              <div className='columns small-2 xxxlarge-2' onClick={() => handleSort('createdAt')} style={{cursor: 'pointer'}}>
   159                                  CREATED AT {getSortArrow('createdAt')}
   160                              </div>
   161                              <div className='columns small-2 xxxlarge-1' onClick={() => handleSort('status')} style={{cursor: 'pointer'}}>
   162                                  STATUS {getSortArrow('status')}
   163                              </div>
   164                          </div>
   165                      </div>
   166                      {sortedResources.map(res => {
   167                          const groupkindjoin = [res.group, res.kind].filter(item => !!item).join('/');
   168                          return (
   169                              <div
   170                                  key={nodeKey(res)}
   171                                  className={classNames('argo-table-list__row', {
   172                                      'application-resource-tree__node--orphaned': res.orphaned
   173                                  })}
   174                                  onClick={() => props.onNodeClick && props.onNodeClick(nodeKey(res))}>
   175                                  <div className='row'>
   176                                      <div className='columns small-1 xxxlarge-1'>
   177                                          <div className='application-details__resource-icon'>
   178                                              <ResourceIcon kind={res.kind} />
   179                                              <br />
   180                                              <div>{ResourceLabel({kind: res.kind})}</div>
   181                                          </div>
   182                                      </div>
   183                                      <Tooltip content={res.name} enabled={!!res.name}>
   184                                          <div className='columns small-2 xxxlarge-2 application-details__item'>
   185                                              <span className='application-details__item_text'>{res.name}</span>
   186                                              {res.kind === 'Application' && (
   187                                                  <Consumer>
   188                                                      {ctx => (
   189                                                          <span className='application-details__external_link'>
   190                                                              <a
   191                                                                  href={ctx.baseHref + 'applications/' + res.namespace + '/' + res.name}
   192                                                                  onClick={e => e.stopPropagation()}
   193                                                                  title='Open application'>
   194                                                                  <i className='fa fa-external-link-alt' />
   195                                                              </a>
   196                                                          </span>
   197                                                      )}
   198                                                  </Consumer>
   199                                              )}
   200                                          </div>
   201                                      </Tooltip>
   202                                      <Tooltip content={groupkindjoin}>
   203                                          <div className='columns small-1 xxxlarge-1'>{groupkindjoin}</div>
   204                                      </Tooltip>
   205                                      <Tooltip content={res.syncWave} enabled={!!res.syncWave}>
   206                                          <div className='columns small-1 xxxlarge-1'>{res.syncWave || '-'}</div>
   207                                      </Tooltip>
   208                                      <Tooltip content={res.namespace} enabled={!!res.namespace}>
   209                                          <div className='columns small-2 xxxlarge-1'>{res.namespace}</div>
   210                                      </Tooltip>
   211                                      {isSameKind &&
   212                                          res.kind === 'ReplicaSet' &&
   213                                          ((nodeByKey.get(nodeKey(res)) as ResourceNode).info || [])
   214                                              .filter(tag => !tag.name.includes('Node'))
   215                                              .slice(0, 4)
   216                                              .map((tag, i) => {
   217                                                  return (
   218                                                      <div key={i} className='columns small-1 xxxlarge-1'>
   219                                                          {tag?.value?.split(':')[1] || '-'}
   220                                                      </div>
   221                                                  );
   222                                              })}
   223                                      <Tooltip content={res.createdAt} enabled={!!res.createdAt}>
   224                                          <div className='columns small-2 xxxlarge-2'>
   225                                              {res.createdAt && (
   226                                                  <span>
   227                                                      <Moment fromNow={true} ago={true}>
   228                                                          {res.createdAt}
   229                                                      </Moment>
   230                                                      &nbsp;ago &nbsp; {format(new Date(res.createdAt), 'MM/dd/yy')}
   231                                                  </span>
   232                                              )}
   233                                          </div>
   234                                      </Tooltip>
   235                                      <div className='columns small-2 xxxlarge-1'>
   236                                          {res.health && (
   237                                              <React.Fragment>
   238                                                  <HealthStatusIcon state={res.health} /> {res.health.status} &nbsp;
   239                                              </React.Fragment>
   240                                          )}
   241                                          {res.status && <ComparisonStatusIcon status={res.status} resource={res} label={true} />}
   242                                          {res.hook && <i title='Resource lifecycle hook' className='fa fa-anchor' />}
   243                                          {props.nodeMenu && (
   244                                              <div className='application-details__node-menu'>
   245                                                  <DropDown
   246                                                      isMenu={true}
   247                                                      anchor={() => (
   248                                                          <button className='argo-button argo-button--light argo-button--lg argo-button--short'>
   249                                                              <i className='fa fa-ellipsis-v' />
   250                                                          </button>
   251                                                      )}>
   252                                                      {() => props.nodeMenu(nodeByKey.get(nodeKey(res)))}
   253                                                  </DropDown>
   254                                              </div>
   255                                          )}
   256                                      </div>
   257                                  </div>
   258                              </div>
   259                          );
   260                      })}
   261                  </div>
   262              </div>
   263          )
   264      );
   265  };