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

     1  import {Checkbox, DataLoader, Tab, Tabs} from 'argo-ui';
     2  import classNames from 'classnames';
     3  import * as deepMerge from 'deepmerge';
     4  import * as React from 'react';
     5  
     6  import {YamlEditor, ClipboardText} from '../../../shared/components';
     7  import {DeepLinks} from '../../../shared/components/deep-links';
     8  import * as models from '../../../shared/models';
     9  import {services} from '../../../shared/services';
    10  import {ResourceTreeNode} from '../application-resource-tree/application-resource-tree';
    11  import {ApplicationResourcesDiff} from '../application-resources-diff/application-resources-diff';
    12  import {ComparisonStatusIcon, formatCreationTimestamp, getPodReadinessGatesState, getPodStateReason, HealthStatusIcon} from '../utils';
    13  import './application-node-info.scss';
    14  import {ReadinessGatesNotPassedWarning} from './readiness-gates-not-passed-warning';
    15  import Moment from 'react-moment';
    16  
    17  const RenderContainerState = (props: {container: any}) => {
    18      const state = (props.container.state?.waiting && 'waiting') || (props.container.state?.terminated && 'terminated') || (props.container.state?.running && 'running');
    19      const status = props.container.state.waiting?.reason || props.container.state.terminated?.reason || props.container.state.running?.reason;
    20      const lastState = props.container.lastState?.terminated;
    21      const msg = props.container.state.waiting?.message || props.container.state.terminated?.message || props.container.state.running?.message;
    22  
    23      return (
    24          <div className='application-node-info__container'>
    25              <div className='application-node-info__container--name'>
    26                  {props.container.state?.running ? (
    27                      <span style={{marginRight: '4px'}}>
    28                          <i className='fa fa-check-circle' style={{color: 'rgb(24, 190, 148)'}} />
    29                      </span>
    30                  ) : (
    31                      (props.container.state.terminated && props.container.state.terminated?.exitCode !== 0) ||
    32                      (lastState && lastState?.exitCode !== 0 && (
    33                          <span style={{marginRight: '4px'}}>
    34                              <i className='fa fa-times-circle' style={{color: 'red'}} />
    35                          </span>
    36                      ))
    37                  )}
    38                  {props.container.name}
    39              </div>
    40              <div>
    41                  {state && (
    42                      <>
    43                          Container is <span className='application-node-info__container--highlight'>{state}</span>
    44                          {status && ' because of '}
    45                      </>
    46                  )}
    47                  <span title={msg || ''}>
    48                      {status && (
    49                          <span
    50                              className={classNames('application-node-info__container--highlight', {
    51                                  'application-node-info__container--hint': !!msg
    52                              })}>
    53                              {status}
    54                          </span>
    55                      )}
    56                  </span>
    57                  {'.'}
    58                  {(props.container.state.terminated?.exitCode === 0 || props.container.state.terminated?.exitCode) && (
    59                      <>
    60                          {' '}
    61                          It exited with <span className='application-node-info__container--highlight'>exit code {props.container.state.terminated.exitCode}.</span>
    62                      </>
    63                  )}{' '}
    64                  It is <span className='application-node-info__container--highlight'>{props.container?.started ? 'started' : 'not started'}</span>
    65                  <span className='application-node-info__container--highlight'>{status === 'Completed' ? '.' : props.container?.ready ? ' and ready.' : ' and not ready.'}</span>
    66                  <br />
    67                  {lastState && (
    68                      <>
    69                          <>
    70                              The container last terminated{' '}
    71                              <span className='application-node-info__container--highlight'>
    72                                  <Moment fromNow={true} ago={true}>
    73                                      {lastState.finishedAt}
    74                                  </Moment>{' '}
    75                                  ago with exit code {lastState?.exitCode}
    76                              </span>
    77                          </>
    78                          {lastState?.reason && ' because of '}
    79                          <span title={props.container.lastState?.message || ''}>
    80                              {lastState?.reason && (
    81                                  <span
    82                                      className={classNames('application-node-info__container--highlight', {
    83                                          'application-node-info__container--hint': !!props.container.lastState?.message
    84                                      })}>
    85                                      {lastState?.reason}
    86                                  </span>
    87                              )}
    88                          </span>
    89                          {'.'}
    90                      </>
    91                  )}
    92              </div>
    93          </div>
    94      );
    95  };
    96  
    97  export const ApplicationNodeInfo = (props: {
    98      application: models.Application;
    99      node: models.ResourceNode;
   100      live: models.State;
   101      links: models.LinksResponse;
   102      controlled: {summary: models.ResourceStatus; state: models.ResourceDiff};
   103  }) => {
   104      const attributes: {title: string; value: any}[] = [
   105          {title: 'KIND', value: props.node.kind},
   106          {title: 'NAME', value: <ClipboardText text={props.node.name} />},
   107          {title: 'NAMESPACE', value: <ClipboardText text={props.node.namespace} />}
   108      ];
   109      if (props.node.createdAt) {
   110          attributes.push({
   111              title: 'CREATED AT',
   112              value: formatCreationTimestamp(props.node.createdAt)
   113          });
   114      }
   115      if ((props.node.images || []).length) {
   116          attributes.push({
   117              title: 'IMAGES',
   118              value: (
   119                  <div className='application-node-info__labels'>
   120                      {(props.node.images || []).sort().map(image => (
   121                          <span className='application-node-info__label' key={image}>
   122                              {image}
   123                          </span>
   124                      ))}
   125                  </div>
   126              )
   127          });
   128      }
   129  
   130      if (props.live) {
   131          if (props.node.kind === 'Pod') {
   132              const {reason, message, netContainerStatuses} = getPodStateReason(props.live);
   133              attributes.push({title: 'STATE', value: reason});
   134              if (message) {
   135                  attributes.push({title: 'STATE DETAILS', value: message});
   136              }
   137              if (netContainerStatuses.length > 0) {
   138                  attributes.push({
   139                      title: 'CONTAINER STATE',
   140                      value: (
   141                          <div className='application-node-info__labels'>
   142                              {netContainerStatuses.map((container, i) => {
   143                                  return <RenderContainerState key={i} container={container} />;
   144                              })}
   145                          </div>
   146                      )
   147                  });
   148              }
   149          } else if (props.node.kind === 'Service') {
   150              attributes.push({title: 'TYPE', value: props.live.spec.type});
   151              let hostNames = '';
   152              const status = props.live.status;
   153              if (status && status.loadBalancer && status.loadBalancer.ingress) {
   154                  hostNames = (status.loadBalancer.ingress || []).map((item: any) => item.hostname || item.ip).join(', ');
   155              }
   156              attributes.push({title: 'HOSTNAMES', value: hostNames});
   157          } else if (props.node.kind === 'ReplicaSet') {
   158              attributes.push({title: 'REPLICAS', value: `${props.live.spec?.replicas || 0}/${props.live.status?.readyReplicas || 0}/${props.live.status?.replicas || 0}`});
   159          }
   160      }
   161  
   162      if (props.controlled) {
   163          if (!props.controlled.summary.hook) {
   164              attributes.push({
   165                  title: 'STATUS',
   166                  value: (
   167                      <span>
   168                          <ComparisonStatusIcon status={props.controlled.summary.status} resource={props.controlled.summary} label={true} />
   169                      </span>
   170                  )
   171              } as any);
   172          }
   173          if (props.controlled.summary.health !== undefined) {
   174              attributes.push({
   175                  title: 'HEALTH',
   176                  value: (
   177                      <span>
   178                          <HealthStatusIcon state={props.controlled.summary.health} /> {props.controlled.summary.health.status}
   179                      </span>
   180                  )
   181              } as any);
   182              if (props.controlled.summary.health.message) {
   183                  attributes.push({title: 'HEALTH DETAILS', value: props.controlled.summary.health.message});
   184              }
   185          }
   186      } else if (props.node && (props.node as ResourceTreeNode).health) {
   187          const treeNode = props.node as ResourceTreeNode;
   188          if (treeNode && treeNode.health) {
   189              attributes.push({
   190                  title: 'HEALTH',
   191                  value: (
   192                      <span>
   193                          <HealthStatusIcon state={treeNode.health} /> {treeNode.health.message || treeNode.health.status}
   194                      </span>
   195                  )
   196              } as any);
   197          }
   198      }
   199      let showLiveState = true;
   200      if (props.links) {
   201          attributes.push({
   202              title: 'LINKS',
   203              value: <DeepLinks links={props.links.items} />
   204          });
   205      }
   206  
   207      const tabs: Tab[] = [
   208          {
   209              key: 'manifest',
   210              title: 'Live Manifest',
   211              content: (
   212                  <DataLoader load={() => services.viewPreferences.getPreferences()}>
   213                      {pref => {
   214                          const live = deepMerge(props.live, {}) as any;
   215                          if (Object.keys(live).length === 0) {
   216                              showLiveState = false;
   217                          }
   218  
   219                          if (live?.metadata?.managedFields && pref.appDetails.hideManagedFields) {
   220                              delete live.metadata.managedFields;
   221                          }
   222                          return (
   223                              <React.Fragment>
   224                                  {showLiveState ? (
   225                                      <React.Fragment>
   226                                          <div className='application-node-info__checkboxes'>
   227                                              <Checkbox
   228                                                  id='hideManagedFields'
   229                                                  checked={!!pref.appDetails.hideManagedFields}
   230                                                  onChange={() =>
   231                                                      services.viewPreferences.updatePreferences({
   232                                                          appDetails: {
   233                                                              ...pref.appDetails,
   234                                                              hideManagedFields: !pref.appDetails.hideManagedFields
   235                                                          }
   236                                                      })
   237                                                  }
   238                                              />
   239                                              <label htmlFor='hideManagedFields'>Hide Managed Fields</label>
   240                                              <Checkbox
   241                                                  id='enableWordWrap'
   242                                                  checked={!!pref.appDetails.enableWordWrap}
   243                                                  onChange={() =>
   244                                                      services.viewPreferences.updatePreferences({
   245                                                          appDetails: {
   246                                                              ...pref.appDetails,
   247                                                              enableWordWrap: !pref.appDetails.enableWordWrap
   248                                                          }
   249                                                      })
   250                                                  }
   251                                              />
   252                                              <label htmlFor='enableWordWrap'>Enable Word Wrap</label>
   253                                          </div>
   254                                          <YamlEditor
   255                                              input={live}
   256                                              hideModeButtons={!live}
   257                                              vScrollbar={live}
   258                                              enableWordWrap={pref.appDetails.enableWordWrap}
   259                                              onSave={(patch, patchType) =>
   260                                                  services.applications.patchResource(
   261                                                      props.application.metadata.name,
   262                                                      props.application.metadata.namespace,
   263                                                      props.node,
   264                                                      patch,
   265                                                      patchType
   266                                                  )
   267                                              }
   268                                          />
   269                                      </React.Fragment>
   270                                  ) : (
   271                                      <div className='application-node-info__err_msg'>
   272                                          Resource not found in cluster:{' '}
   273                                          {`${props?.controlled?.state?.targetState?.apiVersion}/${props?.controlled?.state?.targetState?.kind}:${props.node.name}`}
   274                                          <br />
   275                                          {props?.controlled?.state?.normalizedLiveState?.apiVersion && (
   276                                              <span>
   277                                                  Please update your resource specification to use the latest Kubernetes API resources supported by the target cluster. The
   278                                                  recommended syntax is{' '}
   279                                                  {`${props.controlled.state.normalizedLiveState.apiVersion}/${props?.controlled.state.normalizedLiveState?.kind}:${props.node.name}`}
   280                                              </span>
   281                                          )}
   282                                      </div>
   283                                  )}
   284                              </React.Fragment>
   285                          );
   286                      }}
   287                  </DataLoader>
   288              )
   289          }
   290      ];
   291      if (props.controlled && !props.controlled.summary.hook) {
   292          tabs.push({
   293              key: 'diff',
   294              icon: 'fa fa-file-medical',
   295              title: 'Diff',
   296              content: <ApplicationResourcesDiff states={[props.controlled.state]} />
   297          });
   298          tabs.push({
   299              key: 'desiredManifest',
   300              title: 'Desired Manifest',
   301              content: (
   302                  <DataLoader load={() => services.viewPreferences.getPreferences()}>
   303                      {pref => (
   304                          <React.Fragment>
   305                              <div className='application-node-info__checkboxes'>
   306                                  <Checkbox
   307                                      id='enableWordWrap'
   308                                      checked={!!pref.appDetails.enableWordWrap}
   309                                      onChange={() =>
   310                                          services.viewPreferences.updatePreferences({
   311                                              appDetails: {
   312                                                  ...pref.appDetails,
   313                                                  enableWordWrap: !pref.appDetails.enableWordWrap
   314                                              }
   315                                          })
   316                                      }
   317                                  />
   318                                  <label htmlFor='enableWordWrap'>Enable Word Wrap</label>
   319                              </div>
   320                              <YamlEditor enableWordWrap={pref.appDetails.enableWordWrap} input={props.controlled.state.targetState} hideModeButtons={true} />
   321                          </React.Fragment>
   322                      )}
   323                  </DataLoader>
   324              )
   325          });
   326      }
   327  
   328      const readinessGatesState = React.useMemo(() => {
   329          // If containers are not ready then readiness gate status is not important.
   330          if (!props.live?.status?.containerStatuses?.length) {
   331              return null;
   332          }
   333          if (props.live?.status?.containerStatuses?.some((containerStatus: {ready: boolean}) => !containerStatus.ready)) {
   334              return null;
   335          }
   336  
   337          if (props.live && props.node?.kind === 'Pod') {
   338              return getPodReadinessGatesState(props.live);
   339          }
   340  
   341          return null;
   342      }, [props.live, props.node]);
   343  
   344      return (
   345          <div>
   346              {Boolean(readinessGatesState) && <ReadinessGatesNotPassedWarning readinessGatesState={readinessGatesState} />}
   347              <div className='white-box'>
   348                  <div className='white-box__details'>
   349                      {attributes.map(attr => (
   350                          <div className='row white-box__details-row' key={attr.title}>
   351                              <div className='columns small-3'>{attr.title}</div>
   352                              <div className='columns small-9'>{attr.value}</div>
   353                          </div>
   354                      ))}
   355                  </div>
   356              </div>
   357  
   358              <div className='application-node-info__manifest'>
   359                  <DataLoader load={() => services.viewPreferences.getPreferences()}>
   360                      {pref => (
   361                          <Tabs
   362                              selectedTabKey={(tabs.length > 1 && pref.appDetails.resourceView) || 'manifest'}
   363                              tabs={tabs}
   364                              onTabSelected={selected => {
   365                                  services.viewPreferences.updatePreferences({appDetails: {...pref.appDetails, resourceView: selected as any}});
   366                              }}
   367                          />
   368                      )}
   369                  </DataLoader>
   370              </div>
   371          </div>
   372      );
   373  };