|
31 | 31 | * @property {boolean} connect_by_url |
32 | 32 | * @property {string?} url |
33 | 33 | * @property {boolean} connect_by_key |
| 34 | + * @property {boolean} connect_with_identity |
34 | 35 | * @property {string?} private_key |
35 | 36 | * @property {string?} private_key_passphrase |
36 | 37 | * @property {string?} http_path |
@@ -126,6 +127,7 @@ const ConnectionForm = (props, saveButton) => { |
126 | 127 | warehouse: connection?.warehouse ?? '', |
127 | 128 | url: connection?.url ?? '', |
128 | 129 | service_account_key: connection?.service_account_key ?? '', |
| 130 | + connect_with_identity: connection?.connect_with_identity ?? false, |
129 | 131 | sql_flavor_code: connectionFlavor.rawVal ?? '', |
130 | 132 | connection_name: connectionName.rawVal ?? '', |
131 | 133 | max_threads: connectionMaxThreads.rawVal ?? 4, |
@@ -550,7 +552,197 @@ const RedshiftSpectrumForm = RedshiftForm; |
550 | 552 |
|
551 | 553 | const PostgresqlForm = RedshiftForm; |
552 | 554 |
|
553 | | -const AzureMSSQLForm = RedshiftForm; |
| 555 | +const AzureMSSQLForm = ( |
| 556 | + connection, |
| 557 | + flavor, |
| 558 | + onChange, |
| 559 | + originalConnection, |
| 560 | + dynamicConnectionUrl, |
| 561 | +) => { |
| 562 | + const isValid = van.state(true); |
| 563 | + const connectByUrl = van.state(connection.rawVal.connect_by_url ?? false); |
| 564 | + const connectionHost = van.state(connection.rawVal.project_host ?? ''); |
| 565 | + const connectionPort = van.state(connection.rawVal.project_port || defaultPorts[flavor.flavor]); |
| 566 | + const connectionDatabase = van.state(connection.rawVal.project_db ?? ''); |
| 567 | + const connectionUsername = van.state(connection.rawVal.project_user ?? ''); |
| 568 | + const connectionPassword = van.state(connection.rawVal?.project_pw_encrypted ?? ''); |
| 569 | + const connectionUrl = van.state(connection.rawVal?.url ?? ''); |
| 570 | + const connectWithIdentity = van.state(connection.rawVal?.connect_with_identity ?? ''); |
| 571 | + |
| 572 | + const validityPerField = {}; |
| 573 | + |
| 574 | + van.derive(() => { |
| 575 | + onChange({ |
| 576 | + project_host: connectionHost.val, |
| 577 | + project_port: connectionPort.val, |
| 578 | + project_db: connectionDatabase.val, |
| 579 | + project_user: connectionUsername.val, |
| 580 | + project_pw_encrypted: connectionPassword.val, |
| 581 | + connect_by_url: connectByUrl.val, |
| 582 | + url: connectByUrl.val ? connectionUrl.val : connectionUrl.rawVal, |
| 583 | + connect_by_key: false, |
| 584 | + connect_with_identity: connectWithIdentity.val, |
| 585 | + }, isValid.val); |
| 586 | + }); |
| 587 | + |
| 588 | + van.derive(() => { |
| 589 | + const newUrlValue = (dynamicConnectionUrl.val ?? '').replace(extractPrefix(dynamicConnectionUrl.rawVal), ''); |
| 590 | + if (!connectByUrl.rawVal) { |
| 591 | + connectionUrl.val = newUrlValue; |
| 592 | + } |
| 593 | + }); |
| 594 | + |
| 595 | + return div( |
| 596 | + {class: 'flex-column fx-gap-3 fx-flex'}, |
| 597 | + div( |
| 598 | + { class: 'flex-column border border-radius-1 p-3 mt-1 fx-gap-1', style: 'position: relative;' }, |
| 599 | + Caption({content: 'Server', style: 'position: absolute; top: -10px; background: var(--app-background-color); padding: 0px 8px;' }), |
| 600 | + RadioGroup({ |
| 601 | + label: 'Connect by', |
| 602 | + options: [ |
| 603 | + { |
| 604 | + label: 'Host', |
| 605 | + value: false, |
| 606 | + }, |
| 607 | + { |
| 608 | + label: 'URL', |
| 609 | + value: true, |
| 610 | + }, |
| 611 | + ], |
| 612 | + value: connectByUrl, |
| 613 | + onChange: (value) => connectByUrl.val = value, |
| 614 | + layout: 'inline', |
| 615 | + }), |
| 616 | + div( |
| 617 | + { class: 'flex-row fx-gap-3 fx-flex' }, |
| 618 | + Input({ |
| 619 | + name: 'db_host', |
| 620 | + label: 'Host', |
| 621 | + value: connectionHost, |
| 622 | + class: 'fx-flex', |
| 623 | + disabled: connectByUrl, |
| 624 | + onChange: (value, state) => { |
| 625 | + connectionHost.val = value; |
| 626 | + validityPerField['db_host'] = state.valid; |
| 627 | + isValid.val = Object.values(validityPerField).every(v => v); |
| 628 | + }, |
| 629 | + validators: [ |
| 630 | + maxLength(250), |
| 631 | + requiredIf(() => !connectByUrl.val), |
| 632 | + ], |
| 633 | + }), |
| 634 | + Input({ |
| 635 | + name: 'db_port', |
| 636 | + label: 'Port', |
| 637 | + value: connectionPort, |
| 638 | + type: 'number', |
| 639 | + disabled: connectByUrl, |
| 640 | + onChange: (value, state) => { |
| 641 | + connectionPort.val = value; |
| 642 | + validityPerField['db_port'] = state.valid; |
| 643 | + isValid.val = Object.values(validityPerField).every(v => v); |
| 644 | + }, |
| 645 | + validators: [ |
| 646 | + minLength(3), |
| 647 | + maxLength(5), |
| 648 | + requiredIf(() => !connectByUrl.val), |
| 649 | + ], |
| 650 | + }) |
| 651 | + ), |
| 652 | + Input({ |
| 653 | + name: 'db_name', |
| 654 | + label: 'Database', |
| 655 | + value: connectionDatabase, |
| 656 | + disabled: connectByUrl, |
| 657 | + onChange: (value, state) => { |
| 658 | + connectionDatabase.val = value; |
| 659 | + validityPerField['db_name'] = state.valid; |
| 660 | + isValid.val = Object.values(validityPerField).every(v => v); |
| 661 | + }, |
| 662 | + validators: [ |
| 663 | + maxLength(100), |
| 664 | + requiredIf(() => !connectByUrl.val), |
| 665 | + ], |
| 666 | + }), |
| 667 | + () => div( |
| 668 | + { class: 'flex-row fx-gap-3 fx-align-stretch', style: 'position: relative;' }, |
| 669 | + Input({ |
| 670 | + label: 'URL', |
| 671 | + value: connectionUrl, |
| 672 | + class: 'fx-flex', |
| 673 | + name: 'url_suffix', |
| 674 | + prefix: span({ style: 'white-space: nowrap; color: var(--disabled-text-color)' }, extractPrefix(dynamicConnectionUrl.val)), |
| 675 | + disabled: !connectByUrl.val, |
| 676 | + onChange: (value, state) => { |
| 677 | + connectionUrl.val = value; |
| 678 | + validityPerField['url_suffix'] = state.valid; |
| 679 | + isValid.val = Object.values(validityPerField).every(v => v); |
| 680 | + }, |
| 681 | + validators: [ |
| 682 | + requiredIf(() => connectByUrl.val), |
| 683 | + ], |
| 684 | + }), |
| 685 | + ), |
| 686 | + ), |
| 687 | + |
| 688 | + div( |
| 689 | + { class: 'flex-column border border-radius-1 p-3 mt-1 fx-gap-1', style: 'position: relative;' }, |
| 690 | + Caption({content: 'Authentication', style: 'position: absolute; top: -10px; background: var(--app-background-color); padding: 0px 8px;' }), |
| 691 | + |
| 692 | + RadioGroup({ |
| 693 | + label: 'Connection Strategy', |
| 694 | + options: [ |
| 695 | + {label: 'Connect By Password', value: false}, |
| 696 | + {label: 'Connect with Managed Identity', value: true}, |
| 697 | + ], |
| 698 | + value: connectWithIdentity, |
| 699 | + onChange: (value) => connectWithIdentity.val = value, |
| 700 | + layout: 'inline', |
| 701 | + }), |
| 702 | + |
| 703 | + () => { |
| 704 | + const _connectWithIdentity = connectWithIdentity.val; |
| 705 | + if (_connectWithIdentity) { |
| 706 | + return div( |
| 707 | + {class: 'flex-row p-4 fx-justify-center text-secondary'}, |
| 708 | + 'Configured Microsoft Entra ID credentials will be used', |
| 709 | + ); |
| 710 | + } |
| 711 | + |
| 712 | + return div( |
| 713 | + {class: 'flex-column fx-gap-1'}, |
| 714 | + Input({ |
| 715 | + name: 'db_user', |
| 716 | + label: 'Username', |
| 717 | + value: connectionUsername, |
| 718 | + onChange: (value, state) => { |
| 719 | + connectionUsername.val = value; |
| 720 | + validityPerField['db_user'] = state.valid; |
| 721 | + isValid.val = Object.values(validityPerField).every(v => v); |
| 722 | + }, |
| 723 | + validators: [ |
| 724 | + requiredIf(() => !connectWithIdentity.val), |
| 725 | + maxLength(50), |
| 726 | + ], |
| 727 | + }), |
| 728 | + Input({ |
| 729 | + name: 'password', |
| 730 | + label: 'Password', |
| 731 | + value: connectionPassword, |
| 732 | + type: 'password', |
| 733 | + passwordSuggestions: false, |
| 734 | + placeholder: (originalConnection?.connection_id && originalConnection?.project_pw_encrypted) ? secretsPlaceholder : '', |
| 735 | + onChange: (value, state) => { |
| 736 | + connectionPassword.val = value; |
| 737 | + validityPerField['password'] = state.valid; |
| 738 | + isValid.val = Object.values(validityPerField).every(v => v); |
| 739 | + }, |
| 740 | + }), |
| 741 | + ) |
| 742 | + }, |
| 743 | + ), |
| 744 | + ); |
| 745 | +}; |
554 | 746 |
|
555 | 747 | const SynapseMSSQLForm = RedshiftForm; |
556 | 748 |
|
@@ -1110,19 +1302,27 @@ const BigqueryForm = ( |
1110 | 1302 | }; |
1111 | 1303 |
|
1112 | 1304 | function extractPrefix(url) { |
1113 | | - const parts = (url ?? '').split('@'); |
1114 | | - if (!parts[0]) { |
| 1305 | + if (!url) { |
1115 | 1306 | return ''; |
1116 | 1307 | } |
1117 | | - return `${parts[0]}@`; |
| 1308 | + |
| 1309 | + if (url.includes('@')) { |
| 1310 | + const parts = url.split('@'); |
| 1311 | + if (!parts[0]) { |
| 1312 | + return ''; |
| 1313 | + } |
| 1314 | + return `${parts[0]}@`; |
| 1315 | + } |
| 1316 | + |
| 1317 | + return url.slice(0, url.indexOf('://') + 3); |
1118 | 1318 | } |
1119 | 1319 |
|
1120 | 1320 | function shouldRefreshUrl(previous, current) { |
1121 | 1321 | if (current.connect_by_url) { |
1122 | 1322 | return false; |
1123 | 1323 | } |
1124 | 1324 |
|
1125 | | - const fields = ['sql_flavor', 'project_host', 'project_port', 'project_db', 'project_user', 'connect_by_key', 'http_path', 'warehouse']; |
| 1325 | + const fields = ['sql_flavor', 'project_host', 'project_port', 'project_db', 'project_user', 'connect_by_key', 'http_path', 'warehouse', 'connect_with_identity']; |
1126 | 1326 | return fields.some((fieldName) => previous[fieldName] !== current[fieldName]); |
1127 | 1327 | } |
1128 | 1328 |
|
|
0 commit comments