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