Skip to content

Commit 6ebb952

Browse files
add clickhouse db
1 parent 42ae2a1 commit 6ebb952

File tree

14 files changed

+335
-2
lines changed

14 files changed

+335
-2
lines changed

frontend/src/app/app.component.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ export class AppComponent {
111111
this.matIconRegistry.addSvgIcon("cassandra", this.domSanitizer.bypassSecurityTrustResourceUrl("/assets/icons/db-logos/сassandra_logo.svg"));
112112
this.matIconRegistry.addSvgIcon("redis", this.domSanitizer.bypassSecurityTrustResourceUrl("/assets/icons/db-logos/redis_logo.svg"));
113113
this.matIconRegistry.addSvgIcon("elasticsearch", this.domSanitizer.bypassSecurityTrustResourceUrl("/assets/icons/db-logos/elasticsearch_logo.svg"));
114+
this.matIconRegistry.addSvgIcon("clickhouse", this.domSanitizer.bypassSecurityTrustResourceUrl("/assets/icons/db-logos/clickhouse_logo.svg"));
114115
this.matIconRegistry.addSvgIcon("github", this.domSanitizer.bypassSecurityTrustResourceUrl("/assets/icons/github.svg"));
115116
this.matIconRegistry.addSvgIcon("google", this.domSanitizer.bypassSecurityTrustResourceUrl("/assets/icons/google.svg"));
116117
this.matIconRegistry.addSvgIcon("ai_rocket", this.domSanitizer.bypassSecurityTrustResourceUrl("/assets/icons/ai-rocket.svg"));

frontend/src/app/components/connect-db/connect-db.component.html

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,20 @@ <h1 class="mat-h1 connectForm__fullLine">
193193
(masterKeyChange)="handleMasterKeyChange($event)">
194194
</app-elastic-credentials-form>
195195

196+
<app-clickhouse-credentials-form *ngIf="db.type === 'clickhouse' && db.connectionType === 'direct'"
197+
[ngClass]="{
198+
'credentials-fieldset': !db.isTestConnection,
199+
'credentials-fieldset-no-warning': db.isTestConnection || !isSaas
200+
}"
201+
[connection]="db"
202+
[submitting]="submitting"
203+
[accessLevel]="accessLevel"
204+
[masterKey]="masterKey"
205+
[readonly]="(accessLevel === 'readonly' || db.isTestConnection) && db.id"
206+
(switchToAgent)="switchToAgent()"
207+
(masterKeyChange)="handleMasterKeyChange($event)">
208+
</app-clickhouse-credentials-form>
209+
196210
<app-db2-credentials-form *ngIf="db.type === 'ibmdb2' && db.connectionType === 'direct'"
197211
[ngClass]="{
198212
'credentials-fieldset': !db.isTestConnection,

frontend/src/app/components/connect-db/connect-db.component.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ import { UserService } from 'src/app/services/user.service';
4747
import { environment } from 'src/environments/environment';
4848
import googlIPsList from 'src/app/consts/google-IP-addresses';
4949
import isIP from 'validator/lib/isIP';
50+
import { ClickhouseCredentialsFormComponent } from './db-credentials-forms/clickhouse-credentials-form/clickhouse-credentials-form.component';
5051

5152
@Component({
5253
selector: 'app-connect-db',
@@ -76,6 +77,7 @@ import isIP from 'validator/lib/isIP';
7677
PostgresCredentialsFormComponent,
7778
RedisCredentialsFormComponent,
7879
ElasticCredentialsFormComponent,
80+
ClickhouseCredentialsFormComponent,
7981
IpAddressButtonComponent,
8082
AlertComponent,
8183
Angulartics2Module
@@ -107,6 +109,7 @@ export class ConnectDBComponent implements OnInit {
107109
[DBtype.Cassandra]: '9042',
108110
[DBtype.Redis]: '6379',
109111
[DBtype.Elasticsearch]: '9200',
112+
[DBtype.ClickHouse]: '8443',
110113
[DBtype.DB2]: '50000'
111114
}
112115

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
.connectForm__hostname,
2+
.connectForm__port {
3+
padding-bottom: 20px;
4+
}
5+
6+
@media (width <= 600px) {
7+
.connectForm__hostname {
8+
padding-bottom: 44px;
9+
}
10+
11+
.connectForm__port {
12+
padding-bottom: 0;
13+
}
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
<mat-form-field appearance="outline" class="connectForm__hostname credentials-fieldset__1-3-columns">
2+
<mat-label>Hostname</mat-label>
3+
<input matInput name="hostname" #hostname="ngModel"
4+
data-testid="connection-hostname-input"
5+
angulartics2On="change"
6+
angularticsAction="Connection creds {{ connection.id ? 'edit' : 'add' }}: hostname is edited"
7+
required hostnameValidator
8+
[readonly]="readonly"
9+
[disabled]="submitting"
10+
[(ngModel)]="connection.host">
11+
<mat-hint>
12+
E.g. <strong><code>my-test-db.cvfuxe8nltiq.us-east-2.rds.amazonaws.com</code></strong>
13+
<br/>
14+
Connections from internal IPs (e.g. localhost) are not supported
15+
</mat-hint>
16+
17+
<mat-error *ngIf="hostname.errors?.isLocalhost && hostname.invalid">
18+
To connect a database to an internal IP, use something like <strong>Pinggy</strong>
19+
(<a [href]="tunnelingServiceLink" target="_blank" class="credentials-fieldset__hint-link">here's a guide</a>),
20+
or <button type="button" (click)="switchToAgent.emit()" class="credentials-fieldset__hint-button">click here</button> to connect through an agent
21+
</mat-error>
22+
<mat-error *ngIf="hostname.errors?.isInvalidHostname && hostname.invalid">Hostname is invalid</mat-error>
23+
</mat-form-field>
24+
25+
<mat-form-field appearance="outline" class="connectForm__port">
26+
<mat-label>Port</mat-label>
27+
<input matInput type="number" name="port" #port="ngModel"
28+
data-testid="connection-port-input"
29+
angulartics2On="change"
30+
angularticsAction="Connection creds {{ connection.id ? 'edit' : 'add' }}: port is edited"
31+
required
32+
[readonly]="readonly"
33+
[disabled]="submitting"
34+
[(ngModel)]="connection.port">
35+
<mat-error *ngIf="port.errors?.required && (port.invalid && port.touched)">Port should not be empty</mat-error>
36+
</mat-form-field>
37+
38+
<mat-form-field appearance="outline" class="credentials-fieldset__1-2-columns">
39+
<mat-label>Username</mat-label>
40+
<input matInput name="username" #username="ngModel"
41+
data-testid="connection-username-input"
42+
angulartics2On="change"
43+
angularticsAction="Connection creds {{ connection.id ? 'edit' : 'add' }}: username is edited"
44+
required
45+
[readonly]="readonly"
46+
[disabled]="submitting"
47+
[(ngModel)]="connection.username">
48+
<mat-error *ngIf="username.errors?.required && (username.invalid && username.touched)">Username should not be empty</mat-error>
49+
</mat-form-field>
50+
51+
<mat-form-field appearance="outline" class="credentials-fieldset__3-4-columns">
52+
<mat-label>Password</mat-label>
53+
<input type="password" matInput name="password" #password="ngModel"
54+
data-testid="connection-password-input"
55+
angulartics2On="change"
56+
angularticsAction="Connection creds {{ connection.id ? 'edit' : 'add' }}: password is edited"
57+
[required]="!connection.id || hostname.touched || port.touched"
58+
[readonly]="readonly"
59+
[disabled]="submitting"
60+
[(ngModel)]="connection.password">
61+
<mat-hint *ngIf="connection.id && (hostname.pristine && port.pristine)">To keep password the same keep this field blank</mat-hint>
62+
<mat-hint *ngIf="connection.id && (hostname.dirty || port.dirty)">Password needed due to hostname/port change</mat-hint>
63+
<!-- <mat-error *ngIf="email.errors.required && (email.invalid && email.touched)">Email should not be empty</mat-error> -->
64+
</mat-form-field>
65+
66+
<mat-form-field appearance="outline" class="credentials-fieldset__1-4-columns">
67+
<mat-label>Database Name</mat-label>
68+
<input matInput name="database" #database="ngModel"
69+
data-testid="connection-database-input"
70+
angulartics2On="change"
71+
angularticsAction="Connection creds {{ connection.id ? 'edit' : 'add' }}: database name is edited"
72+
required
73+
[readonly]="readonly"
74+
[disabled]="submitting"
75+
[(ngModel)]="connection.database">
76+
<mat-error *ngIf="database.errors?.required && (database.invalid && database.touched)">Name should not be empty</mat-error>
77+
</mat-form-field>
78+
79+
<mat-expansion-panel class="credentials-fieldset__1-4-columns">
80+
<mat-expansion-panel-header data-testid="connection-advanced-settings-expansion-panel-header">
81+
<mat-panel-title>
82+
Advanced settings
83+
</mat-panel-title>
84+
</mat-expansion-panel-header>
85+
86+
<div class="advanced-settings">
87+
<app-master-encryption-password
88+
class="advanced-settings__fullLine"
89+
[masterKey]="masterKey"
90+
[disabled]="accessLevel === 'readonly' || submitting || connection?.isTestConnection"
91+
(onMasterKeyChange)="handleMasterKeyChange($event)">
92+
</app-master-encryption-password>
93+
94+
<mat-checkbox class="checkbox-line advanced-settings__fullLine" name="ssh" #ssh="ngModel"
95+
data-testid="connection-ssh-checkbox"
96+
labelPosition="after"
97+
angulartics2On="click"
98+
angularticsAction="Connection creds {{ connection.id ? 'edit' : 'add' }}: SSH is switched"
99+
[angularticsProperties]="{'enable': connection.ssh}"
100+
[disabled]="submitting || connection.isTestConnection"
101+
[(ngModel)]="connection.ssh">
102+
Use SSH tunnel
103+
</mat-checkbox>
104+
105+
<mat-form-field *ngIf="connection.ssh" appearance="outline" floatLabel="always" class="advanced-settings__fullLine">
106+
<mat-label>Private SSH key</mat-label>
107+
<textarea matInput resizeToFitContent rows="8" name="privateSSHKey" #privateSSHKey="ngModel"
108+
placeholder="Sensitive — write-only field"
109+
data-testid="connection-ssh-key-textarea"
110+
angulartics2On="change"
111+
angularticsAction="Connection creds {{ connection.id ? 'edit' : 'add' }}: SSH key is edited"
112+
[required]="connection.ssh && !connection.id" [readonly]="accessLevel === 'readonly' && connection.id"
113+
[disabled]="submitting"
114+
[(ngModel)]="connection.privateSSHKey"
115+
></textarea>
116+
<mat-error *ngIf="privateSSHKey.errors?.required && (privateSSHKey.invalid && privateSSHKey.touched)">Private SSH key should not be empty</mat-error>
117+
</mat-form-field>
118+
119+
<mat-form-field *ngIf="connection.ssh" appearance="outline">
120+
<mat-label>SSH host</mat-label>
121+
<input matInput name="sshHost" #sshHost="ngModel"
122+
data-testid="connection-ssh-host-input"
123+
angulartics2On="change"
124+
angularticsAction="Connection creds {{ connection.id ? 'edit' : 'add' }}: SSH host is edited"
125+
[required]="connection.ssh" [readonly]="accessLevel === 'readonly' && connection.id"
126+
[disabled]="submitting"
127+
[(ngModel)]="connection.sshHost">
128+
<mat-error *ngIf="sshHost.errors?.required && (sshHost.invalid && sshHost.touched)">SSH host should not be empty</mat-error>
129+
</mat-form-field>
130+
131+
<mat-form-field *ngIf="connection.ssh" appearance="outline">
132+
<mat-label>SSH port</mat-label>
133+
<input matInput type="number" name="sshPort" #sshPort="ngModel"
134+
data-testid="connection-ssh-port-input"
135+
angulartics2On="change"
136+
angularticsAction="Connection creds {{ connection.id ? 'edit' : 'add' }}: SSH port is edited"
137+
[required]="connection.ssh" [readonly]="accessLevel === 'readonly' && connection.id"
138+
[disabled]="submitting"
139+
[(ngModel)]="connection.sshPort">
140+
<mat-error *ngIf="sshPort.errors?.required && (sshPort.invalid && sshPort.touched)">SSH port should not be empty</mat-error>
141+
</mat-form-field>
142+
143+
<mat-form-field *ngIf="connection.ssh" appearance="outline" floatLabel="always">
144+
<mat-label>SSH username</mat-label>
145+
<input matInput name="sshUsername" #sshUsername="ngModel"
146+
placeholder="Sensitive — write-only field"
147+
data-testid="connection-ssh-username-input"
148+
angulartics2On="change"
149+
angularticsAction="Connection creds {{ connection.id ? 'edit' : 'add' }}: SSH username is edited"
150+
[required]="connection.ssh" [readonly]="accessLevel === 'readonly' && connection.id"
151+
[disabled]="submitting"
152+
[(ngModel)]="connection.sshUsername">
153+
<mat-error *ngIf="sshUsername.errors?.required && (sshUsername.invalid && sshUsername.touched)">SSH username should not be empty</mat-error>
154+
</mat-form-field>
155+
156+
<mat-checkbox class="checkbox-line advanced-settings__fullLine" name="ssl" #ssh="ngModel"
157+
labelPosition="after"
158+
data-testid="connection-ssl-checkbox"
159+
angulartics2On="click"
160+
angularticsAction="Connection creds {{ connection.id ? 'edit' : 'add' }}: SSL is switched"
161+
[angularticsProperties]="{'enable': connection.ssl}"
162+
[disabled]="submitting || connection.isTestConnection"
163+
[(ngModel)]="connection.ssl">
164+
Check SSL certificate
165+
</mat-checkbox>
166+
167+
<mat-form-field *ngIf="connection.ssl" appearance="outline" class="advanced-settings__fullLine">
168+
<mat-label>SSL certificate</mat-label>
169+
<textarea matInput resizeToFitContent rows="8" name="sslCert" #sslCert="ngModel"
170+
data-testid="connection-ssl-certificate-textarea"
171+
angulartics2On="change"
172+
angularticsAction="Connection creds {{ connection.id ? 'edit' : 'add' }}: SSL certificate is edited"
173+
[required]="connection.ssl" [readonly]="accessLevel === 'readonly' && connection.id"
174+
[disabled]="submitting"
175+
[(ngModel)]="connection.cert"
176+
></textarea>
177+
<mat-error *ngIf="sslCert.errors?.required && (sslCert.invalid && sslCert.touched)">SSL certificate should not be empty</mat-error>
178+
</mat-form-field>
179+
</div>
180+
</mat-expansion-panel>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { ComponentFixture, TestBed } from '@angular/core/testing';
2+
3+
import { ClickhouseCredentialsFormComponent } from './clickhouse-credentials-form.component';
4+
import { FormsModule } from '@angular/forms';
5+
import { MatCheckboxModule } from '@angular/material/checkbox';
6+
import { Angulartics2Module } from 'angulartics2';
7+
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
8+
import { provideHttpClient } from '@angular/common/http';
9+
10+
describe('ClickhouseCredentialsFormComponent', () => {
11+
let component: ClickhouseCredentialsFormComponent;
12+
let fixture: ComponentFixture<ClickhouseCredentialsFormComponent>;
13+
14+
beforeEach(async () => {
15+
await TestBed.configureTestingModule({
16+
imports: [
17+
FormsModule,
18+
MatCheckboxModule,
19+
BrowserAnimationsModule,
20+
Angulartics2Module.forRoot({}),
21+
ClickhouseCredentialsFormComponent
22+
],
23+
providers: [provideHttpClient()]
24+
})
25+
.compileComponents();
26+
27+
fixture = TestBed.createComponent(ClickhouseCredentialsFormComponent);
28+
component = fixture.componentInstance;
29+
30+
component.connection = {
31+
id: "12345678"
32+
} as any;
33+
34+
fixture.detectChanges();
35+
});
36+
37+
it('should create', () => {
38+
expect(component).toBeTruthy();
39+
});
40+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { Angulartics2Module } from 'angulartics2';
2+
import { BaseCredentialsFormComponent } from '../base-credentials-form/base-credentials-form.component';
3+
import { Component } from '@angular/core';
4+
import { FormsModule } from '@angular/forms';
5+
import { HostnameValidationDirective } from 'src/app/directives/hostnameValidator.directive';
6+
import { MasterEncryptionPasswordComponent } from '../../master-encryption-password/master-encryption-password.component';
7+
import { MatCheckboxModule } from '@angular/material/checkbox';
8+
import { MatExpansionModule } from '@angular/material/expansion';
9+
import { MatFormFieldModule } from '@angular/material/form-field';
10+
import { MatInputModule } from '@angular/material/input';
11+
import { NgIf } from '@angular/common';
12+
13+
@Component({
14+
selector: 'app-clickhouse-credentials-form',
15+
templateUrl: './clickhouse-credentials-form.component.html',
16+
styleUrls: ['../base-credentials-form/base-credentials-form.component.css', './clickhouse-credentials-form.component.css'],
17+
standalone: true,
18+
imports: [
19+
NgIf,
20+
FormsModule,
21+
MatFormFieldModule,
22+
MatInputModule,
23+
MatCheckboxModule,
24+
MatExpansionModule,
25+
HostnameValidationDirective,
26+
MasterEncryptionPasswordComponent,
27+
Angulartics2Module
28+
]
29+
})
30+
export class ClickhouseCredentialsFormComponent extends BaseCredentialsFormComponent {
31+
32+
}

frontend/src/app/components/db-table-row-edit/db-table-row-edit.component.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ export class DbTableRowEditComponent implements OnInit {
227227
.filter((field: TableField) => !this.getModifyingFields(res.structure).some(modifyingField => field.column_name === modifyingField.column_name))
228228
.map((field: TableField) => field.column_name);
229229
this.readonlyFields = [...res.readonly_fields, ...this.nonModifyingFields];
230-
if (this.connectionType === DBtype.Dynamo) {
230+
if (this.connectionType === DBtype.Dynamo || this.connectionType === DBtype.ClickHouse) {
231231
this.readonlyFields = [...this.readonlyFields, ...res.primaryColumns.map((field: TableField) => field.column_name)];
232232
}
233233
this.tableForeignKeys = res.foreignKeys;
@@ -584,7 +584,7 @@ export class DbTableRowEditComponent implements OnInit {
584584
//end crutch
585585

586586
// don't ovverride primary key fields for dynamoDB
587-
if (this.connectionType === DBtype.Dynamo) {
587+
if (this.connectionType === DBtype.Dynamo || this.connectionType === DBtype.ClickHouse) {
588588
const primaryKeyFields = Object.keys(this.keyAttributesFromURL);
589589
primaryKeyFields.forEach((field) => {
590590
delete updatedRow[field];

frontend/src/app/consts/databases.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export const supportedOrderedDatabases = [
88
"mssql",
99
"redis",
1010
"elasticsearch",
11+
"clickhouse",
1112
"ibmdb2"
1213
]
1314

@@ -21,5 +22,6 @@ export const supportedDatabasesTitles = {
2122
mssql: "SQL Server",
2223
redis: "Redis",
2324
elasticsearch: "Elasticsearch",
25+
clickhouse: "ClickHouse",
2426
ibmdb2: "IBM DB2"
2527
}

frontend/src/app/consts/record-edit-types.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,5 +327,20 @@ export const recordEditTypes = {
327327
object: JsonEditorEditComponent,
328328
array: JsonEditorEditComponent,
329329
binary: FileEditComponent,
330+
},
331+
clickhouse: {
332+
string: TextEditComponent,
333+
uuid: UuidEditComponent,
334+
boolean: BooleanEditComponent,
335+
integer: NumberEditComponent,
336+
bigint: NumberEditComponent,
337+
float: NumberEditComponent,
338+
double: NumberEditComponent,
339+
decimal: NumberEditComponent,
340+
date: DateEditComponent,
341+
datetime: DateTimeEditComponent,
342+
json: JsonEditorEditComponent,
343+
object: JsonEditorEditComponent,
344+
array: JsonEditorEditComponent,
330345
}
331346
}

0 commit comments

Comments
 (0)