github.com/argoproj/argo-cd/v2@v2.10.9/ui/src/app/settings/components/cluster-details/cluster-details.tsx (about) 1 import * as classNames from 'classnames'; 2 import * as moment from 'moment'; 3 import * as React from 'react'; 4 import {FieldApi, FormField as ReactFormField, Text} from 'react-form'; 5 import {RouteComponentProps} from 'react-router-dom'; 6 import {from, timer} from 'rxjs'; 7 import {mergeMap} from 'rxjs/operators'; 8 9 import {FormField, Ticker} from 'argo-ui'; 10 import {ConnectionStateIcon, DataLoader, EditablePanel, Page, Timestamp, MapInputField} from '../../../shared/components'; 11 import {Cluster} from '../../../shared/models'; 12 import {services} from '../../../shared/services'; 13 14 function isRefreshRequested(cluster: Cluster): boolean { 15 return cluster.info.connectionState.attemptedAt && cluster.refreshRequestedAt && moment(cluster.info.connectionState.attemptedAt).isBefore(moment(cluster.refreshRequestedAt)); 16 } 17 18 export const NamespacesEditor = ReactFormField((props: {fieldApi: FieldApi; className: string}) => { 19 const val = (props.fieldApi.getValue() || []).join(','); 20 return <input className={props.className} value={val} onChange={event => props.fieldApi.setValue(event.target.value.split(','))} />; 21 }); 22 23 export const ClusterDetails = (props: RouteComponentProps<{server: string}>) => { 24 const server = decodeURIComponent(props.match.params.server); 25 const loaderRef = React.useRef<DataLoader>(); 26 const [updating, setUpdating] = React.useState(false); 27 return ( 28 <DataLoader ref={loaderRef} input={server} load={(url: string) => timer(0, 1000).pipe(mergeMap(() => from(services.clusters.get(url, ''))))}> 29 {(cluster: Cluster) => ( 30 <Page 31 title='Clusters' 32 toolbar={{ 33 breadcrumbs: [{title: 'Settings', path: '/settings'}, {title: 'Clusters', path: '/settings/clusters'}, {title: server}], 34 actionMenu: { 35 items: [ 36 { 37 iconClassName: classNames('fa fa-redo', {'status-icon--spin': isRefreshRequested(cluster)}), 38 title: 'Invalidate Cache', 39 disabled: isRefreshRequested(cluster) || updating, 40 action: async () => { 41 setUpdating(true); 42 try { 43 const updated = await services.clusters.invalidateCache(props.match.params.server); 44 loaderRef.current.setData(updated); 45 } finally { 46 setUpdating(false); 47 } 48 } 49 } 50 ] 51 } 52 }}> 53 <p /> 54 55 <div className='argo-container'> 56 <EditablePanel 57 values={cluster} 58 save={async updated => { 59 const item = await services.clusters.get(updated.server, ''); 60 item.name = updated.name; 61 item.namespaces = updated.namespaces; 62 item.labels = updated.labels; 63 item.annotations = updated.annotations; 64 loaderRef.current.setData(await services.clusters.update(item, 'name', 'namespaces', 'labels', 'annotations')); 65 }} 66 title='GENERAL' 67 items={[ 68 { 69 title: 'SERVER', 70 view: cluster.server 71 }, 72 { 73 title: 'CREDENTIALS TYPE', 74 view: 75 (cluster.config.awsAuthConfig && `IAM AUTH (cluster name: ${cluster.config.awsAuthConfig.clusterName})`) || 76 (cluster.config.execProviderConfig && `External provider (command: ${cluster.config.execProviderConfig.command})`) || 77 'Token/Basic Auth' 78 }, 79 { 80 title: 'NAME', 81 view: cluster.name, 82 edit: formApi => <FormField formApi={formApi} field='name' component={Text} /> 83 }, 84 { 85 title: 'NAMESPACES', 86 view: ((cluster.namespaces || []).length === 0 && 'All namespaces') || cluster.namespaces.join(', '), 87 edit: formApi => <FormField formApi={formApi} field='namespaces' component={NamespacesEditor} /> 88 }, 89 { 90 title: 'LABELS', 91 view: Object.keys(cluster.labels || []) 92 .map(label => `${label}=${cluster.labels[label]}`) 93 .join(' '), 94 edit: formApi => <FormField formApi={formApi} field='labels' component={MapInputField} /> 95 }, 96 { 97 title: 'ANNOTATIONS', 98 view: Object.keys(cluster.annotations || []) 99 .map(annotation => `${annotation}=${cluster.annotations[annotation]}`) 100 .join(' '), 101 edit: formApi => <FormField formApi={formApi} field='annotations' component={MapInputField} /> 102 } 103 ]} 104 /> 105 <div className='white-box'> 106 <p>CONNECTION STATE</p> 107 <div className='white-box__details'> 108 <div className='row white-box__details-row'> 109 <div className='columns small-3'>STATUS:</div> 110 <div className='columns small-9'> 111 <ConnectionStateIcon state={cluster.info.connectionState} /> {cluster.info.connectionState.status} 112 </div> 113 </div> 114 <div className='row white-box__details-row'> 115 <div className='columns small-3'>VERSION:</div> 116 <div className='columns small-9'> {cluster.info.serverVersion}</div> 117 </div> 118 <div className='row white-box__details-row'> 119 <div className='columns small-3'>DETAILS:</div> 120 <div className='columns small-9'> {cluster.info.connectionState.message} </div> 121 </div> 122 <div className='row white-box__details-row'> 123 <div className='columns small-3'>MODIFIED AT:</div> 124 <div className='columns small-9'> 125 <Ticker> 126 {now => { 127 if (!cluster.info.connectionState.attemptedAt) { 128 return <span>Never (next refresh in few seconds)</span>; 129 } 130 const secondsBeforeRefresh = Math.round(Math.max(10 - now.diff(moment(cluster.info.connectionState.attemptedAt)) / 1000, 1)); 131 return ( 132 <React.Fragment> 133 <Timestamp date={cluster.info.connectionState.attemptedAt} /> (next refresh in {secondsBeforeRefresh} seconds) 134 </React.Fragment> 135 ); 136 }} 137 </Ticker> 138 </div> 139 </div> 140 </div> 141 </div> 142 143 <div className='white-box'> 144 <p>CACHE INFO</p> 145 <div className='white-box__details'> 146 <Ticker> 147 {() => ( 148 <div className='row white-box__details-row'> 149 <div className='columns small-3'>RE-SYNCHRONIZED:</div> 150 <div className='columns small-9'> 151 <Timestamp date={cluster.info.cacheInfo.lastCacheSyncTime} /> 152 </div> 153 </div> 154 )} 155 </Ticker> 156 <div className='row white-box__details-row'> 157 <div className='columns small-3'>APIs COUNT:</div> 158 <div className='columns small-9'> {cluster.info.cacheInfo.apisCount} </div> 159 </div> 160 <div className='row white-box__details-row'> 161 <div className='columns small-3'>RESOURCES COUNT:</div> 162 <div className='columns small-9'> {cluster.info.cacheInfo.resourcesCount} </div> 163 </div> 164 <div className='row white-box__details-row'> 165 <div className='columns small-3'>APPLICATIONS COUNT:</div> 166 <div className='columns small-9'> {cluster.info.applicationsCount} </div> 167 </div> 168 </div> 169 </div> 170 </div> 171 </Page> 172 )} 173 </DataLoader> 174 ); 175 };