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

     1  import {DataLoader, Tooltip} from 'argo-ui';
     2  import * as classNames from 'classnames';
     3  import * as React from 'react';
     4  import {Key, KeybindingContext, NumKey, NumKeyToNumber, NumPadKey, useNav} from 'argo-ui/v2';
     5  import {Cluster} from '../../../shared/components';
     6  import {Consumer, Context, AuthSettingsCtx} from '../../../shared/context';
     7  import * as models from '../../../shared/models';
     8  import {ApplicationURLs} from '../application-urls';
     9  import * as AppUtils from '../utils';
    10  import {getAppDefaultSource, OperationState} from '../utils';
    11  import {services} from '../../../shared/services';
    12  
    13  import './applications-tiles.scss';
    14  
    15  export interface ApplicationTilesProps {
    16      applications: models.Application[];
    17      syncApplication: (appName: string, appNamespace: string) => any;
    18      refreshApplication: (appName: string, appNamespace: string) => any;
    19      deleteApplication: (appName: string, appNamespace: string) => any;
    20  }
    21  
    22  const useItemsPerContainer = (itemRef: any, containerRef: any): number => {
    23      const [itemsPer, setItemsPer] = React.useState(0);
    24  
    25      React.useEffect(() => {
    26          const handleResize = () => {
    27              let timeoutId: any;
    28              clearTimeout(timeoutId);
    29              timeoutId = setTimeout(() => {
    30                  timeoutId = null;
    31                  const itemWidth = itemRef.current ? itemRef.current.offsetWidth : -1;
    32                  const containerWidth = containerRef.current ? containerRef.current.offsetWidth : -1;
    33                  const curItemsPer = containerWidth > 0 && itemWidth > 0 ? Math.floor(containerWidth / itemWidth) : 1;
    34                  if (curItemsPer !== itemsPer) {
    35                      setItemsPer(curItemsPer);
    36                  }
    37              }, 1000);
    38          };
    39          window.addEventListener('resize', handleResize);
    40          handleResize();
    41          return () => {
    42              window.removeEventListener('resize', handleResize);
    43          };
    44      }, []);
    45  
    46      return itemsPer || 1;
    47  };
    48  
    49  export const ApplicationTiles = ({applications, syncApplication, refreshApplication, deleteApplication}: ApplicationTilesProps) => {
    50      const [selectedApp, navApp, reset] = useNav(applications.length);
    51  
    52      const ctxh = React.useContext(Context);
    53      const appRef = {ref: React.useRef(null), set: false};
    54      const appContainerRef = React.useRef(null);
    55      const appsPerRow = useItemsPerContainer(appRef.ref, appContainerRef);
    56      const useAuthSettingsCtx = React.useContext(AuthSettingsCtx);
    57  
    58      const {useKeybinding} = React.useContext(KeybindingContext);
    59  
    60      useKeybinding({keys: Key.RIGHT, action: () => navApp(1)});
    61      useKeybinding({keys: Key.LEFT, action: () => navApp(-1)});
    62      useKeybinding({keys: Key.DOWN, action: () => navApp(appsPerRow)});
    63      useKeybinding({keys: Key.UP, action: () => navApp(-1 * appsPerRow)});
    64  
    65      useKeybinding({
    66          keys: Key.ENTER,
    67          action: () => {
    68              if (selectedApp > -1) {
    69                  ctxh.navigation.goto(`/${AppUtils.getAppUrl(applications[selectedApp])}`);
    70                  return true;
    71              }
    72              return false;
    73          }
    74      });
    75  
    76      useKeybinding({
    77          keys: Key.ESCAPE,
    78          action: () => {
    79              if (selectedApp > -1) {
    80                  reset();
    81                  return true;
    82              }
    83              return false;
    84          }
    85      });
    86  
    87      useKeybinding({
    88          keys: Object.values(NumKey) as NumKey[],
    89          action: n => {
    90              reset();
    91              return navApp(NumKeyToNumber(n));
    92          }
    93      });
    94      useKeybinding({
    95          keys: Object.values(NumPadKey) as NumPadKey[],
    96          action: n => {
    97              reset();
    98              return navApp(NumKeyToNumber(n));
    99          }
   100      });
   101      return (
   102          <Consumer>
   103              {ctx => (
   104                  <DataLoader load={() => services.viewPreferences.getPreferences()}>
   105                      {pref => {
   106                          const favList = pref.appList.favoritesAppList || [];
   107                          return (
   108                              <div className='applications-tiles argo-table-list argo-table-list--clickable' ref={appContainerRef}>
   109                                  {applications.map((app, i) => {
   110                                      const source = getAppDefaultSource(app);
   111                                      const isOci = source?.repoURL?.startsWith('oci://');
   112                                      const targetRevision = source ? source.targetRevision || 'HEAD' : 'Unknown';
   113                                      return (
   114                                          <div
   115                                              key={AppUtils.appInstanceName(app)}
   116                                              ref={appRef.set ? null : appRef.ref}
   117                                              className={`argo-table-list__row applications-list__entry applications-list__entry--health-${app.status.health.status} ${
   118                                                  selectedApp === i ? 'applications-tiles__selected' : ''
   119                                              }`}>
   120                                              <div
   121                                                  className='row applications-tiles__wrapper'
   122                                                  onClick={e => ctx.navigation.goto(`/${AppUtils.getAppUrl(app)}`, {view: pref.appDetails.view}, {event: e})}>
   123                                                  <div
   124                                                      className={`columns small-12 applications-list__info qe-applications-list-${AppUtils.appInstanceName(
   125                                                          app
   126                                                      )} applications-tiles__item`}>
   127                                                      <div className='row '>
   128                                                          <div className={app.status.summary.externalURLs?.length > 0 ? 'columns small-10' : 'columns small-11'}>
   129                                                              <i
   130                                                                  className={
   131                                                                      'icon argo-icon-' + (source?.chart != null ? 'helm' : isOci ? 'oci applications-tiles__item__small' : 'git')
   132                                                                  }
   133                                                              />
   134                                                              <Tooltip content={AppUtils.appInstanceName(app)}>
   135                                                                  <span className='applications-list__title'>
   136                                                                      {AppUtils.appQualifiedName(app, useAuthSettingsCtx?.appsInAnyNamespaceEnabled)}
   137                                                                  </span>
   138                                                              </Tooltip>
   139                                                          </div>
   140                                                          <div className={app.status.summary.externalURLs?.length > 0 ? 'columns small-2' : 'columns small-1'}>
   141                                                              <div className='applications-list__external-link'>
   142                                                                  <ApplicationURLs urls={app.status.summary.externalURLs} />
   143                                                                  <Tooltip content={favList?.includes(app.metadata.name) ? 'Remove Favorite' : 'Add Favorite'}>
   144                                                                      <button
   145                                                                          className='large-text-height'
   146                                                                          onClick={e => {
   147                                                                              e.stopPropagation();
   148                                                                              favList?.includes(app.metadata.name)
   149                                                                                  ? favList.splice(favList.indexOf(app.metadata.name), 1)
   150                                                                                  : favList.push(app.metadata.name);
   151                                                                              services.viewPreferences.updatePreferences({appList: {...pref.appList, favoritesAppList: favList}});
   152                                                                          }}>
   153                                                                          <i
   154                                                                              className={favList?.includes(app.metadata.name) ? 'fas fa-star fa-lg' : 'far fa-star fa-lg'}
   155                                                                              style={{
   156                                                                                  cursor: 'pointer',
   157                                                                                  marginLeft: '7px',
   158                                                                                  color: favList?.includes(app.metadata.name) ? '#FFCE25' : '#8fa4b1'
   159                                                                              }}
   160                                                                          />
   161                                                                      </button>
   162                                                                  </Tooltip>
   163                                                              </div>
   164                                                          </div>
   165                                                      </div>
   166                                                      <div className='row'>
   167                                                          <div className='columns small-3' title='Project:'>
   168                                                              Project:
   169                                                          </div>
   170                                                          <div className='columns small-9'>{app.spec.project}</div>
   171                                                      </div>
   172                                                      <div className='row'>
   173                                                          <div className='columns small-3' title='Labels:'>
   174                                                              Labels:
   175                                                          </div>
   176                                                          <div className='columns small-9'>
   177                                                              <Tooltip
   178                                                                  zIndex={4}
   179                                                                  content={
   180                                                                      <div>
   181                                                                          {Object.keys(app.metadata.labels || {})
   182                                                                              .map(label => ({label, value: app.metadata.labels[label]}))
   183                                                                              .map(item => (
   184                                                                                  <div key={item.label}>
   185                                                                                      {item.label}={item.value}
   186                                                                                  </div>
   187                                                                              ))}
   188                                                                      </div>
   189                                                                  }>
   190                                                                  <span>
   191                                                                      {Object.keys(app.metadata.labels || {})
   192                                                                          .map(label => `${label}=${app.metadata.labels[label]}`)
   193                                                                          .join(', ')}
   194                                                                  </span>
   195                                                              </Tooltip>
   196                                                          </div>
   197                                                      </div>
   198                                                      <div className='row'>
   199                                                          <div className='columns small-3' title='Status:'>
   200                                                              Status:
   201                                                          </div>
   202                                                          <div className='columns small-9' qe-id='applications-tiles-health-status'>
   203                                                              <AppUtils.HealthStatusIcon state={app.status.health} /> {app.status.health.status}
   204                                                              &nbsp;
   205                                                              {app.status.sourceHydrator?.currentOperation && (
   206                                                                  <>
   207                                                                      <AppUtils.HydrateOperationPhaseIcon operationState={app.status.sourceHydrator.currentOperation} />{' '}
   208                                                                      {app.status.sourceHydrator.currentOperation.phase}
   209                                                                      &nbsp;
   210                                                                  </>
   211                                                              )}
   212                                                              <AppUtils.ComparisonStatusIcon status={app.status.sync.status} /> {app.status.sync.status}
   213                                                              &nbsp;
   214                                                              <OperationState app={app} quiet={true} />
   215                                                          </div>
   216                                                      </div>
   217                                                      <div className='row'>
   218                                                          <div className='columns small-3' title='Repository:'>
   219                                                              Repository:
   220                                                          </div>
   221                                                          <div className='columns small-9'>
   222                                                              <Tooltip content={source?.repoURL} zIndex={4}>
   223                                                                  <span>{source?.repoURL}</span>
   224                                                              </Tooltip>
   225                                                          </div>
   226                                                      </div>
   227                                                      <div className='row'>
   228                                                          <div className='columns small-3' title='Target Revision:'>
   229                                                              Target Revision:
   230                                                          </div>
   231                                                          <div className='columns small-9'>{targetRevision}</div>
   232                                                      </div>
   233                                                      {source?.path && (
   234                                                          <div className='row'>
   235                                                              <div className='columns small-3' title='Path:'>
   236                                                                  Path:
   237                                                              </div>
   238                                                              <div className='columns small-9'>{source?.path}</div>
   239                                                          </div>
   240                                                      )}
   241                                                      {source?.chart && (
   242                                                          <div className='row'>
   243                                                              <div className='columns small-3' title='Chart:'>
   244                                                                  Chart:
   245                                                              </div>
   246                                                              <div className='columns small-9'>{source?.chart}</div>
   247                                                          </div>
   248                                                      )}
   249                                                      <div className='row'>
   250                                                          <div className='columns small-3' title='Destination:'>
   251                                                              Destination:
   252                                                          </div>
   253                                                          <div className='columns small-9'>
   254                                                              <Cluster server={app.spec.destination.server} name={app.spec.destination.name} />
   255                                                          </div>
   256                                                      </div>
   257                                                      <div className='row'>
   258                                                          <div className='columns small-3' title='Namespace:'>
   259                                                              Namespace:
   260                                                          </div>
   261                                                          <div className='columns small-9'>{app.spec.destination.namespace}</div>
   262                                                      </div>
   263                                                      <div className='row'>
   264                                                          <div className='columns small-3' title='Age:'>
   265                                                              Created At:
   266                                                          </div>
   267                                                          <div className='columns small-9'>{AppUtils.formatCreationTimestamp(app.metadata.creationTimestamp)}</div>
   268                                                      </div>
   269                                                      {app.status.operationState && (
   270                                                          <div className='row'>
   271                                                              <div className='columns small-3' title='Last sync:'>
   272                                                                  Last Sync:
   273                                                              </div>
   274                                                              <div className='columns small-9'>
   275                                                                  {AppUtils.formatCreationTimestamp(app.status.operationState.finishedAt || app.status.operationState.startedAt)}
   276                                                              </div>
   277                                                          </div>
   278                                                      )}
   279                                                      <div className='row applications-tiles__actions'>
   280                                                          <div className='columns applications-list__entry--actions'>
   281                                                              <a
   282                                                                  className='argo-button argo-button--base'
   283                                                                  qe-id='applications-tiles-button-sync'
   284                                                                  onClick={e => {
   285                                                                      e.stopPropagation();
   286                                                                      syncApplication(app.metadata.name, app.metadata.namespace);
   287                                                                  }}>
   288                                                                  <i className='fa fa-sync' /> Sync
   289                                                              </a>
   290                                                              &nbsp;
   291                                                              <Tooltip className='custom-tooltip' content={'Refresh'}>
   292                                                                  <a
   293                                                                      className='argo-button argo-button--base'
   294                                                                      qe-id='applications-tiles-button-refresh'
   295                                                                      {...AppUtils.refreshLinkAttrs(app)}
   296                                                                      onClick={e => {
   297                                                                          e.stopPropagation();
   298                                                                          refreshApplication(app.metadata.name, app.metadata.namespace);
   299                                                                      }}>
   300                                                                      <i className={classNames('fa fa-redo', {'status-icon--spin': AppUtils.isAppRefreshing(app)})} />{' '}
   301                                                                      <span className='show-for-xxlarge'>Refresh</span>
   302                                                                  </a>
   303                                                              </Tooltip>
   304                                                              &nbsp;
   305                                                              <Tooltip className='custom-tooltip' content={'Delete'}>
   306                                                                  <a
   307                                                                      className='argo-button argo-button--base'
   308                                                                      qe-id='applications-tiles-button-delete'
   309                                                                      onClick={e => {
   310                                                                          e.stopPropagation();
   311                                                                          deleteApplication(app.metadata.name, app.metadata.namespace);
   312                                                                      }}>
   313                                                                      <i className='fa fa-times-circle' /> <span className='show-for-xxlarge'>Delete</span>
   314                                                                  </a>
   315                                                              </Tooltip>
   316                                                          </div>
   317                                                      </div>
   318                                                  </div>
   319                                              </div>
   320                                          </div>
   321                                      );
   322                                  })}
   323                              </div>
   324                          );
   325                      }}
   326                  </DataLoader>
   327              )}
   328          </Consumer>
   329      );
   330  };