github.com/argoproj/argo-cd/v2@v2.10.9/ui/src/app/settings/components/repos-list/repos-list.tsx (about) 1 import {AutocompleteField, DropDownMenu, FormField, FormSelect, HelpIcon, NotificationType, SlidingPanel, Tooltip} from 'argo-ui'; 2 import * as PropTypes from 'prop-types'; 3 import * as React from 'react'; 4 import {Form, FormValues, FormApi, Text, TextArea, FormErrors} from 'react-form'; 5 import {RouteComponentProps} from 'react-router'; 6 7 import {CheckboxField, ConnectionStateIcon, DataLoader, EmptyState, ErrorNotification, NumberField, Page, Repo, Spinner} from '../../../shared/components'; 8 import {AppContext} from '../../../shared/context'; 9 import * as models from '../../../shared/models'; 10 import {services} from '../../../shared/services'; 11 import {RepoDetails} from '../repo-details/repo-details'; 12 13 require('./repos-list.scss'); 14 15 interface NewSSHRepoParams { 16 type: string; 17 name: string; 18 url: string; 19 sshPrivateKey: string; 20 insecure: boolean; 21 enableLfs: boolean; 22 proxy: string; 23 project?: string; 24 } 25 26 export interface NewHTTPSRepoParams { 27 type: string; 28 name: string; 29 url: string; 30 username: string; 31 password: string; 32 tlsClientCertData: string; 33 tlsClientCertKey: string; 34 insecure: boolean; 35 enableLfs: boolean; 36 proxy: string; 37 project?: string; 38 forceHttpBasicAuth?: boolean; 39 enableOCI: boolean; 40 } 41 42 interface NewGitHubAppRepoParams { 43 type: string; 44 name: string; 45 url: string; 46 githubAppPrivateKey: string; 47 githubAppId: bigint; 48 githubAppInstallationId: bigint; 49 githubAppEnterpriseBaseURL: string; 50 tlsClientCertData: string; 51 tlsClientCertKey: string; 52 insecure: boolean; 53 enableLfs: boolean; 54 proxy: string; 55 project?: string; 56 } 57 58 interface NewGoogleCloudSourceRepoParams { 59 type: string; 60 name: string; 61 url: string; 62 gcpServiceAccountKey: string; 63 proxy: string; 64 project?: string; 65 } 66 67 interface NewSSHRepoCredsParams { 68 url: string; 69 sshPrivateKey: string; 70 } 71 72 interface NewHTTPSRepoCredsParams { 73 url: string; 74 username: string; 75 password: string; 76 tlsClientCertData: string; 77 tlsClientCertKey: string; 78 proxy: string; 79 forceHttpBasicAuth: boolean; 80 enableOCI: boolean; 81 } 82 83 interface NewGitHubAppRepoCredsParams { 84 url: string; 85 githubAppPrivateKey: string; 86 githubAppId: bigint; 87 githubAppInstallationId: bigint; 88 githubAppEnterpriseBaseURL: string; 89 tlsClientCertData: string; 90 tlsClientCertKey: string; 91 proxy: string; 92 } 93 94 interface NewGoogleCloudSourceRepoCredsParams { 95 url: string; 96 gcpServiceAccountKey: string; 97 } 98 99 export enum ConnectionMethod { 100 SSH = 'via SSH', 101 HTTPS = 'via HTTPS', 102 GITHUBAPP = 'via GitHub App', 103 GOOGLECLOUD = 'via Google Cloud' 104 } 105 106 export class ReposList extends React.Component< 107 RouteComponentProps<any>, 108 { 109 connecting: boolean; 110 method: string; 111 currentRepo: models.Repository; 112 displayEditPanel: boolean; 113 } 114 > { 115 public static contextTypes = { 116 router: PropTypes.object, 117 apis: PropTypes.object, 118 history: PropTypes.object 119 }; 120 121 private formApi: FormApi; 122 private credsTemplate: boolean; 123 private repoLoader: DataLoader; 124 private credsLoader: DataLoader; 125 126 constructor(props: RouteComponentProps<any>) { 127 super(props); 128 this.state = { 129 connecting: false, 130 method: ConnectionMethod.SSH, 131 currentRepo: null, 132 displayEditPanel: false 133 }; 134 } 135 136 private ConnectRepoFormButton(method: string, onSelection: (method: string) => void) { 137 return ( 138 <div className='white-box'> 139 <p>Choose your connection method:</p> 140 <DropDownMenu 141 anchor={() => ( 142 <p> 143 {method.toUpperCase()} <i className='fa fa-caret-down' /> 144 </p> 145 )} 146 items={[ConnectionMethod.SSH, ConnectionMethod.HTTPS, ConnectionMethod.GITHUBAPP, ConnectionMethod.GOOGLECLOUD].map( 147 (connectMethod: ConnectionMethod.SSH | ConnectionMethod.HTTPS | ConnectionMethod.GITHUBAPP | ConnectionMethod.GOOGLECLOUD) => ({ 148 title: connectMethod.toUpperCase(), 149 action: () => { 150 onSelection(connectMethod); 151 const formState = this.formApi.getFormState(); 152 this.formApi.setFormState({ 153 ...formState, 154 errors: {} 155 }); 156 } 157 }) 158 )} 159 /> 160 </div> 161 ); 162 } 163 164 private onChooseDefaultValues = (): FormValues => { 165 return {type: 'git', ghType: 'GitHub'}; 166 }; 167 168 private onValidateErrors(params: FormValues): FormErrors { 169 switch (this.state.method) { 170 case ConnectionMethod.SSH: 171 const sshValues = params as NewSSHRepoParams; 172 return { 173 url: !sshValues.url && 'Repository URL is required' 174 }; 175 case ConnectionMethod.HTTPS: 176 const httpsValues = params as NewHTTPSRepoParams; 177 return { 178 url: 179 (!httpsValues.url && 'Repository URL is required') || 180 (this.credsTemplate && !this.isHTTPSUrl(httpsValues.url) && !httpsValues.enableOCI && 'Not a valid HTTPS URL'), 181 name: httpsValues.type === 'helm' && !httpsValues.name && 'Name is required', 182 username: !httpsValues.username && httpsValues.password && 'Username is required if password is given.', 183 password: !httpsValues.password && httpsValues.username && 'Password is required if username is given.', 184 tlsClientCertKey: !httpsValues.tlsClientCertKey && httpsValues.tlsClientCertData && 'TLS client cert key is required if TLS client cert is given.' 185 }; 186 case ConnectionMethod.GITHUBAPP: 187 const githubAppValues = params as NewGitHubAppRepoParams; 188 return { 189 url: (!githubAppValues.url && 'Repository URL is required') || (this.credsTemplate && !this.isHTTPSUrl(githubAppValues.url) && 'Not a valid HTTPS URL'), 190 githubAppId: !githubAppValues.githubAppId && 'GitHub App ID is required', 191 githubAppInstallationId: !githubAppValues.githubAppInstallationId && 'GitHub App installation ID is required', 192 githubAppPrivateKey: !githubAppValues.githubAppPrivateKey && 'GitHub App private Key is required' 193 }; 194 case ConnectionMethod.GOOGLECLOUD: 195 const googleCloudValues = params as NewGoogleCloudSourceRepoParams; 196 return { 197 url: (!googleCloudValues.url && 'Repo URL is required') || (this.credsTemplate && !this.isHTTPSUrl(googleCloudValues.url) && 'Not a valid HTTPS URL'), 198 gcpServiceAccountKey: !googleCloudValues.gcpServiceAccountKey && 'GCP service account key is required' 199 }; 200 } 201 } 202 203 private SlidingPanelHeader() { 204 return ( 205 <> 206 {this.showConnectRepo && ( 207 <> 208 <button 209 className='argo-button argo-button--base' 210 onClick={() => { 211 this.credsTemplate = false; 212 this.formApi.submitForm(null); 213 }}> 214 <Spinner show={this.state.connecting} style={{marginRight: '5px'}} /> 215 Connect 216 </button>{' '} 217 <button 218 className='argo-button argo-button--base' 219 onClick={() => { 220 this.credsTemplate = true; 221 this.formApi.submitForm(null); 222 }}> 223 Save as credentials template 224 </button>{' '} 225 <button onClick={() => (this.showConnectRepo = false)} className='argo-button argo-button--base-o'> 226 Cancel 227 </button> 228 </> 229 )} 230 {this.state.displayEditPanel && ( 231 <button onClick={() => this.setState({displayEditPanel: false})} className='argo-button argo-button--base-o'> 232 Cancel 233 </button> 234 )} 235 </> 236 ); 237 } 238 239 private onSubmitForm() { 240 switch (this.state.method) { 241 case ConnectionMethod.SSH: 242 return (params: FormValues) => this.connectSSHRepo(params as NewSSHRepoParams); 243 case ConnectionMethod.HTTPS: 244 return (params: FormValues) => { 245 params.url = params.enableOCI ? this.stripProtocol(params.url) : params.url; 246 return this.connectHTTPSRepo(params as NewHTTPSRepoParams); 247 }; 248 case ConnectionMethod.GITHUBAPP: 249 return (params: FormValues) => this.connectGitHubAppRepo(params as NewGitHubAppRepoParams); 250 case ConnectionMethod.GOOGLECLOUD: 251 return (params: FormValues) => this.connectGoogleCloudSourceRepo(params as NewGoogleCloudSourceRepoParams); 252 } 253 } 254 255 public render() { 256 return ( 257 <Page 258 title='Repositories' 259 toolbar={{ 260 breadcrumbs: [{title: 'Settings', path: '/settings'}, {title: 'Repositories'}], 261 actionMenu: { 262 items: [ 263 { 264 iconClassName: 'fa fa-plus', 265 title: 'Connect Repo', 266 action: () => (this.showConnectRepo = true) 267 }, 268 { 269 iconClassName: 'fa fa-redo', 270 title: 'Refresh list', 271 action: () => { 272 this.refreshRepoList(); 273 } 274 } 275 ] 276 } 277 }}> 278 <div className='repos-list'> 279 <div className='argo-container'> 280 <DataLoader load={() => services.repos.list()} ref={loader => (this.repoLoader = loader)}> 281 {(repos: models.Repository[]) => 282 (repos.length > 0 && ( 283 <div className='argo-table-list'> 284 <div className='argo-table-list__head'> 285 <div className='row'> 286 <div className='columns small-1' /> 287 <div className='columns small-1'>TYPE</div> 288 <div className='columns small-2'>NAME</div> 289 <div className='columns small-5'>REPOSITORY</div> 290 <div className='columns small-3'>CONNECTION STATUS</div> 291 </div> 292 </div> 293 {repos.map(repo => ( 294 <div 295 className={`argo-table-list__row ${this.isRepoUpdatable(repo) ? 'item-clickable' : ''}`} 296 key={repo.repo} 297 onClick={() => (this.isRepoUpdatable(repo) ? this.displayEditSliding(repo) : null)}> 298 <div className='row'> 299 <div className='columns small-1'> 300 <i className={'icon argo-icon-' + (repo.type || 'git')} /> 301 </div> 302 <div className='columns small-1'> 303 <span>{repo.type || 'git'}</span> 304 {repo.enableOCI && <span> OCI</span>} 305 </div> 306 <div className='columns small-2'> 307 <Tooltip content={repo.name}> 308 <span>{repo.name}</span> 309 </Tooltip> 310 </div> 311 <div className='columns small-5'> 312 <Tooltip content={repo.repo}> 313 <span> 314 <Repo url={repo.repo} /> 315 </span> 316 </Tooltip> 317 </div> 318 <div className='columns small-3'> 319 <ConnectionStateIcon state={repo.connectionState} /> {repo.connectionState.status} 320 <DropDownMenu 321 anchor={() => ( 322 <button className='argo-button argo-button--light argo-button--lg argo-button--short'> 323 <i className='fa fa-ellipsis-v' /> 324 </button> 325 )} 326 items={[ 327 { 328 title: 'Create application', 329 action: () => 330 this.appContext.apis.navigation.goto('/applications', { 331 new: JSON.stringify({spec: {source: {repoURL: repo.repo}}}) 332 }) 333 }, 334 { 335 title: 'Disconnect', 336 action: () => this.disconnectRepo(repo.repo) 337 } 338 ]} 339 /> 340 </div> 341 </div> 342 </div> 343 ))} 344 </div> 345 )) || ( 346 <EmptyState icon='argo-icon-git'> 347 <h4>No repositories connected</h4> 348 <h5>Connect your repo to deploy apps.</h5> 349 </EmptyState> 350 ) 351 } 352 </DataLoader> 353 </div> 354 <div className='argo-container'> 355 <DataLoader load={() => services.repocreds.list()} ref={loader => (this.credsLoader = loader)}> 356 {(creds: models.RepoCreds[]) => 357 creds.length > 0 && ( 358 <div className='argo-table-list'> 359 <div className='argo-table-list__head'> 360 <div className='row'> 361 <div className='columns small-9'>CREDENTIALS TEMPLATE URL</div> 362 <div className='columns small-3'>CREDS</div> 363 </div> 364 </div> 365 {creds.map(repo => ( 366 <div className='argo-table-list__row' key={repo.url}> 367 <div className='row'> 368 <div className='columns small-9'> 369 <i className='icon argo-icon-git' /> <Repo url={repo.url} /> 370 </div> 371 <div className='columns small-3'> 372 - 373 <DropDownMenu 374 anchor={() => ( 375 <button className='argo-button argo-button--light argo-button--lg argo-button--short'> 376 <i className='fa fa-ellipsis-v' /> 377 </button> 378 )} 379 items={[{title: 'Remove', action: () => this.removeRepoCreds(repo.url)}]} 380 /> 381 </div> 382 </div> 383 </div> 384 ))} 385 </div> 386 ) 387 } 388 </DataLoader> 389 </div> 390 </div> 391 <SlidingPanel 392 isShown={this.showConnectRepo || this.state.displayEditPanel} 393 onClose={() => { 394 if (!this.state.displayEditPanel && this.showConnectRepo) { 395 this.showConnectRepo = false; 396 } 397 if (this.state.displayEditPanel) { 398 this.setState({displayEditPanel: false}); 399 } 400 }} 401 header={this.SlidingPanelHeader()}> 402 {this.showConnectRepo && 403 this.ConnectRepoFormButton(this.state.method, method => { 404 this.setState({method}); 405 })} 406 {this.state.displayEditPanel && <RepoDetails repo={this.state.currentRepo} save={(params: NewHTTPSRepoParams) => this.updateHTTPSRepo(params)} />} 407 {!this.state.displayEditPanel && ( 408 <DataLoader load={() => services.projects.list('items.metadata.name').then(projects => projects.map(proj => proj.metadata.name).sort())}> 409 {projects => ( 410 <Form 411 onSubmit={this.onSubmitForm()} 412 getApi={api => (this.formApi = api)} 413 defaultValues={this.onChooseDefaultValues()} 414 validateError={(values: FormValues) => this.onValidateErrors(values)}> 415 {formApi => ( 416 <form onSubmit={formApi.submitForm} role='form' className='repos-list width-control'> 417 {this.state.method === ConnectionMethod.SSH && ( 418 <div className='white-box'> 419 <p>CONNECT REPO USING SSH</p> 420 <div className='argo-form-row'> 421 <FormField formApi={formApi} label='Name (mandatory for Helm)' field='name' component={Text} /> 422 </div> 423 <div className='argo-form-row'> 424 <FormField 425 formApi={formApi} 426 label='Project' 427 field='project' 428 component={AutocompleteField} 429 componentProps={{items: projects}} 430 /> 431 </div> 432 <div className='argo-form-row'> 433 <FormField formApi={formApi} label='Repository URL' field='url' component={Text} /> 434 </div> 435 <div className='argo-form-row'> 436 <FormField formApi={formApi} label='SSH private key data' field='sshPrivateKey' component={TextArea} /> 437 </div> 438 <div className='argo-form-row'> 439 <FormField formApi={formApi} label='Skip server verification' field='insecure' component={CheckboxField} /> 440 <HelpIcon title='This setting is ignored when creating as credential template.' /> 441 </div> 442 <div className='argo-form-row'> 443 <FormField formApi={formApi} label='Enable LFS support (Git only)' field='enableLfs' component={CheckboxField} /> 444 <HelpIcon title='This setting is ignored when creating as credential template.' /> 445 </div> 446 <div className='argo-form-row'> 447 <FormField formApi={formApi} label='Proxy (optional)' field='proxy' component={Text} /> 448 </div> 449 </div> 450 )} 451 {this.state.method === ConnectionMethod.HTTPS && ( 452 <div className='white-box'> 453 <p>CONNECT REPO USING HTTPS</p> 454 <div className='argo-form-row'> 455 <FormField formApi={formApi} label='Type' field='type' component={FormSelect} componentProps={{options: ['git', 'helm']}} /> 456 </div> 457 {formApi.getFormState().values.type === 'helm' && ( 458 <div className='argo-form-row'> 459 <FormField formApi={formApi} label='Name' field='name' component={Text} /> 460 </div> 461 )} 462 <div className='argo-form-row'> 463 <FormField 464 formApi={formApi} 465 label='Project' 466 field='project' 467 component={AutocompleteField} 468 componentProps={{items: projects}} 469 /> 470 </div> 471 <div className='argo-form-row'> 472 <FormField formApi={formApi} label='Repository URL' field='url' component={Text} /> 473 </div> 474 <div className='argo-form-row'> 475 <FormField formApi={formApi} label='Username (optional)' field='username' component={Text} /> 476 </div> 477 <div className='argo-form-row'> 478 <FormField 479 formApi={formApi} 480 label='Password (optional)' 481 field='password' 482 component={Text} 483 componentProps={{type: 'password'}} 484 /> 485 </div> 486 <div className='argo-form-row'> 487 <FormField formApi={formApi} label='TLS client certificate (optional)' field='tlsClientCertData' component={TextArea} /> 488 </div> 489 <div className='argo-form-row'> 490 <FormField formApi={formApi} label='TLS client certificate key (optional)' field='tlsClientCertKey' component={TextArea} /> 491 </div> 492 {formApi.getFormState().values.type === 'git' && ( 493 <React.Fragment> 494 <div className='argo-form-row'> 495 <FormField formApi={formApi} label='Skip server verification' field='insecure' component={CheckboxField} /> 496 <HelpIcon title='This setting is ignored when creating as credential template.' /> 497 </div> 498 <div className='argo-form-row'> 499 <FormField formApi={formApi} label='Force HTTP basic auth' field='forceHttpBasicAuth' component={CheckboxField} /> 500 </div> 501 <div className='argo-form-row'> 502 <FormField formApi={formApi} label='Enable LFS support (Git only)' field='enableLfs' component={CheckboxField} /> 503 <HelpIcon title='This setting is ignored when creating as credential template.' /> 504 </div> 505 </React.Fragment> 506 )} 507 <div className='argo-form-row'> 508 <FormField formApi={formApi} label='Proxy (optional)' field='proxy' component={Text} /> 509 </div> 510 <div className='argo-form-row'> 511 <FormField formApi={formApi} label='Enable OCI' field='enableOCI' component={CheckboxField} /> 512 </div> 513 </div> 514 )} 515 {this.state.method === ConnectionMethod.GITHUBAPP && ( 516 <div className='white-box'> 517 <p>CONNECT REPO USING GITHUB APP</p> 518 <div className='argo-form-row'> 519 <FormField 520 formApi={formApi} 521 label='Type' 522 field='ghType' 523 component={FormSelect} 524 componentProps={{options: ['GitHub', 'GitHub Enterprise']}} 525 /> 526 </div> 527 {formApi.getFormState().values.ghType === 'GitHub Enterprise' && ( 528 <React.Fragment> 529 <div className='argo-form-row'> 530 <FormField 531 formApi={formApi} 532 label='GitHub Enterprise Base URL (e.g. https://ghe.example.com/api/v3)' 533 field='githubAppEnterpriseBaseURL' 534 component={Text} 535 /> 536 </div> 537 </React.Fragment> 538 )} 539 <div className='argo-form-row'> 540 <FormField 541 formApi={formApi} 542 label='Project' 543 field='project' 544 component={AutocompleteField} 545 componentProps={{items: projects}} 546 /> 547 </div> 548 <div className='argo-form-row'> 549 <FormField formApi={formApi} label='Repository URL' field='url' component={Text} /> 550 </div> 551 <div className='argo-form-row'> 552 <FormField formApi={formApi} label='GitHub App ID' field='githubAppId' component={NumberField} /> 553 </div> 554 <div className='argo-form-row'> 555 <FormField formApi={formApi} label='GitHub App Installation ID' field='githubAppInstallationId' component={NumberField} /> 556 </div> 557 <div className='argo-form-row'> 558 <FormField formApi={formApi} label='GitHub App private key' field='githubAppPrivateKey' component={TextArea} /> 559 </div> 560 <div className='argo-form-row'> 561 <FormField formApi={formApi} label='Skip server verification' field='insecure' component={CheckboxField} /> 562 <HelpIcon title='This setting is ignored when creating as credential template.' /> 563 </div> 564 <div className='argo-form-row'> 565 <FormField formApi={formApi} label='Enable LFS support (Git only)' field='enableLfs' component={CheckboxField} /> 566 <HelpIcon title='This setting is ignored when creating as credential template.' /> 567 </div> 568 {formApi.getFormState().values.ghType === 'GitHub Enterprise' && ( 569 <React.Fragment> 570 <div className='argo-form-row'> 571 <FormField 572 formApi={formApi} 573 label='TLS client certificate (optional)' 574 field='tlsClientCertData' 575 component={TextArea} 576 /> 577 </div> 578 <div className='argo-form-row'> 579 <FormField 580 formApi={formApi} 581 label='TLS client certificate key (optional)' 582 field='tlsClientCertKey' 583 component={TextArea} 584 /> 585 </div> 586 </React.Fragment> 587 )} 588 <div className='argo-form-row'> 589 <FormField formApi={formApi} label='Proxy (optional)' field='proxy' component={Text} /> 590 </div> 591 </div> 592 )} 593 {this.state.method === ConnectionMethod.GOOGLECLOUD && ( 594 <div className='white-box'> 595 <p>CONNECT REPO USING GOOGLE CLOUD</p> 596 <div className='argo-form-row'> 597 <FormField 598 formApi={formApi} 599 label='Project' 600 field='project' 601 component={AutocompleteField} 602 componentProps={{items: projects}} 603 /> 604 </div> 605 <div className='argo-form-row'> 606 <FormField formApi={formApi} label='Repository URL' field='url' component={Text} /> 607 </div> 608 <div className='argo-form-row'> 609 <FormField formApi={formApi} label='GCP service account key' field='gcpServiceAccountKey' component={TextArea} /> 610 </div> 611 <div className='argo-form-row'> 612 <FormField formApi={formApi} label='Proxy (optional)' field='proxy' component={Text} /> 613 </div> 614 </div> 615 )} 616 </form> 617 )} 618 </Form> 619 )} 620 </DataLoader> 621 )} 622 </SlidingPanel> 623 </Page> 624 ); 625 } 626 627 private displayEditSliding(repo: models.Repository) { 628 this.setState({currentRepo: repo}); 629 this.setState({displayEditPanel: true}); 630 } 631 632 // Whether url is a https url (simple version) 633 private isHTTPSUrl(url: string) { 634 if (url.match(/^https:\/\/.*$/gi)) { 635 return true; 636 } else { 637 return false; 638 } 639 } 640 641 private stripProtocol(url: string) { 642 return url.replace('https://', '').replace('oci://', ''); 643 } 644 645 // only connections of git type which is not via GitHub App are updatable 646 private isRepoUpdatable(repo: models.Repository) { 647 return this.isHTTPSUrl(repo.repo) && repo.type === 'git' && !repo.githubAppId; 648 } 649 650 // Forces a reload of configured repositories, circumventing the cache 651 private async refreshRepoList(updatedRepo?: string) { 652 try { 653 await services.repos.listNoCache(); 654 await services.repocreds.list(); 655 this.repoLoader.reload(); 656 this.appContext.apis.notifications.show({ 657 content: updatedRepo ? `Successfully updated ${updatedRepo} repository` : 'Successfully reloaded list of repositories', 658 type: NotificationType.Success 659 }); 660 } catch (e) { 661 this.appContext.apis.notifications.show({ 662 content: <ErrorNotification title='Could not refresh list of repositories' e={e} />, 663 type: NotificationType.Error 664 }); 665 } 666 } 667 668 // Empty all fields in connect repository form 669 private clearConnectRepoForm() { 670 this.credsTemplate = false; 671 this.formApi.resetAll(); 672 } 673 674 // Connect a new repository or create a repository credentials for SSH repositories 675 private async connectSSHRepo(params: NewSSHRepoParams) { 676 if (this.credsTemplate) { 677 this.createSSHCreds({url: params.url, sshPrivateKey: params.sshPrivateKey}); 678 } else { 679 this.setState({connecting: true}); 680 try { 681 await services.repos.createSSH(params); 682 this.repoLoader.reload(); 683 this.showConnectRepo = false; 684 } catch (e) { 685 this.appContext.apis.notifications.show({ 686 content: <ErrorNotification title='Unable to connect SSH repository' e={e} />, 687 type: NotificationType.Error 688 }); 689 } finally { 690 this.setState({connecting: false}); 691 } 692 } 693 } 694 695 // Connect a new repository or create a repository credentials for HTTPS repositories 696 private async connectHTTPSRepo(params: NewHTTPSRepoParams) { 697 if (this.credsTemplate) { 698 this.createHTTPSCreds({ 699 url: params.url, 700 username: params.username, 701 password: params.password, 702 tlsClientCertData: params.tlsClientCertData, 703 tlsClientCertKey: params.tlsClientCertKey, 704 proxy: params.proxy, 705 forceHttpBasicAuth: params.forceHttpBasicAuth, 706 enableOCI: params.enableOCI 707 }); 708 } else { 709 this.setState({connecting: true}); 710 try { 711 await services.repos.createHTTPS(params); 712 this.repoLoader.reload(); 713 this.showConnectRepo = false; 714 } catch (e) { 715 this.appContext.apis.notifications.show({ 716 content: <ErrorNotification title='Unable to connect HTTPS repository' e={e} />, 717 type: NotificationType.Error 718 }); 719 } finally { 720 this.setState({connecting: false}); 721 } 722 } 723 } 724 725 // Update an existing repository for HTTPS repositories 726 private async updateHTTPSRepo(params: NewHTTPSRepoParams) { 727 try { 728 await services.repos.updateHTTPS(params); 729 this.repoLoader.reload(); 730 this.setState({displayEditPanel: false}); 731 this.refreshRepoList(params.url); 732 } catch (e) { 733 this.appContext.apis.notifications.show({ 734 content: <ErrorNotification title='Unable to update HTTPS repository' e={e} />, 735 type: NotificationType.Error 736 }); 737 } finally { 738 this.setState({connecting: false}); 739 } 740 } 741 742 // Connect a new repository or create a repository credentials for GitHub App repositories 743 private async connectGitHubAppRepo(params: NewGitHubAppRepoParams) { 744 if (this.credsTemplate) { 745 this.createGitHubAppCreds({ 746 url: params.url, 747 githubAppPrivateKey: params.githubAppPrivateKey, 748 githubAppId: params.githubAppId, 749 githubAppInstallationId: params.githubAppInstallationId, 750 githubAppEnterpriseBaseURL: params.githubAppEnterpriseBaseURL, 751 tlsClientCertData: params.tlsClientCertData, 752 tlsClientCertKey: params.tlsClientCertKey, 753 proxy: params.proxy 754 }); 755 } else { 756 this.setState({connecting: true}); 757 try { 758 await services.repos.createGitHubApp(params); 759 this.repoLoader.reload(); 760 this.showConnectRepo = false; 761 } catch (e) { 762 this.appContext.apis.notifications.show({ 763 content: <ErrorNotification title='Unable to connect GitHub App repository' e={e} />, 764 type: NotificationType.Error 765 }); 766 } finally { 767 this.setState({connecting: false}); 768 } 769 } 770 } 771 772 // Connect a new repository or create a repository credentials for GitHub App repositories 773 private async connectGoogleCloudSourceRepo(params: NewGoogleCloudSourceRepoParams) { 774 if (this.credsTemplate) { 775 this.createGoogleCloudSourceCreds({ 776 url: params.url, 777 gcpServiceAccountKey: params.gcpServiceAccountKey 778 }); 779 } else { 780 this.setState({connecting: true}); 781 try { 782 await services.repos.createGoogleCloudSource(params); 783 this.repoLoader.reload(); 784 this.showConnectRepo = false; 785 } catch (e) { 786 this.appContext.apis.notifications.show({ 787 content: <ErrorNotification title='Unable to connect Google Cloud Source repository' e={e} />, 788 type: NotificationType.Error 789 }); 790 } finally { 791 this.setState({connecting: false}); 792 } 793 } 794 } 795 796 private async createHTTPSCreds(params: NewHTTPSRepoCredsParams) { 797 try { 798 await services.repocreds.createHTTPS(params); 799 this.credsLoader.reload(); 800 this.showConnectRepo = false; 801 } catch (e) { 802 this.appContext.apis.notifications.show({ 803 content: <ErrorNotification title='Unable to create HTTPS credentials' e={e} />, 804 type: NotificationType.Error 805 }); 806 } 807 } 808 809 private async createSSHCreds(params: NewSSHRepoCredsParams) { 810 try { 811 await services.repocreds.createSSH(params); 812 this.credsLoader.reload(); 813 this.showConnectRepo = false; 814 } catch (e) { 815 this.appContext.apis.notifications.show({ 816 content: <ErrorNotification title='Unable to create SSH credentials' e={e} />, 817 type: NotificationType.Error 818 }); 819 } 820 } 821 822 private async createGitHubAppCreds(params: NewGitHubAppRepoCredsParams) { 823 try { 824 await services.repocreds.createGitHubApp(params); 825 this.credsLoader.reload(); 826 this.showConnectRepo = false; 827 } catch (e) { 828 this.appContext.apis.notifications.show({ 829 content: <ErrorNotification title='Unable to create GitHub App credentials' e={e} />, 830 type: NotificationType.Error 831 }); 832 } 833 } 834 835 private async createGoogleCloudSourceCreds(params: NewGoogleCloudSourceRepoCredsParams) { 836 try { 837 await services.repocreds.createGoogleCloudSource(params); 838 this.credsLoader.reload(); 839 this.showConnectRepo = false; 840 } catch (e) { 841 this.appContext.apis.notifications.show({ 842 content: <ErrorNotification title='Unable to create Google Cloud Source credentials' e={e} />, 843 type: NotificationType.Error 844 }); 845 } 846 } 847 848 // Remove a repository from the configuration 849 private async disconnectRepo(repo: string) { 850 const confirmed = await this.appContext.apis.popup.confirm('Disconnect repository', `Are you sure you want to disconnect '${repo}'?`); 851 if (confirmed) { 852 try { 853 await services.repos.delete(repo); 854 this.repoLoader.reload(); 855 } catch (e) { 856 this.appContext.apis.notifications.show({ 857 content: <ErrorNotification title='Unable to disconnect repository' e={e} />, 858 type: NotificationType.Error 859 }); 860 } 861 } 862 } 863 864 // Remove repository credentials from the configuration 865 private async removeRepoCreds(url: string) { 866 const confirmed = await this.appContext.apis.popup.confirm('Remove repository credentials', `Are you sure you want to remove credentials for URL prefix '${url}'?`); 867 if (confirmed) { 868 try { 869 await services.repocreds.delete(url); 870 this.credsLoader.reload(); 871 } catch (e) { 872 this.appContext.apis.notifications.show({ 873 content: <ErrorNotification title='Unable to remove repository credentials' e={e} />, 874 type: NotificationType.Error 875 }); 876 } 877 } 878 } 879 880 // Whether to show the new repository connection dialogue on the page 881 private get showConnectRepo() { 882 return new URLSearchParams(this.props.location.search).get('addRepo') === 'true'; 883 } 884 885 private set showConnectRepo(val: boolean) { 886 this.clearConnectRepoForm(); 887 this.appContext.router.history.push(`${this.props.match.url}?addRepo=${val}`); 888 } 889 890 private get appContext(): AppContext { 891 return this.context as AppContext; 892 } 893 }