github.com/argoproj/argo-cd/v3@v3.2.1/ui/src/app/settings/components/project-details/resource-lists-panel.tsx (about) 1 import {AutocompleteField, FormField, Tooltip} from 'argo-ui'; 2 import * as React from 'react'; 3 import {FormApi} from 'react-form'; 4 5 import {EditablePanel} from '../../../shared/components'; 6 import {ApplicationDestination, ApplicationDestinationServiceAccount, GroupKind, Groups, Project, ProjectSpec, ResourceKinds} from '../../../shared/models'; 7 8 function removeEl(items: any[], index: number) { 9 return items.slice(0, index).concat(items.slice(index + 1)); 10 } 11 12 function helpTip(text: string) { 13 return ( 14 <Tooltip content={text}> 15 <span style={{fontSize: 'smaller'}}> 16 {' '} 17 <i className='fas fa-info-circle' /> 18 </span> 19 </Tooltip> 20 ); 21 } 22 23 type field = keyof ProjectSpec; 24 25 const infoByField: {[type: string]: {title: string; helpText: string}} = { 26 clusterResourceWhitelist: { 27 title: 'cluster resource allow list', 28 helpText: 'Cluster-scoped K8s API Groups and Kinds which are permitted to be deployed' 29 }, 30 clusterResourceBlacklist: { 31 title: 'cluster resource deny list', 32 helpText: 'Cluster-scoped K8s API Groups and Kinds which are not permitted to be deployed' 33 }, 34 namespaceResourceWhitelist: { 35 title: 'namespace resource allow list', 36 helpText: 'Namespace-scoped K8s API Groups and Kinds which are permitted to deploy' 37 }, 38 namespaceResourceBlacklist: { 39 title: 'namespace resource deny list', 40 helpText: 'Namespace-scoped K8s API Groups and Kinds which are prohibited from being deployed' 41 } 42 }; 43 44 function viewList(type: field, proj: Project) { 45 const info = infoByField[type]; 46 const list = proj.spec[type] as Array<GroupKind>; 47 return ( 48 <React.Fragment> 49 <p className='project-details__list-title'> 50 {info.title} {helpTip(info.helpText)} 51 </p> 52 {(list || []).length > 0 ? ( 53 <React.Fragment> 54 <div className='row white-box__details-row'> 55 <div className='columns small-4'>Kind</div> 56 <div className='columns small-8'>Group</div> 57 </div> 58 {list.map((resource, i) => ( 59 <div className='row white-box__details-row' key={i}> 60 <div className='columns small-4'>{resource.kind}</div> 61 <div className='columns small-8'>{resource.group}</div> 62 </div> 63 ))} 64 </React.Fragment> 65 ) : ( 66 <p>The {info.title} is empty</p> 67 )} 68 </React.Fragment> 69 ); 70 } 71 72 const sourceReposInfoByField: {[type: string]: {title: string; helpText: string}} = { 73 sourceRepos: { 74 title: 'source repositories', 75 helpText: 'Git repositories where application manifests are permitted to be retrieved from' 76 } 77 }; 78 79 function viewSourceReposInfoList(type: field, proj: Project) { 80 const info = sourceReposInfoByField[type]; 81 const list = proj.spec[type] as Array<string>; 82 return ( 83 <React.Fragment> 84 <p className='project-details__list-title'> 85 {info.title} {helpTip(info.helpText)} 86 </p> 87 {(list || []).length > 0 ? ( 88 <React.Fragment> 89 {list.map((repo, i) => ( 90 <div className='row white-box__details-row' key={i}> 91 <div className='columns small-12'>{repo}</div> 92 </div> 93 ))} 94 </React.Fragment> 95 ) : ( 96 <p>The {info.title} is empty</p> 97 )} 98 </React.Fragment> 99 ); 100 } 101 102 const sourceNamespacesInfoByField: {[type: string]: {title: string; helpText: string}} = { 103 sourceNamespaces: { 104 title: 'source namespaces', 105 helpText: 'Kubernetes namespaces where application resources are allowed to be created in' 106 } 107 }; 108 109 function viewSourceNamespacesInfoList(type: field, proj: Project) { 110 const info = sourceNamespacesInfoByField[type]; 111 const list = proj.spec[type] as Array<string>; 112 return ( 113 <React.Fragment> 114 <p className='project-details__list-title'> 115 {info.title} {helpTip(info.helpText)} 116 </p> 117 {(list || []).length > 0 ? ( 118 <React.Fragment> 119 {list.map((namespace, i) => ( 120 <div className='row white-box__details-row' key={i}> 121 <div className='columns small-12'>{namespace}</div> 122 </div> 123 ))} 124 </React.Fragment> 125 ) : ( 126 <p>The {info.title} is empty</p> 127 )} 128 </React.Fragment> 129 ); 130 } 131 132 const destinationsInfoByField: {[type: string]: {title: string; helpText: string}} = { 133 destinations: { 134 title: 'destinations', 135 helpText: 'Cluster and namespaces where applications are permitted to be deployed to' 136 } 137 }; 138 139 function viewDestinationsInfoList(type: field, proj: Project) { 140 const info = destinationsInfoByField[type]; 141 const list = proj.spec[type] as Array<ApplicationDestination>; 142 return ( 143 <React.Fragment> 144 <p className='project-details__list-title'> 145 {info.title} {helpTip(info.helpText)} 146 </p> 147 {(list || []).length > 0 ? ( 148 <React.Fragment> 149 <div className='row white-box__details-row'> 150 <div className='columns small-4'>Server</div> 151 <div className='columns small-8'>Namespace</div> 152 </div> 153 {list.map((destination, i) => ( 154 <div className='row white-box__details-row' key={i}> 155 <div className='columns small-4'>{destination.server}</div> 156 <div className='columns small-8'>{destination.namespace}</div> 157 </div> 158 ))} 159 </React.Fragment> 160 ) : ( 161 <p>The {info.title} is empty</p> 162 )} 163 </React.Fragment> 164 ); 165 } 166 167 const destinationServiceAccountsInfoByField: {[type: string]: {title: string; helpText: string}} = { 168 destinationServiceAccounts: { 169 title: 'destination service accounts', 170 helpText: 'DestinationServiceAccounts holds information about the service accounts to be impersonated for the application sync operation for each destination.' 171 } 172 }; 173 174 function viewDestinationServiceAccountsInfoList(type: field, proj: Project) { 175 const info = destinationServiceAccountsInfoByField[type]; 176 const list = proj.spec[type] as Array<ApplicationDestinationServiceAccount>; 177 return ( 178 <React.Fragment> 179 <p className='project-details__list-title'> 180 {info.title} {helpTip(info.helpText)} 181 </p> 182 {(list || []).length > 0 ? ( 183 <React.Fragment> 184 <div className='row white-box__details-row'> 185 <div className='columns small-4'>Server</div> 186 <div className='columns small-8'>Namespace</div> 187 <div className='columns small-12'>DefaultServiceAccount</div> 188 </div> 189 {list.map((destinationServiceAccounts, i) => ( 190 <div className='row white-box__details-row' key={i}> 191 <div className='columns small-4'>{destinationServiceAccounts.server}</div> 192 <div className='columns small-8'>{destinationServiceAccounts.namespace}</div> 193 <div className='columns small-12'>{destinationServiceAccounts.defaultServiceAccount}</div> 194 </div> 195 ))} 196 </React.Fragment> 197 ) : ( 198 <p>The {info.title} is empty</p> 199 )} 200 </React.Fragment> 201 ); 202 } 203 204 function editList(type: field, formApi: FormApi) { 205 const info = infoByField[type]; 206 207 return ( 208 <React.Fragment> 209 <p className='project-details__list-title'> 210 {info.title} {helpTip(info.helpText)} 211 </p> 212 <div className='row white-box__details-row'> 213 <div className='columns small-4'>Kind</div> 214 <div className='columns small-8'>Group</div> 215 </div> 216 {(formApi.values.spec[type] || []).map((_: Project, i: number) => ( 217 <div className='row white-box__details-row' key={i}> 218 <div className='columns small-4'> 219 <FormField 220 formApi={formApi} 221 field={`spec.${type}[${i}].kind`} 222 component={AutocompleteField} 223 componentProps={{items: ResourceKinds, filterSuggestions: true}} 224 /> 225 </div> 226 <div className='columns small-8'> 227 <FormField formApi={formApi} field={`spec.${type}[${i}].group`} component={AutocompleteField} componentProps={{items: Groups, filterSuggestions: true}} /> 228 </div> 229 <i className='fa fa-times' onClick={() => formApi.setValue(`spec.${type}`, removeEl(formApi.values.spec[type], i))} /> 230 </div> 231 ))} 232 <button className='argo-button argo-button--short' onClick={() => formApi.setValue(`spec.${type}`, (formApi.values.spec[type] || []).concat({group: '*', kind: '*'}))}> 233 ADD RESOURCE 234 </button> 235 </React.Fragment> 236 ); 237 } 238 239 export const ResourceListsPanel = ({proj, saveProject, title}: {proj: Project; title?: React.ReactNode; saveProject?: (proj: Project) => any}) => ( 240 <EditablePanel 241 save={saveProject} 242 values={proj} 243 view={ 244 <React.Fragment> 245 {title} 246 {Object.keys(infoByField).map(key => ( 247 <React.Fragment key={key}>{viewList(key as field, proj)}</React.Fragment> 248 ))} 249 {!proj.metadata && Object.keys(sourceReposInfoByField).map(key => <React.Fragment key={key}>{viewSourceReposInfoList(key as field, proj)}</React.Fragment>)} 250 {!proj.metadata && 251 Object.keys(sourceNamespacesInfoByField).map(key => <React.Fragment key={key}>{viewSourceNamespacesInfoList(key as field, proj)}</React.Fragment>)} 252 {!proj.metadata && Object.keys(destinationsInfoByField).map(key => <React.Fragment key={key}>{viewDestinationsInfoList(key as field, proj)}</React.Fragment>)} 253 {!proj.metadata && 254 Object.keys(destinationServiceAccountsInfoByField).map(key => ( 255 <React.Fragment key={key}>{viewDestinationServiceAccountsInfoList(key as field, proj)}</React.Fragment> 256 ))} 257 </React.Fragment> 258 } 259 edit={ 260 saveProject && 261 (formApi => ( 262 <React.Fragment> 263 {title} 264 {Object.keys(infoByField).map(key => ( 265 <React.Fragment key={key}>{editList(key as field, formApi)}</React.Fragment> 266 ))} 267 </React.Fragment> 268 )) 269 } 270 items={[]} 271 /> 272 );