-
-
Notifications
You must be signed in to change notification settings - Fork 18
Add configuring status banner and auto-configure redesign #1689
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
lyubov-voloshko
merged 5 commits into
rocket-admin:main
from
karinakharchenko:dashboard-configuring-banner
Mar 26, 2026
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
2022059
feat: add configuring status banner, auto-configure card redesign, an…
karinakharchenko 1001747
autoconfigure: redirect a user to tables list after configuration is …
lyubov-voloshko 7127e06
fix status config banner colors
lyubov-voloshko 4b7cf8c
ai config page: update colors
lyubov-voloshko f110a32
Merge branch 'main' into dashboard-configuring-banner
lyubov-voloshko File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
226 changes: 203 additions & 23 deletions
226
frontend/src/app/components/auto-configure/auto-configure.component.css
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,43 +1,223 @@ | ||
| .auto-configure { | ||
| /* ── Page ── */ | ||
|
|
||
| .page { | ||
| display: flex; | ||
| flex-direction: column; | ||
| justify-content: center; | ||
| align-items: center; | ||
| gap: 24px; | ||
| height: 100vh; | ||
| background: var(--color-primaryPalette-50); | ||
| } | ||
|
|
||
| .auto-configure__title { | ||
| color: var(--mat-sidenav-content-text-color); | ||
| margin: 0; | ||
| font-weight: 500; | ||
| @media (prefers-color-scheme: dark) { | ||
| .page { | ||
| background: var(--surface-dark-color); | ||
| } | ||
| } | ||
|
|
||
| .auto-configure__text { | ||
| color: var(--mat-sidenav-content-text-color); | ||
| opacity: 0.7; | ||
| /* ── Card ── */ | ||
|
|
||
| .card { | ||
| width: 100%; | ||
| max-width: 480px; | ||
| border-radius: 16px; | ||
| background: var(--color-whitePalette-500); | ||
| box-shadow: | ||
| 0 1px 3px rgba(0, 0, 0, 0.08), | ||
| 0 4px 24px rgba(0, 0, 0, 0.06); | ||
| overflow: hidden; | ||
| } | ||
|
|
||
| @media (prefers-color-scheme: dark) { | ||
| .card { | ||
| background: var(--color-primaryPalette-900); | ||
| box-shadow: | ||
| 0 1px 3px rgba(0, 0, 0, 0.3), | ||
| 0 4px 24px rgba(0, 0, 0, 0.2); | ||
| } | ||
| } | ||
|
|
||
| @media (width <= 600px) { | ||
| .card { | ||
| margin: 0 16px; | ||
| } | ||
| } | ||
|
|
||
| .card__body { | ||
| display: flex; | ||
| flex-direction: column; | ||
| align-items: center; | ||
| padding: 48px 32px 36px; | ||
| gap: 16px; | ||
| } | ||
|
|
||
| .card__title { | ||
| margin: 0; | ||
| max-width: 400px; | ||
| font-size: 20px; | ||
| font-weight: 700; | ||
| color: var(--color-primaryPalette-900); | ||
| text-align: center; | ||
| } | ||
|
|
||
| .auto-configure__open-btn { | ||
| margin-top: 8px; | ||
| font-size: 16px; | ||
| padding: 0 32px; | ||
| @media (prefers-color-scheme: dark) { | ||
| .card__title { | ||
| color: var(--color-primaryPalette-50); | ||
| } | ||
| } | ||
|
|
||
| .auto-configure__hint { | ||
| color: var(--mat-sidenav-content-text-color); | ||
| opacity: 0.5; | ||
| .card__subtitle { | ||
| margin: 0; | ||
| font-size: 13px; | ||
| font-size: 14px; | ||
| color: var(--color-primaryPalette-400); | ||
| text-align: center; | ||
| line-height: 1.5; | ||
| max-width: 360px; | ||
| } | ||
|
|
||
| .auto-configure__notice { | ||
| color: var(--mat-sidenav-content-text-color); | ||
| opacity: 0.7; | ||
| @media (prefers-color-scheme: dark) { | ||
| .card__subtitle { | ||
| color: var(--color-primaryPalette-300); | ||
| } | ||
| } | ||
|
|
||
| .card__notice { | ||
| margin: 0; | ||
| font-size: 14px; | ||
| color: var(--color-primaryPalette-400); | ||
| text-align: center; | ||
| max-width: 400px; | ||
| max-width: 360px; | ||
| line-height: 1.5; | ||
| } | ||
|
|
||
| @media (prefers-color-scheme: dark) { | ||
| .card__notice { | ||
| color: var(--color-primaryPalette-300); | ||
| } | ||
| } | ||
|
|
||
| /* ── Footer ── */ | ||
|
|
||
| .card__footer { | ||
| display: flex; | ||
| align-items: center; | ||
| justify-content: space-between; | ||
| gap: 16px; | ||
| padding: 16px 24px; | ||
| border-top: 1px solid var(--color-primaryPalette-200); | ||
| } | ||
|
|
||
| @media (prefers-color-scheme: dark) { | ||
| .card__footer { | ||
| border-top-color: var(--color-primaryPalette-800); | ||
| } | ||
| } | ||
|
|
||
| @media (width <= 600px) { | ||
| .card__footer { | ||
| flex-direction: column; | ||
| text-align: center; | ||
| } | ||
| } | ||
|
|
||
| .card__footer-text { | ||
| margin: 0; | ||
| font-size: 13px; | ||
| color: var(--color-primaryPalette-300); | ||
| line-height: 1.4; | ||
| flex: 1; | ||
| } | ||
|
|
||
| @media (prefers-color-scheme: dark) { | ||
| .card__footer-text { | ||
| color: var(--color-primaryPalette-400); | ||
| } | ||
| } | ||
|
|
||
| /* ── CTA button ── */ | ||
|
|
||
| .card__cta { | ||
| flex-shrink: 0; | ||
| background: var(--color-accentedPalette-500) !important; | ||
| color: var(--color-accentedPalette-500-contrast) !important; | ||
| border-radius: 8px; | ||
| font-weight: 600; | ||
| font-size: 14px; | ||
| padding: 0 20px; | ||
| height: 40px; | ||
| text-decoration: none; | ||
| } | ||
|
|
||
| .card__cta:hover { | ||
| background: var(--color-accentedPalette-700) !important; | ||
| } | ||
|
|
||
| .card__cta-arrow { | ||
| margin-left: 6px; | ||
| } | ||
|
|
||
| /* ── Concentric spinning rings ── */ | ||
|
|
||
| .spinner { | ||
| position: relative; | ||
| width: 72px; | ||
| height: 72px; | ||
| margin-bottom: 8px; | ||
| } | ||
|
|
||
| .spinner__ring { | ||
| position: absolute; | ||
| border-radius: 50%; | ||
| border: 3px solid transparent; | ||
| } | ||
|
|
||
| .spinner__ring--outer { | ||
| inset: 0; | ||
| border-top-color: var(--color-accentedPalette-500); | ||
| border-right-color: var(--color-accentedPalette-500); | ||
| animation: spin-outer 1.8s linear infinite; | ||
| } | ||
|
|
||
| .spinner__ring--inner { | ||
| inset: 10px; | ||
| border-bottom-color: var(--color-accentedPalette-500); | ||
| border-left-color: var(--color-accentedPalette-500); | ||
| animation: spin-inner 1.2s linear infinite; | ||
| } | ||
|
|
||
| @keyframes spin-outer { | ||
| to { transform: rotate(360deg); } | ||
| } | ||
|
|
||
| @keyframes spin-inner { | ||
| to { transform: rotate(-360deg); } | ||
| } | ||
|
|
||
| /* ── Indeterminate progress bar ── */ | ||
|
|
||
| .progress-bar { | ||
| width: 100%; | ||
| max-width: 320px; | ||
| height: 4px; | ||
| border-radius: 2px; | ||
| background: var(--color-primaryPalette-200); | ||
| overflow: hidden; | ||
| margin-top: 4px; | ||
| } | ||
|
|
||
| @media (prefers-color-scheme: dark) { | ||
| .progress-bar { | ||
| background: var(--color-primaryPalette-800); | ||
| } | ||
| } | ||
|
|
||
| .progress-bar__fill { | ||
| width: 40%; | ||
| height: 100%; | ||
| border-radius: 2px; | ||
| background: var(--color-accentedPalette-500); | ||
| animation: indeterminate 1.5s ease-in-out infinite; | ||
| } | ||
|
|
||
| @keyframes indeterminate { | ||
| 0% { transform: translateX(-100%); } | ||
| 100% { transform: translateX(350%); } | ||
| } |
34 changes: 21 additions & 13 deletions
34
frontend/src/app/components/auto-configure/auto-configure.component.html
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,14 +1,22 @@ | ||
| @if (loading()) { | ||
| <div class="auto-configure"> | ||
| <mat-spinner diameter="48"></mat-spinner> | ||
| <h2 class="auto-configure__title">Setting up your dashboard</h2> | ||
| <p class="auto-configure__text">We're analyzing your database structure and applying the best configuration. This may take a while.</p> | ||
| <a mat-flat-button class="auto-configure__open-btn" [routerLink]="'/dashboard/' + connectionId()">Open tables</a> | ||
| <p class="auto-configure__hint">Setup will continue in the background</p> | ||
| <div class="page"> | ||
| <div class="card"> | ||
| <div class="card__body"> | ||
| <div class="spinner"> | ||
| <div class="spinner__ring spinner__ring--outer"></div> | ||
| <div class="spinner__ring spinner__ring--inner"></div> | ||
| </div> | ||
| <h2 class="card__title">Configuring your database</h2> | ||
| <p class="card__subtitle">We're analyzing your structure and applying the best settings. This is running in the background.</p> | ||
| <div class="progress-bar"> | ||
| <div class="progress-bar__fill"></div> | ||
| </div> | ||
| </div> | ||
| <div class="card__footer"> | ||
| <p class="card__footer-text">You can start working — setup will continue in the background.</p> | ||
| <a mat-flat-button class="card__cta" [routerLink]="'/dashboard/' + connectionId()"> | ||
| Open tables | ||
| <span class="card__cta-arrow">→</span> | ||
| </a> | ||
| </div> | ||
| </div> | ||
| } @else if (errorMessage()) { | ||
| <div class="auto-configure"> | ||
| <p class="auto-configure__notice">{{ errorMessage() }}</p> | ||
| <a mat-flat-button [routerLink]="'/dashboard/' + connectionId()">Continue to dashboard</a> | ||
| </div> | ||
| } | ||
| </div> |
25 changes: 6 additions & 19 deletions
25
frontend/src/app/components/auto-configure/auto-configure.component.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,44 +1,31 @@ | ||
| import { Component, inject, OnInit, signal } from '@angular/core'; | ||
| import { MatButtonModule } from '@angular/material/button'; | ||
| import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; | ||
| import { ActivatedRoute, Router, RouterModule } from '@angular/router'; | ||
|
|
||
| import { ApiService } from '../../services/api.service'; | ||
| import { ConfigurationStateService } from '../../services/configuration-state.service'; | ||
|
|
||
| @Component({ | ||
| selector: 'app-auto-configure', | ||
| templateUrl: './auto-configure.component.html', | ||
| styleUrls: ['./auto-configure.component.css'], | ||
| imports: [MatProgressSpinnerModule, MatButtonModule, RouterModule], | ||
| imports: [MatButtonModule, RouterModule], | ||
| }) | ||
| export class AutoConfigureComponent implements OnInit { | ||
| private _route = inject(ActivatedRoute); | ||
| private _router = inject(Router); | ||
| private _api = inject(ApiService); | ||
| private _configState = inject(ConfigurationStateService); | ||
|
|
||
| protected connectionId = signal<string | null>(null); | ||
| protected loading = signal(true); | ||
| protected errorMessage = signal<string | null>(null); | ||
|
|
||
| ngOnInit(): void { | ||
| async ngOnInit(): Promise<void> { | ||
| const connectionId = this._route.snapshot.paramMap.get('connection-id'); | ||
| if (!connectionId) { | ||
| this._router.navigate(['/connections-list']); | ||
| return; | ||
| } | ||
|
|
||
| this.connectionId.set(connectionId); | ||
| this._configure(connectionId); | ||
| } | ||
|
|
||
| private async _configure(connectionId: string): Promise<void> { | ||
| const result = await this._api.get<string>(`/ai/v2/setup/${connectionId}`, { responseType: 'text' }); | ||
|
|
||
| if (result !== null) { | ||
| this._router.navigate([`/dashboard/${connectionId}`]); | ||
| } else { | ||
| this.loading.set(false); | ||
| this.errorMessage.set('Auto-configuration could not be completed. You can still configure your tables manually.'); | ||
| } | ||
| await this._configState.startConfiguring(connectionId); | ||
| this._router.navigate(['/dashboard', connectionId]); | ||
| } | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Guard against post-destruction navigation and consider checking the return value.
Two concerns with the current flow:
Post-destruction navigation: If the user clicks "Open tables" and navigates to the dashboard before
startConfiguringcompletes, this component is destroyed but theawaitcontinues. Line 29 will still execute after the promise resolves, potentially causing a redundant navigation or flicker.Ignored return value:
startConfiguringreturnsfalseon failure (perConfigurationStateService), but the component navigates unconditionally. If failed configurations should be handled differently (e.g., stay on page), this needs adjustment.🛡️ Proposed fix to prevent post-destruction navigation
export class AutoConfigureComponent implements OnInit { private _route = inject(ActivatedRoute); private _router = inject(Router); private _configState = inject(ConfigurationStateService); + private _destroyed = false; protected connectionId = signal<string | null>(null); async ngOnInit(): Promise<void> { const connectionId = this._route.snapshot.paramMap.get('connection-id'); if (!connectionId) { this._router.navigate(['/connections-list']); return; } this.connectionId.set(connectionId); await this._configState.startConfiguring(connectionId); - this._router.navigate(['/dashboard', connectionId]); + if (!this._destroyed) { + this._router.navigate(['/dashboard', connectionId]); + } } + + ngOnDestroy(): void { + this._destroyed = true; + } }Note: You'll need to add
OnDestroyto the imports and implements clause.🤖 Prompt for AI Agents