Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions frontend/src/app/components/connect-db/connect-db.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ <h1 class="mat-h1 connectForm__fullLine">
<mat-option value="postgres">PostgreSQL</mat-option>
<mat-option value="mongodb">MongoDB</mat-option>
<mat-option value="dynamodb" *ngIf="db.connectionType === 'direct'">DynamoDB</mat-option>
<mat-option value="cassandra">Cassandra</mat-option>
<mat-option value="oracledb">Oracle</mat-option>
<mat-option value="mssql">MS SQL</mat-option>
<mat-option value="ibmdb2">IBM DB2</mat-option>
Expand Down Expand Up @@ -127,6 +128,20 @@ <h1 class="mat-h1 connectForm__fullLine">
(masterKeyChange)="handleMasterKeyChange($event)">
</app-dynamodb-credentials-form>

<app-cassandra-credentials-form *ngIf="db.type === 'cassandra' && db.connectionType === 'direct'"
[ngClass]="{
'credentials-fieldset': !db.isTestConnection,
'credentials-fieldset-no-warning': db.isTestConnection || !isSaas
}"
[connection]="db"
[submitting]="submitting"
[accessLevel]="accessLevel"
[masterKey]="masterKey"
[readonly]="(accessLevel === 'readonly' || db.isTestConnection) && db.id"
(switchToAgent)="switchToAgent"
(masterKeyChange)="handleMasterKeyChange($event)">
</app-cassandra-credentials-form>

<app-oracledb-credentials-form *ngIf="db.type === 'oracledb' && db.connectionType === 'direct'"
[ngClass]="{
'credentials-fieldset': !db.isTestConnection,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { DbConnectionConfirmDialogComponent } from './db-connection-confirm-dial
import { DbConnectionDeleteDialogComponent } from './db-connection-delete-dialog/db-connection-delete-dialog.component';
import { DbConnectionIpAccessDialogComponent } from './db-connection-ip-access-dialog/db-connection-ip-access-dialog.component';
import { DynamodbCredentialsFormComponent } from './db-credentials-forms/dynamodb-credentials-form/dynamodb-credentials-form.component';
import { CassandraCredentialsFormComponent } from './db-credentials-forms/cassandra-credentials-form/cassandra-credentials-form.component';
import { FormsModule } from '@angular/forms';
import { IpAddressButtonComponent } from '../ui-components/ip-address-button/ip-address-button.component';
import { MatButtonModule } from '@angular/material/button';
Expand Down Expand Up @@ -64,6 +65,7 @@ import isIP from 'validator/lib/isIP';
MatSlideToggleModule,
Db2CredentialsFormComponent,
DynamodbCredentialsFormComponent,
CassandraCredentialsFormComponent,
MongodbCredentialsFormComponent,
MssqlCredentialsFormComponent,
MysqlCredentialsFormComponent,
Expand Down Expand Up @@ -95,6 +97,7 @@ export class ConnectDBComponent implements OnInit {
[DBtype.MSSQL]: '1433',
[DBtype.Mongo]: '27017',
[DBtype.Dynamo]: '',
[DBtype.Cassandra]: '9142',
[DBtype.DB2]: '50000'
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
.connectForm__hostname,
.connectForm__port {
padding-bottom: 20px;
}

@media (width <= 600px) {
.connectForm__hostname {
padding-bottom: 44px;
}

.connectForm__port {
padding-bottom: 0;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
<mat-form-field appearance="outline" class="connectForm__hostname credentials-fieldset__1-3-columns">
<mat-label>Hostname</mat-label>
<input matInput name="hostname" #hostname="ngModel"
data-testid="connection-hostname-input"
angulartics2On="change"
angularticsAction="Connection creds {{ connection.id ? 'edit' : 'add' }}: hostname is edited"
required hostnameValidator="mongodb"
[readonly]="(accessLevel === 'readonly' || connection.isTestConnection) && connection.id"
[disabled]="submitting"
[(ngModel)]="connection.host">
<mat-hint>
<span>
E.g. <strong><code>mongodb+srv://my-test-db.8a8grvb.mongoconnection.net</code></strong>.
Connections from internal IPs (e.g. localhost) are not supported.
</span>
</mat-hint>

<mat-error *ngIf="hostname.errors?.isLocalhost && hostname.invalid">
To connect a database on internal IP use <strong>Pinggy</strong>
(<a [href]="tunnelingServiceLink" target="_blank" class="credentials-fieldset__hint-link">how-to</a>)
or <button type="button" (click)="switchToAgent.emit()" class="credentials-fieldset__hint-button">click here</button> for agent connection.
</mat-error>
<mat-error *ngIf="hostname.errors?.isInvalidHostname && hostname.invalid">Hostname is invalid.</mat-error>
</mat-form-field>

<mat-form-field appearance="outline" class="connectForm__port">
<mat-label>Port</mat-label>
<input matInput type="number" name="port" #port="ngModel"
data-testid="connection-port-input"
angulartics2On="change"
angularticsAction="Connection creds {{ connection.id ? 'edit' : 'add' }}: port is edited"
required
[readonly]="(accessLevel === 'readonly' || connection.isTestConnection) && connection.id"
[disabled]="submitting"
[(ngModel)]="connection.port">
<mat-error *ngIf="port.errors?.required && (port.invalid && port.touched)">Port should not be empty.</mat-error>
</mat-form-field>

<mat-form-field appearance="outline" class="credentials-fieldset__1-2-columns">
<mat-label>Username</mat-label>
<input matInput name="username" #username="ngModel"
data-testid="connection-username-input"
angulartics2On="change"
angularticsAction="Connection creds {{ connection.id ? 'edit' : 'add' }}: username is edited"
required
[readonly]="(accessLevel === 'readonly' || connection.isTestConnection) && connection.id"
[disabled]="submitting"
[(ngModel)]="connection.username">
<mat-error *ngIf="username.errors?.required && (username.invalid && username.touched)">Username should not be empty.</mat-error>
</mat-form-field>

<mat-form-field appearance="outline" class="credentials-fieldset__3-4-columns">
<mat-label>Password</mat-label>
<input type="password" matInput name="password" #password="ngModel"
data-testid="connection-password-input"
angulartics2On="change"
angularticsAction="Connection creds {{ connection.id ? 'edit' : 'add' }}: password is edited"
[required]="!connection.id || hostname.touched || port.touched"
[readonly]="(accessLevel === 'readonly' || connection.isTestConnection) && connection.id"
[disabled]="submitting"
[(ngModel)]="connection.password">
<mat-hint *ngIf="connection.id && (hostname.pristine && port.pristine)">To keep password the same keep this field blank.</mat-hint>
<mat-hint *ngIf="connection.id && (hostname.dirty || port.dirty)">Password needed due to hostname/port change.</mat-hint>
</mat-form-field>

<mat-form-field appearance="outline" class="credentials-fieldset__1-2-columns">
<mat-label>Database name</mat-label>
<input matInput name="database" #database="ngModel"
data-testid="connection-database-input"
angulartics2On="change"
angularticsAction="Connection creds {{ connection.id ? 'edit' : 'add' }}: database name is edited"
required
[readonly]="(accessLevel === 'readonly' || connection.isTestConnection) && connection.id"
[disabled]="submitting"
[(ngModel)]="connection.database">
<mat-error *ngIf="database.errors?.required && (database.invalid && database.touched)">Name should not be empty.</mat-error>
</mat-form-field>

<mat-form-field appearance="outline" class="credentials-fieldset__3-4-columns">
<mat-label>Data center</mat-label>
<input matInput name="authsource" #authsource="ngModel"
data-testid="connection-database-input"
angulartics2On="change"
angularticsAction="Connection creds {{ connection.id ? 'edit' : 'add' }}: database dataCenter is edited"
[readonly]="(accessLevel === 'readonly' || connection.isTestConnection) && connection.id"
[disabled]="submitting"
[(ngModel)]="connection.dataCenter">
<!--<mat-error *ngIf="database.errors?.required && (database.invalid && database.touched)">Name should not be empty.</mat-error>-->
</mat-form-field>

<mat-expansion-panel class="credentials-fieldset__1-4-columns">
<mat-expansion-panel-header data-testid="connection-advanced-settings-expansion-panel-header">
<mat-panel-title>
Advanced settings
</mat-panel-title>
</mat-expansion-panel-header>

<div class="advanced-settings">
<app-master-encryption-password class="advanced-settings__fullLine"
[masterKey]="masterKey"
[disabled]="accessLevel === 'readonly' || submitting || connection.isTestConnection"
(onMasterKeyChange)="handleMasterKeyChange($event)">
</app-master-encryption-password>

<mat-checkbox class="checkbox-line advanced-settings__fullLine" name="ssh" #ssh="ngModel"
data-testid="connection-ssh-checkbox"
labelPosition="after"
angulartics2On="click"
angularticsAction="Connection creds {{ connection.id ? 'edit' : 'add' }}: SSH is switched"
[angularticsProperties]="{'enable': connection.ssh}"
[disabled]="submitting || connection.isTestConnection"
[(ngModel)]="connection.ssh">
Use SSH tunnel
</mat-checkbox>

<mat-form-field *ngIf="connection.ssh" appearance="outline" floatLabel="always" class="advanced-settings__fullLine">
<mat-label>Private SSH key</mat-label>
<textarea matInput resizeToFitContent rows="8" name="privateSSHKey" #privateSSHKey="ngModel"
placeholder="Sensitive — write only field"
data-testid="connection-ssh-key-textarea"
angulartics2On="change"
angularticsAction="Connection creds {{ connection.id ? 'edit' : 'add' }}: SSH key is edited"
[required]="connection.ssh && !connection.id" [readonly]="accessLevel === 'readonly' && connection.id"
[disabled]="submitting"
[(ngModel)]="connection.privateSSHKey"
></textarea>
<mat-error *ngIf="privateSSHKey.errors?.required && (privateSSHKey.invalid && privateSSHKey.touched)">Private SSH key should not be empty.</mat-error>
</mat-form-field>

<mat-form-field *ngIf="connection.ssh" appearance="outline">
<mat-label>SSH host</mat-label>
<input matInput name="sshHost" #sshHost="ngModel"
data-testid="connection-ssh-host-input"
angulartics2On="change"
angularticsAction="Connection creds {{ connection.id ? 'edit' : 'add' }}: SSH host is edited"
[required]="connection.ssh" [readonly]="accessLevel === 'readonly' && connection.id"
[disabled]="submitting"
[(ngModel)]="connection.sshHost">
<mat-error *ngIf="sshHost.errors?.required && (sshHost.invalid && sshHost.touched)">SSH host should not be empty.</mat-error>
</mat-form-field>

<mat-form-field *ngIf="connection.ssh" appearance="outline">
<mat-label>SSH port</mat-label>
<input matInput type="number" name="sshPort" #sshPort="ngModel"
data-testid="connection-ssh-port-input"
angulartics2On="change"
angularticsAction="Connection creds {{ connection.id ? 'edit' : 'add' }}: SSH port is edited"
[required]="connection.ssh" [readonly]="accessLevel === 'readonly' && connection.id"
[disabled]="submitting"
[(ngModel)]="connection.sshPort">
<mat-error *ngIf="sshPort.errors?.required && (sshPort.invalid && sshPort.touched)">SSH port should not be empty.</mat-error>
</mat-form-field>

<mat-form-field *ngIf="connection.ssh" appearance="outline" floatLabel="always">
<mat-label>SSH username</mat-label>
<input matInput name="sshUsername" #sshUsername="ngModel"
placeholder="Sensitive — write only field"
data-testid="connection-ssh-username-input"
angulartics2On="change"
angularticsAction="Connection creds {{ connection.id ? 'edit' : 'add' }}: SSH username is edited"
[required]="connection.ssh" [readonly]="accessLevel === 'readonly' && connection.id"
[disabled]="submitting"
[(ngModel)]="connection.sshUsername">
<mat-error *ngIf="sshUsername.errors?.required && (sshUsername.invalid && sshUsername.touched)">SSH username should not be empty.</mat-error>
</mat-form-field>

<mat-checkbox class="checkbox-line advanced-settings__fullLine" name="ssl" #ssh="ngModel"
labelPosition="after"
data-testid="connection-ssl-checkbox"
angulartics2On="click"
angularticsAction="Connection creds {{ connection.id ? 'edit' : 'add' }}: SSL is switched"
[angularticsProperties]="{'enable': connection.ssl}"
[disabled]="submitting || connection.isTestConnection"
[(ngModel)]="connection.ssl">
Check SSL certificate
</mat-checkbox>

<mat-form-field *ngIf="connection.ssl" appearance="outline" class="advanced-settings__fullLine">
<mat-label>SSL certificate</mat-label>
<textarea matInput resizeToFitContent rows="8" name="sslCert" #sslCert="ngModel"
data-testid="connection-ssl-certificate-textarea"
angulartics2On="change"
angularticsAction="Connection creds {{ connection.id ? 'edit' : 'add' }}: SSL certificate is edited"
[required]="connection.ssl" [readonly]="accessLevel === 'readonly' && connection.id"
[disabled]="submitting"
[(ngModel)]="connection.cert"
></textarea>
<mat-error *ngIf="sslCert.errors?.required && (sslCert.invalid && sslCert.touched)">SSL certificate should not be empty.</mat-error>
</mat-form-field>
</div>
</mat-expansion-panel>
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';

import { Angulartics2Module } from 'angulartics2';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { CassandraCredentialsFormComponent } from './cassandra-credentials-form.component';
import { provideHttpClient } from '@angular/common/http';

describe('CassandraCredentialsFormComponent', () => {
let component: CassandraCredentialsFormComponent;
let fixture: ComponentFixture<CassandraCredentialsFormComponent>;

beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [
BrowserAnimationsModule,
Angulartics2Module.forRoot(),
CassandraCredentialsFormComponent
],
providers: [provideHttpClient()]
})
.compileComponents();

fixture = TestBed.createComponent(CassandraCredentialsFormComponent);
component = fixture.componentInstance;

component.connection = {
id: "12345678"
} as any;

fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { Component } from '@angular/core';
import { Angulartics2Module } from 'angulartics2';
import { BaseCredentialsFormComponent } from '../base-credentials-form/base-credentials-form.component';
import { FormsModule } from '@angular/forms';
import { HostnameValidationDirective } from 'src/app/directives/hostnameValidator.directive';
import { MasterEncryptionPasswordComponent } from '../../master-encryption-password/master-encryption-password.component';
import { MatExpansionModule } from '@angular/material/expansion';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { NgIf } from '@angular/common';
import { MatCheckboxModule } from '@angular/material/checkbox';

@Component({
selector: 'app-cassandra-credentials-form',
templateUrl: './cassandra-credentials-form.component.html',
styleUrls: ['../base-credentials-form/base-credentials-form.component.css', './cassandra-credentials-form.component.css'],
imports: [
NgIf,
FormsModule,
MatFormFieldModule,
MatInputModule,
MatCheckboxModule,
MatExpansionModule,
HostnameValidationDirective,
MasterEncryptionPasswordComponent,
Angulartics2Module
]
})

export class CassandraCredentialsFormComponent extends BaseCredentialsFormComponent {

}
Original file line number Diff line number Diff line change
Expand Up @@ -585,7 +585,7 @@ export class DbTableRowEditComponent implements OnInit {

//parse json fields
const jsonFields = Object.entries(this.tableTypes)
.filter(([key, value]) => value === 'json' || value === 'jsonb' || value === 'array' || value === 'ARRAY' || value === 'object')
.filter(([key, value]) => value === 'json' || value === 'jsonb' || value === 'array' || value === 'ARRAY' || value === 'object' || value === 'set' || value === 'list' || value === 'map')
.map(jsonField => jsonField[0]);
if (jsonFields.length) {
for (const jsonField of jsonFields) {
Expand Down
26 changes: 26 additions & 0 deletions frontend/src/app/consts/record-edit-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -274,5 +274,31 @@ export const recordEditTypes = {
array: JsonEditorEditComponent,
json: JsonEditorEditComponent,
binary: FileEditComponent,
},
cassandra: {
int: NumberEditComponent,
bigint: NumberEditComponent,
varint: NumberEditComponent,
decimal: NumberEditComponent,
float: NumberEditComponent,
double: NumberEditComponent,

boolean: BooleanEditComponent,

timeuuid: IdEditComponent,

timestamp: DateTimeEditComponent,
date: DateEditComponent,
time: TimeEditComponent,

uuid: TextEditComponent,
varchar: TextEditComponent,
inet: TextEditComponent,
ascii: TextEditComponent,
text: LongTextEditComponent,

list: JsonEditorEditComponent,
map: JsonEditorEditComponent,
set: JsonEditorEditComponent,
}
}
Loading