Skip to content

Commit b4ef990

Browse files
Merge pull request #1674 from rocket-admin/feature/hosted-db-frontend
Add hosted database creation frontend
2 parents da0b62c + 923e193 commit b4ef990

15 files changed

Lines changed: 757 additions & 10 deletions
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
.plan-dialog__content {
2+
display: flex;
3+
flex-direction: column;
4+
gap: 12px;
5+
min-width: min(100%, 30rem);
6+
margin-top: 16px;
7+
}
8+
9+
.plan-dialog__option {
10+
display: flex;
11+
flex-direction: column;
12+
gap: 8px;
13+
padding: 16px;
14+
border: 1px solid rgba(0, 0, 0, 0.12);
15+
border-radius: 12px;
16+
background: transparent;
17+
cursor: pointer;
18+
text-align: left;
19+
transition:
20+
border-color 0.15s,
21+
background 0.15s;
22+
}
23+
24+
.plan-dialog__option:hover {
25+
border-color: var(--color-accentedPalette-200);
26+
background: var(--color-accentedPalette-50);
27+
}
28+
29+
.plan-dialog__option-header {
30+
display: flex;
31+
align-items: center;
32+
gap: 8px;
33+
color: var(--mat-sidenav-content-text-color);
34+
}
35+
36+
.plan-dialog__option-title {
37+
font-size: 16px;
38+
font-weight: 600;
39+
}
40+
41+
.plan-dialog__option-description {
42+
margin: 0;
43+
color: rgba(0, 0, 0, 0.64);
44+
font-size: 14px;
45+
}
46+
47+
.plan-dialog__option-price {
48+
font-size: 13px;
49+
font-weight: 700;
50+
letter-spacing: 0.04em;
51+
text-transform: uppercase;
52+
color: rgba(0, 0, 0, 0.48);
53+
}
54+
55+
@media (prefers-color-scheme: dark) {
56+
.plan-dialog__option {
57+
border-color: rgba(255, 255, 255, 0.08);
58+
}
59+
60+
.plan-dialog__option:hover {
61+
border-color: var(--color-accentedPalette-500);
62+
background: var(--color-accentedPalette-700);
63+
}
64+
65+
.plan-dialog__option-description {
66+
color: rgba(255, 255, 255, 0.64);
67+
}
68+
69+
.plan-dialog__option-price {
70+
color: rgba(255, 255, 255, 0.48);
71+
}
72+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<h1 mat-dialog-title>Choose your hosted database plan</h1>
2+
3+
<mat-dialog-content class="plan-dialog__content">
4+
<button type="button" class="plan-dialog__option" (click)="chooseFree()">
5+
<div class="plan-dialog__option-header">
6+
<mat-icon>dns</mat-icon>
7+
<span class="plan-dialog__option-title">Tiny node</span>
8+
</div>
9+
<p class="plan-dialog__option-description">
10+
Up to 0.1 CPU and 100 MB storage
11+
</p>
12+
<span class="plan-dialog__option-price">Free</span>
13+
</button>
14+
15+
<button type="button" class="plan-dialog__option" (click)="chooseUpgrade()">
16+
<div class="plan-dialog__option-header">
17+
<mat-icon>rocket_launch</mat-icon>
18+
<span class="plan-dialog__option-title">Scalable node</span>
19+
</div>
20+
<p class="plan-dialog__option-description">
21+
$0.04/CPU/minute, $0.2/GB/month and $0.04/million IOPS
22+
</p>
23+
<span class="plan-dialog__option-price">Pay as you go</span>
24+
</button>
25+
</mat-dialog-content>
26+
27+
<mat-dialog-actions align="end">
28+
<button type="button" mat-button mat-dialog-close>Cancel</button>
29+
</mat-dialog-actions>
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { Component } from '@angular/core';
2+
import { MatButtonModule } from '@angular/material/button';
3+
import { MatDialogModule, MatDialogRef } from '@angular/material/dialog';
4+
import { MatIconModule } from '@angular/material/icon';
5+
6+
export type HostedDatabasePlanChoice = 'free' | 'upgrade';
7+
8+
@Component({
9+
selector: 'app-hosted-database-plan-dialog',
10+
templateUrl: './hosted-database-plan-dialog.component.html',
11+
styleUrl: './hosted-database-plan-dialog.component.css',
12+
imports: [MatDialogModule, MatButtonModule, MatIconModule],
13+
})
14+
export class HostedDatabasePlanDialogComponent {
15+
constructor(private _dialogRef: MatDialogRef<HostedDatabasePlanDialogComponent, HostedDatabasePlanChoice>) {}
16+
17+
chooseFree(): void {
18+
this._dialogRef.close('free');
19+
}
20+
21+
chooseUpgrade(): void {
22+
this._dialogRef.close('upgrade');
23+
}
24+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
.hosted-dialog__content {
2+
display: flex;
3+
flex-direction: column;
4+
gap: 16px;
5+
min-width: min(100%, 34rem);
6+
}
7+
8+
.hosted-dialog__description,
9+
.hosted-dialog__hint,
10+
.hosted-dialog__error {
11+
margin: 0;
12+
}
13+
14+
.hosted-dialog__error {
15+
background: var(--color-error-background);
16+
border: 1px solid var(--color-error);
17+
border-radius: 8px;
18+
color: var(--color-error);
19+
padding: 12px 14px;
20+
}
21+
22+
.hosted-dialog__credentials {
23+
display: grid;
24+
gap: 10px;
25+
border: 1px solid var(--color-accentedPalette-100);
26+
border-radius: 12px;
27+
padding: 14px;
28+
}
29+
30+
.hosted-dialog__row {
31+
display: grid;
32+
grid-template-columns: minmax(96px, 120px) minmax(0, 1fr);
33+
align-items: start;
34+
gap: 12px;
35+
}
36+
37+
.hosted-dialog__label {
38+
font-size: 12px;
39+
font-weight: 700;
40+
letter-spacing: 0.04em;
41+
text-transform: uppercase;
42+
}
43+
44+
.hosted-dialog__credentials code {
45+
background: var(--color-accentedPalette-50);
46+
border-radius: 8px;
47+
font-family: "IBM Plex Mono", monospace;
48+
padding: 8px 10px;
49+
overflow-wrap: anywhere;
50+
}
51+
52+
.hosted-dialog__hint {
53+
color: rgba(0, 0, 0, 0.64);
54+
font-size: 13px;
55+
}
56+
57+
.hosted-dialog__actions {
58+
gap: 8px;
59+
padding-top: 8px;
60+
}
61+
62+
@media (prefers-color-scheme: dark) {
63+
.hosted-dialog__credentials {
64+
background: transparent;
65+
border-color: var(--color-accentedPalette-400);
66+
}
67+
68+
.hosted-dialog__credentials code {
69+
background: var(--color-accentedPalette-600);
70+
}
71+
72+
.hosted-dialog__hint {
73+
color: rgba(255, 255, 255, 0.64);
74+
}
75+
}
76+
77+
@media (width <= 600px) {
78+
.hosted-dialog__content {
79+
min-width: auto;
80+
}
81+
82+
.hosted-dialog__row {
83+
grid-template-columns: 1fr;
84+
gap: 6px;
85+
}
86+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
<h1 mat-dialog-title>
2+
{{ data.connectionId ? 'Hosted PostgreSQL is ready' : 'Hosted PostgreSQL created' }}
3+
</h1>
4+
5+
<mat-dialog-content class="hosted-dialog__content">
6+
@if (data.connectionId) {
7+
<p class="hosted-dialog__description">
8+
Your hosted PostgreSQL database is provisioned and already connected to RocketAdmin.
9+
Save these credentials now. The password is shown only once.
10+
</p>
11+
} @else {
12+
<p class="hosted-dialog__description">
13+
Your hosted PostgreSQL database is provisioned, but RocketAdmin could not finish the automatic connection setup.
14+
Save these credentials now and use them for a manual PostgreSQL connection or support follow-up.
15+
</p>
16+
}
17+
18+
@if (data.errorMessage) {
19+
<p class="hosted-dialog__error">
20+
Automatic connection setup failed: {{ data.errorMessage }}
21+
</p>
22+
}
23+
24+
<div class="hosted-dialog__credentials">
25+
<div class="hosted-dialog__row">
26+
<span class="hosted-dialog__label">Database</span>
27+
<code>{{ data.hostedDatabase.databaseName }}</code>
28+
</div>
29+
<div class="hosted-dialog__row">
30+
<span class="hosted-dialog__label">Host</span>
31+
<code>{{ data.hostedDatabase.hostname }}</code>
32+
</div>
33+
<div class="hosted-dialog__row">
34+
<span class="hosted-dialog__label">Port</span>
35+
<code>{{ data.hostedDatabase.port }}</code>
36+
</div>
37+
<div class="hosted-dialog__row">
38+
<span class="hosted-dialog__label">Username</span>
39+
<code>{{ data.hostedDatabase.username }}</code>
40+
</div>
41+
<div class="hosted-dialog__row">
42+
<span class="hosted-dialog__label">Password</span>
43+
<code>{{ data.hostedDatabase.password }}</code>
44+
</div>
45+
</div>
46+
47+
<p class="hosted-dialog__hint">
48+
The generated password cannot be recovered from this screen later.
49+
</p>
50+
</mat-dialog-content>
51+
52+
<mat-dialog-actions align="end" class="hosted-dialog__actions">
53+
<button type="button" mat-stroked-button mat-dialog-close>
54+
Close
55+
</button>
56+
<button
57+
type="button"
58+
mat-flat-button
59+
color="accent"
60+
[cdkCopyToClipboard]="credentialsText"
61+
(cdkCopyToClipboardCopied)="handleCredentialsCopied()">
62+
Copy credentials
63+
</button>
64+
@if (data.connectionId) {
65+
<a
66+
mat-button
67+
[routerLink]="['/dashboard', data.connectionId]"
68+
mat-dialog-close
69+
(click)="handleSecondaryActionClick()">
70+
Open tables
71+
</a>
72+
<a
73+
mat-flat-button
74+
color="primary"
75+
[routerLink]="['/auto-configure', data.connectionId]"
76+
mat-dialog-close
77+
cdkFocusInitial
78+
(click)="handlePrimaryActionClick()">
79+
Set up dashboard
80+
</a>
81+
}
82+
</mat-dialog-actions>
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { CdkCopyToClipboard } from '@angular/cdk/clipboard';
2+
import { Component, Inject } from '@angular/core';
3+
import { MatButtonModule } from '@angular/material/button';
4+
import { MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
5+
import { RouterModule } from '@angular/router';
6+
import posthog from 'posthog-js';
7+
import { CreatedHostedDatabase } from 'src/app/models/hosted-database';
8+
import { NotificationsService } from 'src/app/services/notifications.service';
9+
10+
export interface HostedDatabaseSuccessDialogData {
11+
hostedDatabase: CreatedHostedDatabase;
12+
connectionId: string | null;
13+
errorMessage?: string;
14+
}
15+
16+
@Component({
17+
selector: 'app-hosted-database-success-dialog',
18+
templateUrl: './hosted-database-success-dialog.component.html',
19+
styleUrl: './hosted-database-success-dialog.component.css',
20+
imports: [MatDialogModule, MatButtonModule, RouterModule, CdkCopyToClipboard],
21+
})
22+
export class HostedDatabaseSuccessDialogComponent {
23+
constructor(
24+
@Inject(MAT_DIALOG_DATA) public data: HostedDatabaseSuccessDialogData,
25+
private _notifications: NotificationsService,
26+
) {}
27+
28+
get credentialsText(): string {
29+
const { username, password, hostname, port, databaseName } = this.data.hostedDatabase;
30+
return `postgres://${username}:${password}@${hostname}:${port}/${databaseName}`;
31+
}
32+
33+
handleCredentialsCopied(): void {
34+
posthog.capture('Connections: hosted PostgreSQL credentials copied');
35+
this._notifications.showSuccessSnackbar('Hosted database credentials were copied to clipboard.');
36+
}
37+
38+
handlePrimaryActionClick(): void {
39+
posthog.capture('Connections: hosted PostgreSQL setup dashboard opened');
40+
}
41+
42+
handleSecondaryActionClick(): void {
43+
posthog.capture('Connections: hosted PostgreSQL tables opened');
44+
}
45+
}

0 commit comments

Comments
 (0)