From 21181e7b933af725aee0095a33bf375e307dfbce Mon Sep 17 00:00:00 2001 From: Lyubov Voloshko Date: Mon, 20 Apr 2026 09:59:39 +0000 Subject: [PATCH 1/4] refactor db creds fieldsets, add connection string and validator --- frontend/package.json | 1 + .../connect-db/connect-db.component.css | 37 +++- .../connect-db/connect-db.component.html | 195 ++++-------------- .../connect-db/connect-db.component.ts | 135 ++++++++++-- .../connection-string-validator.directive.ts | 19 ++ .../validators/connection-string.validator.ts | 130 ++++++++++++ frontend/yarn.lock | 10 + 7 files changed, 355 insertions(+), 172 deletions(-) create mode 100644 frontend/src/app/directives/connection-string-validator.directive.ts create mode 100644 frontend/src/app/validators/connection-string.validator.ts diff --git a/frontend/package.json b/frontend/package.json index 66db77136..4e471624b 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -46,6 +46,7 @@ "chart.js": "^4.5.1", "chartjs-adapter-date-fns": "^3.0.0", "color-string": "^2.0.1", + "connection-string-parser": "^1.0.4", "convert": "^5.12.0", "date-fns": "^4.1.0", "ipaddr.js": "^2.2.0", diff --git a/frontend/src/app/components/connect-db/connect-db.component.css b/frontend/src/app/components/connect-db/connect-db.component.css index 359d9d312..990f51ded 100644 --- a/frontend/src/app/components/connect-db/connect-db.component.css +++ b/frontend/src/app/components/connect-db/connect-db.component.css @@ -24,7 +24,7 @@ } } -.credentials-fieldset { +:host ::ng-deep .credentials-fieldset { display: grid; grid-template-columns: subgrid; grid-template-rows: subgrid; @@ -32,14 +32,24 @@ grid-row: 5 / span 4; } +:host ::ng-deep .connectForm__typeSwitch + .credentials-fieldset { + grid-row: 4 / span 4; +} + +:host ::ng-deep .connectForm__warningMessage + .connectForm__credentialsModeSwitch + .credentials-fieldset { + grid-row: 6 / span 4; +} + + + @media (width <= 600px) { - .credentials-fieldset { + :host ::ng-deep .credentials-fieldset { display: flex; flex-direction: column; } } -.credentials-fieldset-no-warning { +/* :host ::ng-deep .credentials-fieldset-no-warning { display: grid; grid-template-columns: subgrid; grid-template-rows: subgrid; @@ -48,11 +58,11 @@ } @media (width <= 600px) { - .credentials-fieldset-no-warning { + :host ::ng-deep .credentials-fieldset-no-warning { display: flex; flex-direction: column; } -} +} */ .mat-h1 { margin-top: 2vw; @@ -87,7 +97,8 @@ } .connectForm__ipAlert { - --alert-margin: 0; + --alert-margin: 0 !important; + margin-bottom: 12px; } .connectForm__title { @@ -103,11 +114,11 @@ grid-column: 1 / span 4; } -.connectForm__typeSwitch { +.connectForm__toggle { margin-bottom: 12px; } -.connectForm__typeSwitch ::ng-deep .mat-button-toggle-checked { +.connectForm__toggle ::ng-deep .mat-button-toggle-checked { background-color: rgba(0, 0, 0, 0.08) !important; } @@ -200,6 +211,16 @@ padding: 8px 12px; } +.connectForm__connectionString { + display: flex; + align-items: flex-start; + gap: 12px; +} + +.connectForm__connectionString button { + margin-top: 4px; +} + .agent-token { display: flex; margin-top: 32px; diff --git a/frontend/src/app/components/connect-db/connect-db.component.html b/frontend/src/app/components/connect-db/connect-db.component.html index 38729bb20..113aaf80c 100644 --- a/frontend/src/app/components/connect-db/connect-db.component.html +++ b/frontend/src/app/components/connect-db/connect-db.component.html @@ -38,10 +38,9 @@

-
+
Direct connection @@ -51,7 +50,7 @@

+ class="connectForm__fullLine connectForm__warningMessage">
warning_amber
@@ -72,156 +71,50 @@

- - - - - - - - - - - - - - - - - - - - - - - - - - + @if (db.connectionType === 'direct' && !db.id) { +
+ + Manual + Connection string + +
+ } - - + @if (connectionInputMode === 'connectionString' && db.connectionType === 'direct' && !db.id) { +
+ + Connection string + + Paste your database connection URI to auto-fill credentials + @if (connectionStringInput.errors?.invalidConnectionStringFormat) { + Invalid format. Expected: scheme://user:password@host:port/database + } + @if (connectionStringInput.errors?.unsupportedScheme) { + Unsupported scheme "{{ connectionStringInput.errors?.unsupportedScheme }}" + } + @if (connectionStringInput.errors?.invalidConnectionString) { + Failed to parse connection string + } + + +
+ } - - +
diff --git a/frontend/src/app/components/connect-db/connect-db.component.ts b/frontend/src/app/components/connect-db/connect-db.component.ts index b225ac880..6a877f92b 100644 --- a/frontend/src/app/components/connect-db/connect-db.component.ts +++ b/frontend/src/app/components/connect-db/connect-db.component.ts @@ -1,6 +1,6 @@ import { CdkCopyToClipboard } from '@angular/cdk/clipboard'; import { CommonModule } from '@angular/common'; -import { Component, NgZone, OnInit } from '@angular/core'; +import { Component, ComponentRef, DoCheck, NgZone, OnDestroy, OnInit, Type, ViewChild, ViewContainerRef } from '@angular/core'; import { FormsModule, NgForm } from '@angular/forms'; import { MatButtonModule } from '@angular/material/button'; import { MatButtonToggleModule } from '@angular/material/button-toggle'; @@ -28,6 +28,9 @@ import { NotificationsService } from 'src/app/services/notifications.service'; import { UserService } from 'src/app/services/user.service'; import { environment } from 'src/environments/environment'; import isIP from 'validator/lib/isIP'; +import { ConnectionStringValidatorDirective } from '../../directives/connection-string-validator.directive'; +import { parseConnectionString } from '../../validators/connection-string.validator'; +import { BaseCredentialsFormComponent } from './db-credentials-forms/base-credentials-form/base-credentials-form.component'; import { AlertComponent } from '../ui-components/alert/alert.component'; import { IpAddressButtonComponent } from '../ui-components/ip-address-button/ip-address-button.component'; import { DbConnectionConfirmDialogComponent } from './db-connection-confirm-dialog/db-connection-confirm-dialog.component'; @@ -63,25 +66,17 @@ import { RedisCredentialsFormComponent } from './db-credentials-forms/redis-cred MatDialogModule, MatCheckboxModule, MatSlideToggleModule, - Db2CredentialsFormComponent, - DynamodbCredentialsFormComponent, - CassandraCredentialsFormComponent, - MongodbCredentialsFormComponent, - MssqlCredentialsFormComponent, - MysqlCredentialsFormComponent, - OracledbCredentialsFormComponent, - PostgresCredentialsFormComponent, - RedisCredentialsFormComponent, - ElasticCredentialsFormComponent, - ClickhouseCredentialsFormComponent, IpAddressButtonComponent, AlertComponent, Angulartics2Module, + ConnectionStringValidatorDirective, ], }) -export class ConnectDBComponent implements OnInit { +export class ConnectDBComponent implements OnInit, DoCheck, OnDestroy { protected posthog = posthog; + @ViewChild('credentialsFormContainer', { read: ViewContainerRef }) credentialsFormContainer: ViewContainerRef; + public isSaas = (environment as any).saas; public connectionID: string | null = null; public masterKey: string; @@ -94,6 +89,23 @@ export class ConnectDBComponent implements OnInit { message: null, }; + public connectionInputMode: 'manual' | 'connectionString' = 'manual'; + public connectionString: string = ''; + + public credentialsFormMap: Record> = { + [DBtype.MySQL]: MysqlCredentialsFormComponent, + [DBtype.Postgres]: PostgresCredentialsFormComponent, + [DBtype.Mongo]: MongodbCredentialsFormComponent, + [DBtype.Dynamo]: DynamodbCredentialsFormComponent, + [DBtype.Cassandra]: CassandraCredentialsFormComponent, + [DBtype.Oracle]: OracledbCredentialsFormComponent, + [DBtype.MSSQL]: MssqlCredentialsFormComponent, + [DBtype.Redis]: RedisCredentialsFormComponent, + [DBtype.Elasticsearch]: ElasticCredentialsFormComponent, + [DBtype.ClickHouse]: ClickhouseCredentialsFormComponent, + [DBtype.DB2]: Db2CredentialsFormComponent, + }; + public supportedOrderedDatabases = supportedOrderedDatabases; public supportedDatabasesTitles = supportedDatabasesTitles; public ports = { @@ -445,6 +457,47 @@ export class ConnectDBComponent implements OnInit { this.masterKey = newMasterKey; } + ngDoCheck() { + this._updateCredentialsForm(); + } + + ngOnDestroy() { + this._destroyCredentialsForm(); + } + + applyConnectionString() { + if (!this.connectionString.trim()) { + return; + } + + try { + const parsed = parseConnectionString(this.connectionString); + + this.db.type = parsed.dbType; + this.db.host = parsed.host; + this.db.port = parsed.port; + this.db.username = parsed.username; + this.db.password = parsed.password; + this.db.database = parsed.database; + + if (parsed.authSource) { + this.db.authSource = parsed.authSource; + } + if (parsed.schema) { + this.db.schema = parsed.schema; + } + if (parsed.ssl) { + this.db.ssl = true; + } + + this.connectionInputMode = 'manual'; + this.connectionString = ''; + this._notifications.showSuccessSnackbar('Connection string parsed successfully'); + } catch (_e) { + // Validation directive handles error display + } + } + getProvider() { let provider: string = null; if (this.db.host.endsWith('.amazonaws.com')) provider = 'amazon'; @@ -464,4 +517,60 @@ export class ConnectDBComponent implements OnInit { } return provider; } + + private _credentialsFormRef: ComponentRef | null = null; + private _credentialsFormType: Type | null = null; + private _outputSubscriptions: { switchToAgent?: any; masterKeyChange?: any } = {}; + + private _updateCredentialsForm() { + if (!this.credentialsFormContainer) { + return; + } + + const isConnectionStringMode = this.connectionInputMode === 'connectionString' && this.db.connectionType === 'direct' && !this.db.id; + const targetType = (!isConnectionStringMode && this.db.connectionType === 'direct' && this.credentialsFormMap[this.db.type]) || null; + + if (targetType !== this._credentialsFormType) { + this._destroyCredentialsForm(); + + if (targetType) { + this._credentialsFormRef = this.credentialsFormContainer.createComponent(targetType); + this._credentialsFormType = targetType; + + const instance = this._credentialsFormRef.instance; + this._outputSubscriptions.switchToAgent = instance.switchToAgent.subscribe(() => this.switchToAgent()); + this._outputSubscriptions.masterKeyChange = instance.masterKeyChange.subscribe((key: string) => + this.handleMasterKeyChange(key), + ); + } + } + + if (this._credentialsFormRef) { + const instance = this._credentialsFormRef.instance; + instance.connection = this.db; + instance.submitting = this.submitting; + instance.accessLevel = this.accessLevel; + instance.masterKey = this.masterKey; + instance.readonly = !!((this.accessLevel === 'readonly' || this.db.isTestConnection) && this.db.id); + + const hostEl = this._credentialsFormRef.location.nativeElement as HTMLElement; + hostEl.classList.add('credentials-fieldset'); + } + } + + private _destroyCredentialsForm() { + this._outputSubscriptions.switchToAgent?.unsubscribe(); + this._outputSubscriptions.masterKeyChange?.unsubscribe(); + this._outputSubscriptions = {}; + + if (this._credentialsFormRef) { + this._credentialsFormRef.destroy(); + this._credentialsFormRef = null; + this._credentialsFormType = null; + } + + if (this.credentialsFormContainer) { + this.credentialsFormContainer.clear(); + } + } } diff --git a/frontend/src/app/directives/connection-string-validator.directive.ts b/frontend/src/app/directives/connection-string-validator.directive.ts new file mode 100644 index 000000000..02bb48a8c --- /dev/null +++ b/frontend/src/app/directives/connection-string-validator.directive.ts @@ -0,0 +1,19 @@ +import { Directive } from '@angular/core'; +import { AbstractControl, NG_VALIDATORS, ValidationErrors, Validator } from '@angular/forms'; +import { connectionStringValidation } from '../validators/connection-string.validator'; + +@Directive({ + selector: '[connectionStringValidator][ngModel]', + providers: [ + { + provide: NG_VALIDATORS, + useExisting: ConnectionStringValidatorDirective, + multi: true, + }, + ], +}) +export class ConnectionStringValidatorDirective implements Validator { + validate(control: AbstractControl): ValidationErrors | null { + return connectionStringValidation()(control); + } +} diff --git a/frontend/src/app/validators/connection-string.validator.ts b/frontend/src/app/validators/connection-string.validator.ts new file mode 100644 index 000000000..37c24ad7d --- /dev/null +++ b/frontend/src/app/validators/connection-string.validator.ts @@ -0,0 +1,130 @@ +import { AbstractControl, ValidationErrors, ValidatorFn } from '@angular/forms'; +import { ConnectionStringParser } from 'connection-string-parser'; +import { DBtype } from '../models/connection'; + +export interface ParsedConnectionString { + dbType: DBtype; + host: string; + port: string; + username: string; + password: string; + database: string; + authSource?: string; + schema?: string; + ssl?: boolean; +} + +const schemeToDbType: Record = { + mysql: DBtype.MySQL, + mariadb: DBtype.MySQL, + postgres: DBtype.Postgres, + postgresql: DBtype.Postgres, + mongodb: DBtype.Mongo, + 'mongodb+srv': DBtype.Mongo, + mssql: DBtype.MSSQL, + sqlserver: DBtype.MSSQL, + oracle: DBtype.Oracle, + oracledb: DBtype.Oracle, + cassandra: DBtype.Cassandra, + redis: DBtype.Redis, + rediss: DBtype.Redis, + elasticsearch: DBtype.Elasticsearch, + clickhouse: DBtype.ClickHouse, + ibmdb2: DBtype.DB2, + db2: DBtype.DB2, +}; + +const defaultPorts: Record = { + [DBtype.MySQL]: '3306', + [DBtype.Postgres]: '5432', + [DBtype.Oracle]: '1521', + [DBtype.MSSQL]: '1433', + [DBtype.Mongo]: '27017', + [DBtype.Dynamo]: '', + [DBtype.Cassandra]: '9042', + [DBtype.Redis]: '6379', + [DBtype.Elasticsearch]: '9200', + [DBtype.ClickHouse]: '8443', + [DBtype.DB2]: '50000', +}; + +export function parseConnectionString(connectionString: string): ParsedConnectionString { + const schemeMatch = connectionString.match(/^([a-zA-Z][a-zA-Z0-9+.-]*):\/\//); + if (!schemeMatch) { + throw new Error('invalidFormat'); + } + + const scheme = schemeMatch[1].toLowerCase(); + const dbType = schemeToDbType[scheme]; + if (!dbType) { + throw new Error('unsupportedScheme'); + } + + const parser = new ConnectionStringParser({ scheme, hosts: [] }); + const parsed = parser.parse(connectionString); + + const result: ParsedConnectionString = { + dbType, + host: '', + port: defaultPorts[dbType], + username: '', + password: '', + database: '', + }; + + if (parsed.hosts?.length > 0) { + result.host = parsed.hosts[0].host || ''; + if (parsed.hosts[0].port) { + result.port = String(parsed.hosts[0].port); + } + } + + if (parsed.username) { + result.username = decodeURIComponent(parsed.username); + } + + if (parsed.password) { + result.password = decodeURIComponent(parsed.password); + } + + if (parsed.endpoint) { + result.database = parsed.endpoint; + } + + if (parsed.options) { + if (parsed.options.authSource) { + result.authSource = parsed.options.authSource; + } + if (parsed.options.schema) { + result.schema = parsed.options.schema; + } + if (parsed.options.ssl === 'true' || parsed.options.sslmode === 'require' || parsed.options.sslmode === 'verify-full') { + result.ssl = true; + } + } + + return result; +} + +export function connectionStringValidation(): ValidatorFn { + return (control: AbstractControl): ValidationErrors | null => { + const value = control.value as string; + if (!value || !value.trim()) { + return null; + } + + try { + parseConnectionString(value); + return null; + } catch (e) { + if (e.message === 'invalidFormat') { + return { invalidConnectionStringFormat: true }; + } + if (e.message === 'unsupportedScheme') { + const schemeMatch = value.match(/^([a-zA-Z][a-zA-Z0-9+.-]*):/); + return { unsupportedScheme: schemeMatch?.[1] || true }; + } + return { invalidConnectionString: true }; + } + }; +} diff --git a/frontend/yarn.lock b/frontend/yarn.lock index ecccd40e2..dde84d26b 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -7255,6 +7255,15 @@ __metadata: languageName: node linkType: hard +"connection-string-parser@npm:^1.0.4": + version: 1.0.4 + resolution: "connection-string-parser@npm:1.0.4" + dependencies: + tslib: ^2.0.0 + checksum: 9424a8b58c1f8eb68c51c7c8254a666f20b405659e3c02c07c2b6b975da643c4b1842704a600bb0448e8f78ad8b6c133a2aadad42493462e382af04ef6900267 + languageName: node + linkType: hard + "content-disposition@npm:^1.0.0": version: 1.0.1 resolution: "content-disposition@npm:1.0.1" @@ -12778,6 +12787,7 @@ __metadata: chart.js: ^4.5.1 chartjs-adapter-date-fns: ^3.0.0 color-string: ^2.0.1 + connection-string-parser: ^1.0.4 convert: ^5.12.0 date-fns: ^4.1.0 ipaddr.js: ^2.2.0 From 8c50fcc4bcebeebc080e52d41116f9b2f8b07a21 Mon Sep 17 00:00:00 2001 From: Lyubov Voloshko Date: Mon, 20 Apr 2026 13:47:20 +0000 Subject: [PATCH 2/4] substitute native js with ndc-dynamic component --- .../connect-db/connect-db.component.html | 7 +- .../connect-db/connect-db.component.ts | 80 ++++++------------- 2 files changed, 29 insertions(+), 58 deletions(-) diff --git a/frontend/src/app/components/connect-db/connect-db.component.html b/frontend/src/app/components/connect-db/connect-db.component.html index 113aaf80c..ced91f421 100644 --- a/frontend/src/app/components/connect-db/connect-db.component.html +++ b/frontend/src/app/components/connect-db/connect-db.component.html @@ -114,7 +114,12 @@

} - +
diff --git a/frontend/src/app/components/connect-db/connect-db.component.ts b/frontend/src/app/components/connect-db/connect-db.component.ts index 6a877f92b..5da775969 100644 --- a/frontend/src/app/components/connect-db/connect-db.component.ts +++ b/frontend/src/app/components/connect-db/connect-db.component.ts @@ -1,6 +1,6 @@ import { CdkCopyToClipboard } from '@angular/cdk/clipboard'; import { CommonModule } from '@angular/common'; -import { Component, ComponentRef, DoCheck, NgZone, OnDestroy, OnInit, Type, ViewChild, ViewContainerRef } from '@angular/core'; +import { Component, DoCheck, NgZone, OnInit, Type } from '@angular/core'; import { FormsModule, NgForm } from '@angular/forms'; import { MatButtonModule } from '@angular/material/button'; import { MatButtonToggleModule } from '@angular/material/button-toggle'; @@ -13,6 +13,7 @@ import { MatSlideToggleModule } from '@angular/material/slide-toggle'; import { MatTooltipModule } from '@angular/material/tooltip'; import { Title } from '@angular/platform-browser'; import { Router, RouterModule } from '@angular/router'; +import { DynamicAttributesDirective, DynamicModule } from 'ng-dynamic-component'; import { Angulartics2, Angulartics2Module } from 'angulartics2'; import * as ipaddr from 'ipaddr.js'; import posthog from 'posthog-js'; @@ -70,13 +71,13 @@ import { RedisCredentialsFormComponent } from './db-credentials-forms/redis-cred AlertComponent, Angulartics2Module, ConnectionStringValidatorDirective, + DynamicModule, + DynamicAttributesDirective, ], }) -export class ConnectDBComponent implements OnInit, DoCheck, OnDestroy { +export class ConnectDBComponent implements OnInit, DoCheck { protected posthog = posthog; - @ViewChild('credentialsFormContainer', { read: ViewContainerRef }) credentialsFormContainer: ViewContainerRef; - public isSaas = (environment as any).saas; public connectionID: string | null = null; public masterKey: string; @@ -458,11 +459,7 @@ export class ConnectDBComponent implements OnInit, DoCheck, OnDestroy { } ngDoCheck() { - this._updateCredentialsForm(); - } - - ngOnDestroy() { - this._destroyCredentialsForm(); + this._updateCredentialsFormInputs(); } applyConnectionString() { @@ -518,59 +515,28 @@ export class ConnectDBComponent implements OnInit, DoCheck, OnDestroy { return provider; } - private _credentialsFormRef: ComponentRef | null = null; - private _credentialsFormType: Type | null = null; - private _outputSubscriptions: { switchToAgent?: any; masterKeyChange?: any } = {}; - - private _updateCredentialsForm() { - if (!this.credentialsFormContainer) { - return; - } + public credentialsFormComponent: Type | null = null; + public credentialsFormInputs: Record = {}; + public credentialsFormOutputs: Record = { + switchToAgent: () => this.switchToAgent(), + masterKeyChange: (key: string) => this.handleMasterKeyChange(key), + }; + public credentialsFormAttributes: Record = { class: 'credentials-fieldset' }; + private _updateCredentialsFormInputs() { const isConnectionStringMode = this.connectionInputMode === 'connectionString' && this.db.connectionType === 'direct' && !this.db.id; const targetType = (!isConnectionStringMode && this.db.connectionType === 'direct' && this.credentialsFormMap[this.db.type]) || null; - if (targetType !== this._credentialsFormType) { - this._destroyCredentialsForm(); - - if (targetType) { - this._credentialsFormRef = this.credentialsFormContainer.createComponent(targetType); - this._credentialsFormType = targetType; - - const instance = this._credentialsFormRef.instance; - this._outputSubscriptions.switchToAgent = instance.switchToAgent.subscribe(() => this.switchToAgent()); - this._outputSubscriptions.masterKeyChange = instance.masterKeyChange.subscribe((key: string) => - this.handleMasterKeyChange(key), - ); - } - } - - if (this._credentialsFormRef) { - const instance = this._credentialsFormRef.instance; - instance.connection = this.db; - instance.submitting = this.submitting; - instance.accessLevel = this.accessLevel; - instance.masterKey = this.masterKey; - instance.readonly = !!((this.accessLevel === 'readonly' || this.db.isTestConnection) && this.db.id); - - const hostEl = this._credentialsFormRef.location.nativeElement as HTMLElement; - hostEl.classList.add('credentials-fieldset'); - } - } - - private _destroyCredentialsForm() { - this._outputSubscriptions.switchToAgent?.unsubscribe(); - this._outputSubscriptions.masterKeyChange?.unsubscribe(); - this._outputSubscriptions = {}; + this.credentialsFormComponent = targetType; - if (this._credentialsFormRef) { - this._credentialsFormRef.destroy(); - this._credentialsFormRef = null; - this._credentialsFormType = null; - } - - if (this.credentialsFormContainer) { - this.credentialsFormContainer.clear(); + if (targetType) { + this.credentialsFormInputs = { + connection: this.db, + submitting: this.submitting, + accessLevel: this.accessLevel, + masterKey: this.masterKey, + readonly: !!((this.accessLevel === 'readonly' || this.db.isTestConnection) && this.db.id), + }; } } } From ab623bdc267fb05a12ed3193f468dfece503ed8f Mon Sep 17 00:00:00 2001 From: Lyubov Voloshko Date: Tue, 21 Apr 2026 10:27:00 +0000 Subject: [PATCH 3/4] remove switcher and make connection string permanently seen, refactoring --- .../connect-db/connect-db.component.css | 9 ++-- .../connect-db/connect-db.component.html | 36 +++++++-------- .../connect-db/connect-db.component.ts | 46 ++++++------------- 3 files changed, 33 insertions(+), 58 deletions(-) diff --git a/frontend/src/app/components/connect-db/connect-db.component.css b/frontend/src/app/components/connect-db/connect-db.component.css index 990f51ded..34c6a8987 100644 --- a/frontend/src/app/components/connect-db/connect-db.component.css +++ b/frontend/src/app/components/connect-db/connect-db.component.css @@ -29,17 +29,13 @@ grid-template-columns: subgrid; grid-template-rows: subgrid; grid-column: 1 / span 4; - grid-row: 5 / span 4; + grid-row: 6 / span 4; } -:host ::ng-deep .connectForm__typeSwitch + .credentials-fieldset { +:host ::ng-deep .connectForm__typeSwitch + ndc-dynamic + .credentials-fieldset { grid-row: 4 / span 4; } -:host ::ng-deep .connectForm__warningMessage + .connectForm__credentialsModeSwitch + .credentials-fieldset { - grid-row: 6 / span 4; -} - @media (width <= 600px) { @@ -219,6 +215,7 @@ .connectForm__connectionString button { margin-top: 4px; + margin-left: -80px; } .agent-token { diff --git a/frontend/src/app/components/connect-db/connect-db.component.html b/frontend/src/app/components/connect-db/connect-db.component.html index ced91f421..554d27df9 100644 --- a/frontend/src/app/components/connect-db/connect-db.component.html +++ b/frontend/src/app/components/connect-db/connect-db.component.html @@ -72,18 +72,6 @@

@if (db.connectionType === 'direct' && !db.id) { -
- - Manual - Connection string - -
- } - - @if (connectionInputMode === 'connectionString' && db.connectionType === 'direct' && !db.id) {
Connection string @@ -105,7 +93,8 @@

Failed to parse connection string } -

} - + @if (db.connectionType === 'direct') { + + } +
diff --git a/frontend/src/app/components/connect-db/connect-db.component.ts b/frontend/src/app/components/connect-db/connect-db.component.ts index 5da775969..0fa290dc2 100644 --- a/frontend/src/app/components/connect-db/connect-db.component.ts +++ b/frontend/src/app/components/connect-db/connect-db.component.ts @@ -1,6 +1,6 @@ import { CdkCopyToClipboard } from '@angular/cdk/clipboard'; import { CommonModule } from '@angular/common'; -import { Component, DoCheck, NgZone, OnInit, Type } from '@angular/core'; +import { Component, NgZone, OnInit, Type } from '@angular/core'; import { FormsModule, NgForm } from '@angular/forms'; import { MatButtonModule } from '@angular/material/button'; import { MatButtonToggleModule } from '@angular/material/button-toggle'; @@ -75,7 +75,7 @@ import { RedisCredentialsFormComponent } from './db-credentials-forms/redis-cred DynamicAttributesDirective, ], }) -export class ConnectDBComponent implements OnInit, DoCheck { +export class ConnectDBComponent implements OnInit { protected posthog = posthog; public isSaas = (environment as any).saas; @@ -90,7 +90,6 @@ export class ConnectDBComponent implements OnInit, DoCheck { message: null, }; - public connectionInputMode: 'manual' | 'connectionString' = 'manual'; public connectionString: string = ''; public credentialsFormMap: Record> = { @@ -132,6 +131,14 @@ export class ConnectDBComponent implements OnInit, DoCheck { "This is a DEMO SESSION! It will disappear after you log out. Don't use databases you're actively using or that contain information you wish to retain.", }; + public credentialsFormComponent: Type | null = null; + public credentialsFormInputs: Record = {}; + public credentialsFormOutputs: Record = { + switchToAgent: () => this.switchToAgent(), + masterKeyChange: (key: string) => this.handleMasterKeyChange(key), + }; + public credentialsFormAttributes: Record = { class: 'credentials-fieldset' }; + constructor( private _connections: ConnectionsService, private _notifications: NotificationsService, @@ -158,6 +165,8 @@ export class ConnectDBComponent implements OnInit, DoCheck { this.db.port = this.ports[databaseType]; } + this.credentialsFormComponent = this.credentialsFormMap[this.db.type] || null; + this._connections .getCurrentConnectionTitle() .pipe(take(1)) @@ -190,6 +199,7 @@ export class ConnectDBComponent implements OnInit, DoCheck { dbTypeChange() { this.db.port = this.ports[this.db.type]; + this.credentialsFormComponent = this.credentialsFormMap[this.db.type] || null; } testConnection() { @@ -458,10 +468,6 @@ export class ConnectDBComponent implements OnInit, DoCheck { this.masterKey = newMasterKey; } - ngDoCheck() { - this._updateCredentialsFormInputs(); - } - applyConnectionString() { if (!this.connectionString.trim()) { return; @@ -487,7 +493,6 @@ export class ConnectDBComponent implements OnInit, DoCheck { this.db.ssl = true; } - this.connectionInputMode = 'manual'; this.connectionString = ''; this._notifications.showSuccessSnackbar('Connection string parsed successfully'); } catch (_e) { @@ -514,29 +519,4 @@ export class ConnectDBComponent implements OnInit, DoCheck { } return provider; } - - public credentialsFormComponent: Type | null = null; - public credentialsFormInputs: Record = {}; - public credentialsFormOutputs: Record = { - switchToAgent: () => this.switchToAgent(), - masterKeyChange: (key: string) => this.handleMasterKeyChange(key), - }; - public credentialsFormAttributes: Record = { class: 'credentials-fieldset' }; - - private _updateCredentialsFormInputs() { - const isConnectionStringMode = this.connectionInputMode === 'connectionString' && this.db.connectionType === 'direct' && !this.db.id; - const targetType = (!isConnectionStringMode && this.db.connectionType === 'direct' && this.credentialsFormMap[this.db.type]) || null; - - this.credentialsFormComponent = targetType; - - if (targetType) { - this.credentialsFormInputs = { - connection: this.db, - submitting: this.submitting, - accessLevel: this.accessLevel, - masterKey: this.masterKey, - readonly: !!((this.accessLevel === 'readonly' || this.db.isTestConnection) && this.db.id), - }; - } - } } From 9ad8a4b8e96ed76f5c2d799e6ab6e07c743da1b8 Mon Sep 17 00:00:00 2001 From: Lyubov Voloshko Date: Tue, 21 Apr 2026 10:35:08 +0000 Subject: [PATCH 4/4] connection string: fix right padding --- .../src/app/components/connect-db/connect-db.component.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/frontend/src/app/components/connect-db/connect-db.component.css b/frontend/src/app/components/connect-db/connect-db.component.css index 34c6a8987..47be30a50 100644 --- a/frontend/src/app/components/connect-db/connect-db.component.css +++ b/frontend/src/app/components/connect-db/connect-db.component.css @@ -213,6 +213,10 @@ gap: 12px; } +.connectForm__connectionString ::ng-deep .mat-mdc-text-field-wrapper { + padding-right: 80px; +} + .connectForm__connectionString button { margin-top: 4px; margin-left: -80px;