github.com/argoproj/argo-cd/v2@v2.10.9/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, 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 function editList(type: field, formApi: FormApi) { 168 const info = infoByField[type]; 169 170 return ( 171 <React.Fragment> 172 <p className='project-details__list-title'> 173 {info.title} {helpTip(info.helpText)} 174 </p> 175 <div className='row white-box__details-row'> 176 <div className='columns small-4'>Kind</div> 177 <div className='columns small-8'>Group</div> 178 </div> 179 {(formApi.values.spec[type] || []).map((_: Project, i: number) => ( 180 <div className='row white-box__details-row' key={i}> 181 <div className='columns small-4'> 182 <FormField 183 formApi={formApi} 184 field={`spec.${type}[${i}].kind`} 185 component={AutocompleteField} 186 componentProps={{items: ResourceKinds, filterSuggestions: true}} 187 /> 188 </div> 189 <div className='columns small-8'> 190 <FormField formApi={formApi} field={`spec.${type}[${i}].group`} component={AutocompleteField} componentProps={{items: Groups, filterSuggestions: true}} /> 191 </div> 192 <i className='fa fa-times' onClick={() => formApi.setValue(`spec.${type}`, removeEl(formApi.values.spec[type], i))} /> 193 </div> 194 ))} 195 <button className='argo-button argo-button--short' onClick={() => formApi.setValue(`spec.${type}`, (formApi.values.spec[type] || []).concat({group: '*', kind: '*'}))}> 196 ADD RESOURCE 197 </button> 198 </React.Fragment> 199 ); 200 } 201 202 export const ResourceListsPanel = ({proj, saveProject, title}: {proj: Project; title?: React.ReactNode; saveProject?: (proj: Project) => any}) => ( 203 <EditablePanel 204 save={saveProject} 205 values={proj} 206 view={ 207 <React.Fragment> 208 {title} 209 {Object.keys(infoByField).map(key => ( 210 <React.Fragment key={key}>{viewList(key as field, proj)}</React.Fragment> 211 ))} 212 {!proj.metadata && Object.keys(sourceReposInfoByField).map(key => <React.Fragment key={key}>{viewSourceReposInfoList(key as field, proj)}</React.Fragment>)} 213 {!proj.metadata && 214 Object.keys(sourceNamespacesInfoByField).map(key => <React.Fragment key={key}>{viewSourceNamespacesInfoList(key as field, proj)}</React.Fragment>)} 215 {!proj.metadata && Object.keys(destinationsInfoByField).map(key => <React.Fragment key={key}>{viewDestinationsInfoList(key as field, proj)}</React.Fragment>)} 216 </React.Fragment> 217 } 218 edit={ 219 saveProject && 220 (formApi => ( 221 <React.Fragment> 222 {title} 223 {Object.keys(infoByField).map(key => ( 224 <React.Fragment key={key}>{editList(key as field, formApi)}</React.Fragment> 225 ))} 226 </React.Fragment> 227 )) 228 } 229 items={[]} 230 /> 231 );