diff --git a/backend/src/entities/visualizations/dashboard/repository/dashboard-custom-repository-extension.ts b/backend/src/entities/visualizations/dashboard/repository/dashboard-custom-repository-extension.ts index 895e4dcee..8c120867f 100644 --- a/backend/src/entities/visualizations/dashboard/repository/dashboard-custom-repository-extension.ts +++ b/backend/src/entities/visualizations/dashboard/repository/dashboard-custom-repository-extension.ts @@ -20,10 +20,11 @@ export const dashboardCustomRepositoryExtension: IDashboardRepository = { }, async findAllDashboardsByConnectionId(connectionId: string): Promise { - return await this.find({ - where: { connection_id: connectionId }, - order: { created_at: 'DESC' }, - }); + const qb = this.createQueryBuilder('dashboard') + .leftJoinAndSelect('dashboard.widgets', 'widgets') + .where('dashboard.connection_id = :connectionId', { connectionId }) + .orderBy('dashboard.created_at', 'DESC'); + return await qb.getMany(); }, async findAllDashboardsWithWidgetsByConnectionId(connectionId: string): Promise { diff --git a/frontend/src/app/components/charts/chart-edit/chart-edit.component.css b/frontend/src/app/components/charts/chart-edit/chart-edit.component.css index d776500ba..660f2cef7 100644 --- a/frontend/src/app/components/charts/chart-edit/chart-edit.component.css +++ b/frontend/src/app/components/charts/chart-edit/chart-edit.component.css @@ -1,12 +1,26 @@ +.chart-edit-layout { + display: flex; + height: calc(100vh - 64px); +} + +.chart-edit-main { + flex: 1; + overflow-y: auto; +} + .chart-edit-page { - margin: 2em auto; - padding: 0 clamp(100px, 10vw, 200px); + display: flex; + flex-direction: column; + min-height: 100%; + padding: 0 clamp(40px, 5vw, 100px); max-width: 1400px; + margin: 3em auto; } -@media (width <= 900px) { +@media (width <= 600px) { .chart-edit-page { padding: 0 16px; + margin: 1.5em auto; } } @@ -14,7 +28,8 @@ display: flex; align-items: center; gap: 16px; - margin-bottom: 24px; + margin-bottom: 32px; + flex-shrink: 0; } .chart-edit-header h1 { @@ -31,11 +46,15 @@ display: flex; flex-direction: column; gap: 24px; + flex: 1; + overflow-y: auto; + padding-bottom: 24px; } .query-details { display: flex; gap: 16px; + margin-top: 8px; } @media (width <= 600px) { @@ -210,13 +229,19 @@ display: flex; justify-content: flex-end; gap: 16px; - padding-top: 16px; + padding: 16px 0; border-top: 1px solid rgba(0, 0, 0, 0.12); + background-color: #fff; + flex-shrink: 0; + position: sticky; + bottom: 0; + margin-top: auto; } @media (prefers-color-scheme: dark) { .actions { border-top-color: rgba(255, 255, 255, 0.12); + background-color: #1e1e1e; } } @@ -224,3 +249,9 @@ display: inline-block; margin-right: 8px; } + +.test-query-button .button-spinner { + display: inline-block; + vertical-align: middle; + margin-right: 4px; +} diff --git a/frontend/src/app/components/charts/chart-edit/chart-edit.component.html b/frontend/src/app/components/charts/chart-edit/chart-edit.component.html index b5b28b085..d6f6eb859 100644 --- a/frontend/src/app/components/charts/chart-edit/chart-edit.component.html +++ b/frontend/src/app/components/charts/chart-edit/chart-edit.component.html @@ -1,11 +1,18 @@ - +
+ + -
+
+ + +
-

{{ isEditMode() ? 'Edit Saved Query' : 'Create Saved Query' }}

+

{{ isEditMode() ? 'Edit Widget' : 'Create Widget' }}

@@ -41,9 +48,10 @@

SQL Query

@@ -148,18 +156,20 @@

Query Results ({{ testResults().length }} rows)

info

Query executed successfully but returned no results.

+
-
- - +
+ + +
diff --git a/frontend/src/app/components/charts/chart-edit/chart-edit.component.spec.ts b/frontend/src/app/components/charts/chart-edit/chart-edit.component.spec.ts index 385b1cc6c..12449a77e 100644 --- a/frontend/src/app/components/charts/chart-edit/chart-edit.component.spec.ts +++ b/frontend/src/app/components/charts/chart-edit/chart-edit.component.spec.ts @@ -74,6 +74,7 @@ describe('ChartEditComponent', () => { mockUiSettingsService = { editorTheme: 'vs-dark', + getUiSettings: vi.fn().mockReturnValue(of({})), }; await TestBed.configureTestingModule({ diff --git a/frontend/src/app/components/charts/chart-edit/chart-edit.component.ts b/frontend/src/app/components/charts/chart-edit/chart-edit.component.ts index 03329e70d..0b01a4d7d 100644 --- a/frontend/src/app/components/charts/chart-edit/chart-edit.component.ts +++ b/frontend/src/app/components/charts/chart-edit/chart-edit.component.ts @@ -19,6 +19,7 @@ import { ChartType, TestQueryResult } from 'src/app/models/saved-query'; import { ConnectionsService } from 'src/app/services/connections.service'; import { SavedQueriesService } from 'src/app/services/saved-queries.service'; import { UiSettingsService } from 'src/app/services/ui-settings.service'; +import { DashboardsSidebarComponent } from '../../dashboards/dashboards-sidebar/dashboards-sidebar.component'; import { AlertComponent } from '../../ui-components/alert/alert.component'; import { ChartPreviewComponent } from '../chart-preview/chart-preview.component'; @@ -41,6 +42,7 @@ import { ChartPreviewComponent } from '../chart-preview/chart-preview.component' CodeEditorModule, ChartPreviewComponent, AlertComponent, + DashboardsSidebarComponent, ], }) export class ChartEditComponent implements OnInit { diff --git a/frontend/src/app/components/charts/charts-list/charts-list.component.css b/frontend/src/app/components/charts/charts-list/charts-list.component.css index ec54d827d..1f3c8001c 100644 --- a/frontend/src/app/components/charts/charts-list/charts-list.component.css +++ b/frontend/src/app/components/charts/charts-list/charts-list.component.css @@ -1,11 +1,23 @@ +.charts-layout { + display: flex; + height: calc(100vh - 64px); +} + +.charts-main { + flex: 1; + overflow-y: auto; +} + .charts-page { margin: 3em auto; - padding: 0 clamp(200px, 20vw, 300px); + padding: 0 clamp(40px, 5vw, 100px); + max-width: 1400px; } @media (width <= 600px) { .charts-page { - padding: 0 9vw; + padding: 0 16px; + margin: 1.5em auto; } } @@ -50,45 +62,103 @@ } } -.charts-table { +/* Table Styles */ +.widgets-table { width: 100%; - margin-bottom: 16px; + background: transparent; } -.charts-cell_name { - max-width: 250px; +.widgets-table tr.mat-mdc-row { + height: 64px; } -.name-text { +.widgets-table th.mat-mdc-header-cell { font-weight: 500; + color: rgba(0, 0, 0, 0.6); + font-size: 12px; + text-transform: uppercase; + letter-spacing: 0.5px; } -.description-text { - color: rgba(0, 0, 0, 0.87); +@media (prefers-color-scheme: dark) { + .widgets-table th.mat-mdc-header-cell { + color: rgba(255, 255, 255, 0.6); + } +} + +/* Preview Cell */ +.preview-cell { + width: 64px; + padding-right: 8px !important; +} + +.preview-link { + display: flex; + align-items: center; + justify-content: center; + width: 56px; + height: 56px; + text-decoration: none; + overflow: hidden; +} + +.preview-link app-chart-mini-preview { + width: 48px; + height: 40px; +} + +/* Name Link */ +.widget-name-link { + font-weight: 500; + color: inherit; + text-decoration: none; +} + +.widget-name-link:hover { + color: #673ab7; } @media (prefers-color-scheme: dark) { - .description-text { - color: rgba(255, 255, 255, 0.87); + .widget-name-link:hover { + color: #b388ff; + } +} + +/* Description Cell */ +.description-cell { + color: rgba(0, 0, 0, 0.6); + max-width: 300px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +@media (prefers-color-scheme: dark) { + .description-cell { + color: rgba(255, 255, 255, 0.6); } } -.no-description { - color: rgba(0, 0, 0, 0.38); - font-style: italic; +/* Updated Cell */ +.updated-cell { + color: rgba(0, 0, 0, 0.5); + white-space: nowrap; } @media (prefers-color-scheme: dark) { - .no-description { - color: rgba(255, 255, 255, 0.38); + .updated-cell { + color: rgba(255, 255, 255, 0.5); } } -.charts-cell_actions { +/* Actions Cell */ +.actions-cell { + width: 48px; text-align: right; } -.no-charts { +/* Empty State */ +.no-widgets { display: flex; flex-direction: column; align-items: center; @@ -100,12 +170,12 @@ } @media (prefers-color-scheme: dark) { - .no-charts { + .no-widgets { background-color: rgba(255, 255, 255, 0.05); } } -.no-charts-icon { +.no-widgets-icon { font-size: 64px; width: 64px; height: 64px; @@ -114,29 +184,29 @@ } @media (prefers-color-scheme: dark) { - .no-charts-icon { + .no-widgets-icon { color: rgba(255, 255, 255, 0.3); } } -.no-charts h3 { +.no-widgets h3 { margin: 0 0 8px 0; color: rgba(0, 0, 0, 0.87); } @media (prefers-color-scheme: dark) { - .no-charts h3 { + .no-widgets h3 { color: rgba(255, 255, 255, 0.87); } } -.no-charts p { +.no-widgets p { margin: 0 0 16px 0; color: rgba(0, 0, 0, 0.54); } @media (prefers-color-scheme: dark) { - .no-charts p { + .no-widgets p { color: rgba(255, 255, 255, 0.54); } } @@ -150,59 +220,3 @@ color: #ef5350; } } - -/* Responsive table styles */ -@media (width <= 600px) { - .charts-table { - display: grid; - grid-template-columns: auto 1fr; - max-width: 100%; - } - - .charts-table-heading { - display: none; - } - - .charts-table ::ng-deep tbody { - display: grid; - grid-template-columns: subgrid; - grid-column: 1 / 3; - } - - .charts-row { - display: grid; - grid-template-columns: subgrid; - grid-column: 1 / 3; - grid-gap: 12px 28px; - border-bottom-color: var(--mat-table-row-item-outline-color, rgba(0, 0, 0, 0.12)); - border-bottom-width: var(--mat-table-row-item-outline-width, 1px); - border-bottom-style: solid; - height: auto; - padding: 20px 0; - } - - .charts-cell { - display: grid; - grid-template-columns: subgrid; - grid-column: 1 / 3; - border-bottom: none; - } - - .charts-cell::before { - content: attr(data-label); - display: inline-block; - font-weight: bold; - white-space: nowrap; - } - - .charts-cell_actions { - grid-column: 1 / span 3; - display: flex; - justify-content: flex-end; - border-bottom: none; - } - - .charts-cell_actions::before { - display: none; - } -} diff --git a/frontend/src/app/components/charts/charts-list/charts-list.component.html b/frontend/src/app/components/charts/charts-list/charts-list.component.html index bfcb37cee..3595deba5 100644 --- a/frontend/src/app/components/charts/charts-list/charts-list.component.html +++ b/frontend/src/app/components/charts/charts-list/charts-list.component.html @@ -1,111 +1,126 @@ - +
+ + -
-
-

Saved Queries

-

- Create and manage saved SQL queries with chart visualizations. -

-
+
+ -
- - Search queries - - search - +
+
+

Widgets

+

+ Create and manage widgets with chart visualizations for your dashboards. +

+
- - add - Create Query - -
+
+ + Search widgets + + search + - + + add + Create Widget + +
-
- bar_chart -

No saved queries found

-

No queries match your search criteria.

-

Create your first saved query to visualize your data.

- - add - Create Query - -
+ - - - - - - +
+ widgets +

No widgets found

+

No widgets match your search criteria.

+

Create your first widget to visualize your data.

+ + add + Create Widget + +
- - - - - +
Name - {{query.name}} - Description - {{query.description}} - No description -
+ + + + + - - - - - + + + + + - - - - - + + + + + - - -
Type + + + + Last Updated - {{query.updated_at | date:'medium'}} - Name + + {{query.name}} + + - - - - edit - Edit - - - - - Description + {{query.description || '—'}} +
+ + + Updated + + {{formatUpdatedAt(query.updated_at)}} + + + + + + + + + + + edit + Edit + + + + + + + + + + +
+
diff --git a/frontend/src/app/components/charts/charts-list/charts-list.component.spec.ts b/frontend/src/app/components/charts/charts-list/charts-list.component.spec.ts index f42d8e04b..a536165cb 100644 --- a/frontend/src/app/components/charts/charts-list/charts-list.component.spec.ts +++ b/frontend/src/app/components/charts/charts-list/charts-list.component.spec.ts @@ -116,7 +116,7 @@ describe('ChartsListComponent', () => { }); it('should have correct displayed columns', () => { - expect(component.displayedColumns).toEqual(['name', 'description', 'updatedAt', 'actions']); + expect(component.displayedColumns).toEqual(['preview', 'name', 'description', 'updated', 'actions']); }); describe('filteredQueries computed', () => { diff --git a/frontend/src/app/components/charts/charts-list/charts-list.component.ts b/frontend/src/app/components/charts/charts-list/charts-list.component.ts index 81f0f0e49..caa969c21 100644 --- a/frontend/src/app/components/charts/charts-list/charts-list.component.ts +++ b/frontend/src/app/components/charts/charts-list/charts-list.component.ts @@ -3,23 +3,26 @@ import { Component, computed, effect, inject, OnInit, signal } from '@angular/co import { toSignal } from '@angular/core/rxjs-interop'; import { FormsModule } from '@angular/forms'; import { MatButtonModule } from '@angular/material/button'; +import { MatCardModule } from '@angular/material/card'; +import { MatTableModule } from '@angular/material/table'; import { MatDialog } from '@angular/material/dialog'; import { MatDividerModule } from '@angular/material/divider'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatIconModule } from '@angular/material/icon'; import { MatInputModule } from '@angular/material/input'; import { MatMenuModule } from '@angular/material/menu'; -import { MatTableModule } from '@angular/material/table'; import { MatTooltipModule } from '@angular/material/tooltip'; import { Title } from '@angular/platform-browser'; import { ActivatedRoute, RouterModule } from '@angular/router'; import { Angulartics2 } from 'angulartics2'; -import { SavedQuery } from 'src/app/models/saved-query'; +import { ChartType, SavedQuery } from 'src/app/models/saved-query'; import { ConnectionsService } from 'src/app/services/connections.service'; import { SavedQueriesService } from 'src/app/services/saved-queries.service'; +import { ChartMiniPreviewComponent } from '../../dashboards/chart-mini-preview/chart-mini-preview.component'; import { PlaceholderTableDataComponent } from '../../skeletons/placeholder-table-data/placeholder-table-data.component'; import { AlertComponent } from '../../ui-components/alert/alert.component'; import { ChartDeleteDialogComponent } from '../chart-delete-dialog/chart-delete-dialog.component'; +import { DashboardsSidebarComponent } from '../../dashboards/dashboards-sidebar/dashboards-sidebar.component'; @Component({ selector: 'app-charts-list', @@ -29,22 +32,48 @@ import { ChartDeleteDialogComponent } from '../chart-delete-dialog/chart-delete- CommonModule, FormsModule, RouterModule, - MatTableModule, MatButtonModule, + MatCardModule, MatIconModule, MatMenuModule, MatInputModule, MatFormFieldModule, MatTooltipModule, MatDividerModule, + MatTableModule, + ChartMiniPreviewComponent, PlaceholderTableDataComponent, AlertComponent, + DashboardsSidebarComponent, ], }) export class ChartsListComponent implements OnInit { protected searchQuery = signal(''); protected connectionId = signal(''); - public displayedColumns = ['name', 'description', 'updatedAt', 'actions']; + displayedColumns = ['preview', 'name', 'description', 'updated', 'actions']; + + private chartTypeIcons: Record = { + bar: 'bar_chart', + line: 'show_chart', + pie: 'pie_chart', + doughnut: 'donut_large', + polarArea: 'radar', + }; + + getChartIcon(chartType: ChartType | null): string { + return chartType ? this.chartTypeIcons[chartType] : 'bar_chart'; + } + + getChartTypeName(chartType: ChartType | null): string { + const names: Record = { + bar: 'Bar Chart', + line: 'Line Chart', + pie: 'Pie Chart', + doughnut: 'Doughnut Chart', + polarArea: 'Polar Area Chart', + }; + return chartType ? names[chartType] : 'Bar Chart'; + } private _savedQueries = inject(SavedQueriesService); private _connections = inject(ConnectionsService); @@ -110,4 +139,20 @@ export class ChartsListComponent implements OnInit { action: 'Charts: delete chart dialog opened', }); } + + formatUpdatedAt(date: string): string { + const now = new Date(); + const updated = new Date(date); + const diffMs = now.getTime() - updated.getTime(); + const diffMins = Math.floor(diffMs / 60000); + const diffHours = Math.floor(diffMs / 3600000); + const diffDays = Math.floor(diffMs / 86400000); + + if (diffMins < 1) return 'Just now'; + if (diffMins < 60) return `${diffMins} min ago`; + if (diffHours < 24) return `${diffHours} hour${diffHours > 1 ? 's' : ''} ago`; + if (diffDays < 7) return `${diffDays} day${diffDays > 1 ? 's' : ''} ago`; + + return updated.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' }); + } } diff --git a/frontend/src/app/components/dashboard/db-table-view/db-table-widgets/db-table-widgets.component.ts b/frontend/src/app/components/dashboard/db-table-view/db-table-widgets/db-table-widgets.component.ts index 2b58e24fb..7b8143c10 100644 --- a/frontend/src/app/components/dashboard/db-table-view/db-table-widgets/db-table-widgets.component.ts +++ b/frontend/src/app/components/dashboard/db-table-view/db-table-widgets/db-table-widgets.component.ts @@ -23,7 +23,6 @@ import { BreadcrumbsComponent } from '../../../ui-components/breadcrumbs/breadcr import { CodeEditComponent } from '../../../ui-components/record-edit-fields/code/code.component'; import { ImageEditComponent } from '../../../ui-components/record-edit-fields/image/image.component'; import { LongTextEditComponent } from '../../../ui-components/record-edit-fields/long-text/long-text.component'; -import { MarkdownEditComponent } from '../../../ui-components/record-edit-fields/markdown/markdown.component'; import { PasswordEditComponent } from '../../../ui-components/record-edit-fields/password/password.component'; import { SelectEditComponent } from '../../../ui-components/record-edit-fields/select/select.component'; import { TextEditComponent } from '../../../ui-components/record-edit-fields/text/text.component'; @@ -49,7 +48,6 @@ import { WidgetDeleteDialogComponent } from './widget-delete-dialog/widget-delet PasswordEditComponent, ImageEditComponent, CodeEditComponent, - MarkdownEditComponent, WidgetComponent, TextEditComponent, LongTextEditComponent, diff --git a/frontend/src/app/components/dashboards/chart-mini-preview/chart-mini-preview.component.css b/frontend/src/app/components/dashboards/chart-mini-preview/chart-mini-preview.component.css new file mode 100644 index 000000000..1be3330d7 --- /dev/null +++ b/frontend/src/app/components/dashboards/chart-mini-preview/chart-mini-preview.component.css @@ -0,0 +1,85 @@ +.chart-mini-preview { + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; +} + +.chart-svg { + width: 100%; + height: 100%; + max-width: 120px; + max-height: 80px; +} + +/* Bar Chart */ +.bar { + fill: rgba(99, 102, 241, 0.7); +} + +.bar-1 { fill: rgba(99, 102, 241, 0.8); } +.bar-2 { fill: rgba(168, 85, 247, 0.8); } +.bar-3 { fill: rgba(236, 72, 153, 0.8); } +.bar-4 { fill: rgba(59, 130, 246, 0.8); } +.bar-5 { fill: rgba(34, 197, 94, 0.8); } + +/* Line Chart */ +.line-path { + stroke: rgba(99, 102, 241, 1); + stroke-width: 2.5; + stroke-linecap: round; + stroke-linejoin: round; +} + +.line-area { + fill: rgba(99, 102, 241, 0.15); +} + +.line-dot { + fill: rgba(99, 102, 241, 1); +} + +.gradient-start { + stop-color: rgba(99, 102, 241, 0.3); +} + +.gradient-end { + stop-color: rgba(99, 102, 241, 0); +} + +/* Pie Chart */ +.pie-slice { + stroke: #fff; + stroke-width: 1; +} + +.slice-1 { fill: rgba(99, 102, 241, 0.85); } +.slice-2 { fill: rgba(168, 85, 247, 0.85); } +.slice-3 { fill: rgba(236, 72, 153, 0.85); } +.slice-4 { fill: rgba(251, 146, 60, 0.85); } +.slice-5 { fill: rgba(34, 197, 94, 0.85); } + +/* Doughnut Chart */ +.doughnut-slice { + stroke: #fff; + stroke-width: 1; +} + +/* Polar Area Chart */ +.polar-slice { + stroke: #fff; + stroke-width: 1; +} + +@media (prefers-color-scheme: dark) { + .pie-slice, + .doughnut-slice, + .polar-slice { + stroke: #1e1e1e; + } + + .line-area { + fill: rgba(99, 102, 241, 0.25); + } +} diff --git a/frontend/src/app/components/dashboards/chart-mini-preview/chart-mini-preview.component.html b/frontend/src/app/components/dashboards/chart-mini-preview/chart-mini-preview.component.html new file mode 100644 index 000000000..13c0315bc --- /dev/null +++ b/frontend/src/app/components/dashboards/chart-mini-preview/chart-mini-preview.component.html @@ -0,0 +1,54 @@ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/frontend/src/app/components/dashboards/chart-mini-preview/chart-mini-preview.component.ts b/frontend/src/app/components/dashboards/chart-mini-preview/chart-mini-preview.component.ts new file mode 100644 index 000000000..0acaf1852 --- /dev/null +++ b/frontend/src/app/components/dashboards/chart-mini-preview/chart-mini-preview.component.ts @@ -0,0 +1,17 @@ +import { CommonModule } from '@angular/common'; +import { Component, Input } from '@angular/core'; +import { ChartType } from 'src/app/models/saved-query'; + +@Component({ + selector: 'app-chart-mini-preview', + templateUrl: './chart-mini-preview.component.html', + styleUrls: ['./chart-mini-preview.component.css'], + imports: [CommonModule], +}) +export class ChartMiniPreviewComponent { + @Input() chartType: ChartType | null = 'bar'; + + get displayType(): ChartType { + return this.chartType || 'bar'; + } +} diff --git a/frontend/src/app/components/dashboards/dashboard-edit-dialog/dashboard-edit-dialog.component.css b/frontend/src/app/components/dashboards/dashboard-edit-dialog/dashboard-edit-dialog.component.css index 7d04bba7b..87944b9f3 100644 --- a/frontend/src/app/components/dashboards/dashboard-edit-dialog/dashboard-edit-dialog.component.css +++ b/frontend/src/app/components/dashboards/dashboard-edit-dialog/dashboard-edit-dialog.component.css @@ -3,6 +3,7 @@ flex-direction: column; gap: 8px; min-width: 400px; + padding-top: 8px; } @media (width <= 600px) { diff --git a/frontend/src/app/components/dashboards/dashboard-edit-dialog/dashboard-edit-dialog.component.html b/frontend/src/app/components/dashboards/dashboard-edit-dialog/dashboard-edit-dialog.component.html index dee780ba6..a3955f8ab 100644 --- a/frontend/src/app/components/dashboards/dashboard-edit-dialog/dashboard-edit-dialog.component.html +++ b/frontend/src/app/components/dashboards/dashboard-edit-dialog/dashboard-edit-dialog.component.html @@ -1,41 +1,42 @@ -

{{ isEdit ? 'Edit Dashboard' : 'Create Dashboard' }}

+
+

{{ isEdit ? 'Edit Dashboard' : 'Create Dashboard' }}

- - - - Name - - @if (form.get('name')?.hasError('required')) { - Name is required - } - @if (form.get('name')?.hasError('maxlength')) { - Name must be less than 255 characters - } - + +
+ + Name + + @if (form.get('name')?.hasError('required')) { + Name is required + } + @if (form.get('name')?.hasError('maxlength')) { + Name must be less than 255 characters + } + - - Description - - @if (form.get('description')?.hasError('maxlength')) { - Description must be less than 1000 characters - } - - - + + Description + + @if (form.get('description')?.hasError('maxlength')) { + Description must be less than 1000 characters + } + +
+
- - - - + + + + + diff --git a/frontend/src/app/components/dashboards/dashboard-view/dashboard-view.component.html b/frontend/src/app/components/dashboards/dashboard-view/dashboard-view.component.html index 0496d6e8d..f7245f281 100644 --- a/frontend/src/app/components/dashboards/dashboard-view/dashboard-view.component.html +++ b/frontend/src/app/components/dashboards/dashboard-view/dashboard-view.component.html @@ -13,21 +13,23 @@

{{ dashboard()?.name || 'Loading...' }}

}
-
- - Edit Mode - - -
+ @if (gridsterItems().length > 0) { +
+ + Edit Mode + + +
+ } @if (loading()) { @@ -41,7 +43,7 @@

{{ dashboard()?.name || 'Loading...' }}

bar_chart

No charts yet

Add charts to visualize your data from saved queries.

- diff --git a/frontend/src/app/components/dashboards/dashboards-list/dashboards-list.component.css b/frontend/src/app/components/dashboards/dashboards-list/dashboards-list.component.css index e4878a054..e141f9eff 100644 --- a/frontend/src/app/components/dashboards/dashboards-list/dashboards-list.component.css +++ b/frontend/src/app/components/dashboards/dashboards-list/dashboards-list.component.css @@ -1,11 +1,23 @@ +.dashboards-layout { + display: flex; + height: calc(100vh - 64px); +} + +.dashboards-main { + flex: 1; + overflow-y: auto; +} + .dashboards-page { margin: 3em auto; - padding: 0 clamp(200px, 20vw, 300px); + padding: 0 clamp(40px, 5vw, 100px); + max-width: 1400px; } @media (width <= 600px) { .dashboards-page { - padding: 0 9vw; + padding: 0 16px; + margin: 1.5em auto; } } @@ -50,71 +62,171 @@ } } -.toolbar-actions { +/* Cards Grid */ +.dashboards-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); + gap: 24px; +} + +@media (width <= 600px) { + .dashboards-grid { + grid-template-columns: 1fr; + gap: 16px; + } +} + +/* Dashboard Card */ +.dashboard-card { + display: flex; + flex-direction: column; + overflow: hidden; + transition: box-shadow 0.2s ease, transform 0.2s ease; +} + +.dashboard-card:hover { + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.12); + transform: translateY(-2px); +} + +@media (prefers-color-scheme: dark) { + .dashboard-card:hover { + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.4); + } +} + +/* Preview Area */ +.dashboard-preview { + display: block; + height: 160px; + background-color: rgba(0, 0, 0, 0.03); + border-bottom: 1px solid rgba(0, 0, 0, 0.08); + text-decoration: none; + overflow: hidden; +} + +@media (prefers-color-scheme: dark) { + .dashboard-preview { + background-color: rgba(255, 255, 255, 0.05); + border-bottom-color: rgba(255, 255, 255, 0.08); + } +} + +.preview-placeholder { display: flex; - gap: 12px; align-items: center; + justify-content: center; + height: 100%; + background-color: #f5f5f5; } -@media (width <= 600px) { - .toolbar-actions { - flex-direction: column; - width: 100%; +.preview-placeholder-icon { + font-size: 56px; + width: 56px; + height: 56px; + color: rgba(0, 0, 0, 0.2); +} + +@media (prefers-color-scheme: dark) { + .preview-placeholder { + background-color: #2a2a2a; } - .toolbar-actions a, - .toolbar-actions button { - width: 100%; + .preview-placeholder-icon { + color: rgba(255, 255, 255, 0.2); } } -.dashboards-table { - width: 100%; - margin-bottom: 16px; +/* Card Content */ +.dashboard-card-content { + padding: 16px; + flex: 1; + display: flex; + flex-direction: column; } -.dashboards-cell_name { - max-width: 250px; +.dashboard-card-header { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 8px; + margin-bottom: 8px; } -.name-link { - text-decoration: none; +.dashboard-name { + font-size: 16px; + font-weight: 500; color: inherit; + text-decoration: none; + line-height: 1.4; + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; } -.name-link:hover .name-text { - text-decoration: underline; +.dashboard-name:hover { + color: #673ab7; } -.name-text { - font-weight: 500; +@media (prefers-color-scheme: dark) { + .dashboard-name:hover { + color: #b388ff; + } } -.description-text { - color: rgba(0, 0, 0, 0.87); +.dashboard-menu-button { + flex-shrink: 0; + margin: -8px -8px 0 0; +} + +.dashboard-description-text { + font-size: 14px; + color: rgba(0, 0, 0, 0.6); + margin: 0 0 12px 0; + overflow: hidden; + text-overflow: ellipsis; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; } @media (prefers-color-scheme: dark) { - .description-text { - color: rgba(255, 255, 255, 0.87); + .dashboard-description-text { + color: rgba(255, 255, 255, 0.6); } } -.no-description { - color: rgba(0, 0, 0, 0.38); - font-style: italic; +.dashboard-meta { + display: flex; + align-items: center; + justify-content: space-between; + margin-top: auto; + font-size: 12px; + color: rgba(0, 0, 0, 0.5); } @media (prefers-color-scheme: dark) { - .no-description { - color: rgba(255, 255, 255, 0.38); + .dashboard-meta { + color: rgba(255, 255, 255, 0.5); } } -.dashboards-cell_actions { - text-align: right; +.dashboard-widgets-count { + display: flex; + align-items: center; + gap: 4px; +} + +.meta-icon { + font-size: 16px; + width: 16px; + height: 16px; } +/* Empty State */ .no-dashboards { display: flex; flex-direction: column; @@ -177,59 +289,3 @@ color: #ef5350; } } - -/* Responsive table styles */ -@media (width <= 600px) { - .dashboards-table { - display: grid; - grid-template-columns: auto 1fr; - max-width: 100%; - } - - .dashboards-table-heading { - display: none; - } - - .dashboards-table ::ng-deep tbody { - display: grid; - grid-template-columns: subgrid; - grid-column: 1 / 3; - } - - .dashboards-row { - display: grid; - grid-template-columns: subgrid; - grid-column: 1 / 3; - grid-gap: 12px 28px; - border-bottom-color: var(--mat-table-row-item-outline-color, rgba(0, 0, 0, 0.12)); - border-bottom-width: var(--mat-table-row-item-outline-width, 1px); - border-bottom-style: solid; - height: auto; - padding: 20px 0; - } - - .dashboards-cell { - display: grid; - grid-template-columns: subgrid; - grid-column: 1 / 3; - border-bottom: none; - } - - .dashboards-cell::before { - content: attr(data-label); - display: inline-block; - font-weight: bold; - white-space: nowrap; - } - - .dashboards-cell_actions { - grid-column: 1 / span 3; - display: flex; - justify-content: flex-end; - border-bottom: none; - } - - .dashboards-cell_actions::before { - display: none; - } -} diff --git a/frontend/src/app/components/dashboards/dashboards-list/dashboards-list.component.html b/frontend/src/app/components/dashboards/dashboards-list/dashboards-list.component.html index 1d5f770ef..825122006 100644 --- a/frontend/src/app/components/dashboards/dashboards-list/dashboards-list.component.html +++ b/frontend/src/app/components/dashboards/dashboards-list/dashboards-list.component.html @@ -1,126 +1,117 @@ - +
+ + -
-
-

Dashboards

-

- Create and manage custom dashboards with charts, tables, and metrics. -

-
+
+ -
- - Search dashboards - - search - +
+
+

Dashboards

+

+ Create and manage custom dashboards with charts, tables, and metrics. +

+
-
- - code - Manage Saved Queries - - -
-
+
+ + Search dashboards + + search + - - -
- dashboard -

No dashboards found

-

No dashboards match your search criteria.

-

Create your first dashboard to visualize your data.

- -
+ +
- - - - - - + - - - - - +
+ dashboard +

No dashboards found

+

No dashboards match your search criteria.

+

Create your first dashboard to visualize your data.

+ +
- - - - - +
+ - - -
- - - - -
Name - - {{dashboard.name}} - - Description - {{dashboard.description}} - No description - Last Updated - {{dashboard.updated_at | date:'medium'}} - - - - + - visibility - View + class="dashboard-preview"> +
+ dashboard +
- - - -
-
+ + +
+ + {{dashboard.name}} + + + + + visibility + View + + + + + +
+

+ {{dashboard.description}} +

+
+ + widgets + {{dashboard.widgets?.length || 0}} widgets + + + {{dashboard.updated_at | date:'mediumDate'}} + +
+
+ +
+
+
diff --git a/frontend/src/app/components/dashboards/dashboards-list/dashboards-list.component.spec.ts b/frontend/src/app/components/dashboards/dashboards-list/dashboards-list.component.spec.ts index 4d1d52fc4..596ce1129 100644 --- a/frontend/src/app/components/dashboards/dashboards-list/dashboards-list.component.spec.ts +++ b/frontend/src/app/components/dashboards/dashboards-list/dashboards-list.component.spec.ts @@ -30,6 +30,7 @@ describe('DashboardsListComponent', () => { dashboardsLoading: dashboardsLoadingSignal.asReadonly(), dashboardsUpdated: dashboardsUpdatedSignal.asReadonly(), setActiveConnection: vi.fn(), + setActiveDashboard: vi.fn(), refreshDashboards: vi.fn(), }; diff --git a/frontend/src/app/components/dashboards/dashboards-list/dashboards-list.component.ts b/frontend/src/app/components/dashboards/dashboards-list/dashboards-list.component.ts index 38074d176..40cfc1143 100644 --- a/frontend/src/app/components/dashboards/dashboards-list/dashboards-list.component.ts +++ b/frontend/src/app/components/dashboards/dashboards-list/dashboards-list.component.ts @@ -3,13 +3,13 @@ import { Component, computed, DestroyRef, effect, inject, OnInit, signal } from import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { FormsModule } from '@angular/forms'; import { MatButtonModule } from '@angular/material/button'; +import { MatCardModule } from '@angular/material/card'; import { MatDialog } from '@angular/material/dialog'; import { MatDividerModule } from '@angular/material/divider'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatIconModule } from '@angular/material/icon'; import { MatInputModule } from '@angular/material/input'; import { MatMenuModule } from '@angular/material/menu'; -import { MatTableModule } from '@angular/material/table'; import { MatTooltipModule } from '@angular/material/tooltip'; import { Title } from '@angular/platform-browser'; import { ActivatedRoute, RouterModule } from '@angular/router'; @@ -21,6 +21,7 @@ import { PlaceholderTableDataComponent } from '../../skeletons/placeholder-table import { AlertComponent } from '../../ui-components/alert/alert.component'; import { DashboardDeleteDialogComponent } from '../dashboard-delete-dialog/dashboard-delete-dialog.component'; import { DashboardEditDialogComponent } from '../dashboard-edit-dialog/dashboard-edit-dialog.component'; +import { DashboardsSidebarComponent } from '../dashboards-sidebar/dashboards-sidebar.component'; @Component({ selector: 'app-dashboards-list', @@ -30,8 +31,8 @@ import { DashboardEditDialogComponent } from '../dashboard-edit-dialog/dashboard CommonModule, FormsModule, RouterModule, - MatTableModule, MatButtonModule, + MatCardModule, MatIconModule, MatMenuModule, MatInputModule, @@ -40,12 +41,12 @@ import { DashboardEditDialogComponent } from '../dashboard-edit-dialog/dashboard MatDividerModule, PlaceholderTableDataComponent, AlertComponent, + DashboardsSidebarComponent, ], }) export class DashboardsListComponent implements OnInit { protected searchQuery = signal(''); protected connectionId = signal(''); - public displayedColumns = ['name', 'description', 'updatedAt', 'actions']; private _dashboards = inject(DashboardsService); private _connections = inject(ConnectionsService); @@ -88,13 +89,17 @@ export class DashboardsListComponent implements OnInit { // Dashboards update effect effect(() => { const action = this._dashboards.dashboardsUpdated(); - if (action) this._dashboards.refreshDashboards(); + if (action) { + this._dashboards.refreshDashboards(); + this._dashboards.clearDashboardsUpdated(); + } }); } ngOnInit(): void { const connId = this.route.snapshot.paramMap.get('connection-id') || ''; this.connectionId.set(connId); + this._dashboards.setActiveDashboard(null); this._dashboards.setActiveConnection(connId); } diff --git a/frontend/src/app/components/dashboards/dashboards-sidebar/dashboards-sidebar.component.css b/frontend/src/app/components/dashboards/dashboards-sidebar/dashboards-sidebar.component.css new file mode 100644 index 000000000..8fab0af64 --- /dev/null +++ b/frontend/src/app/components/dashboards/dashboards-sidebar/dashboards-sidebar.component.css @@ -0,0 +1,98 @@ +.dashboards-sidebar { + width: 220px; + min-width: 220px; + height: 100%; + border-right: 1px solid rgba(0, 0, 0, 0.12); + display: flex; + flex-direction: column; + background-color: #fafafa; + overflow: hidden; +} + +.dashboards-sidebar_initialized { + transition: width 0.2s ease-in-out, min-width 0.2s ease-in-out; +} + +@media (prefers-color-scheme: dark) { + .dashboards-sidebar { + background-color: #1e1e1e; + border-right-color: rgba(255, 255, 255, 0.12); + } +} + +.dashboards-sidebar_collapsed { + width: 56px; + min-width: 56px; +} + +.sidebar-toggle-button { + margin: 12px; + display: flex; + align-items: center; + justify-content: flex-end; + gap: 4px; +} + +.sidebar-toggle-button_collapsed { + justify-content: center; + margin: 12px 8px; +} + +.sidebar-nav { + padding-top: 0; + overflow: hidden; +} + +.sidebar-nav-item { + margin: 4px 8px; + border-radius: 8px; + overflow: hidden; +} + +.sidebar-nav-item ::ng-deep .mdc-list-item__primary-text { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.sidebar-nav-item_collapsed { + margin: 4px; +} + +.sidebar-nav-item_collapsed ::ng-deep .mdc-list-item__content { + padding: 0; + display: flex; + justify-content: center; +} + +.active-link { + background-color: rgba(103, 58, 183, 0.12); +} + +@media (prefers-color-scheme: dark) { + .active-link { + background-color: rgba(179, 136, 255, 0.16); + } +} + +.active-link mat-icon { + color: #673ab7; +} + +@media (prefers-color-scheme: dark) { + .active-link mat-icon { + color: #b388ff; + } +} + +/* Mobile styles */ +@media (width <= 768px) { + .dashboards-sidebar { + width: 56px; + min-width: 56px; + } + + .sidebar-toggle-button:not(.sidebar-toggle-button_collapsed) { + display: none; + } +} diff --git a/frontend/src/app/components/dashboards/dashboards-sidebar/dashboards-sidebar.component.html b/frontend/src/app/components/dashboards/dashboards-sidebar/dashboards-sidebar.component.html new file mode 100644 index 000000000..1b9bd8136 --- /dev/null +++ b/frontend/src/app/components/dashboards/dashboards-sidebar/dashboards-sidebar.component.html @@ -0,0 +1,42 @@ +
+ + + + + + + + + dashboard + Dashboards + + + widgets + Widgets + + +
diff --git a/frontend/src/app/components/dashboards/dashboards-sidebar/dashboards-sidebar.component.ts b/frontend/src/app/components/dashboards/dashboards-sidebar/dashboards-sidebar.component.ts new file mode 100644 index 000000000..762ed7b9e --- /dev/null +++ b/frontend/src/app/components/dashboards/dashboards-sidebar/dashboards-sidebar.component.ts @@ -0,0 +1,54 @@ +import { AfterViewInit, Component, inject, input, OnInit, signal } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { RouterModule } from '@angular/router'; +import { MatListModule } from '@angular/material/list'; +import { MatIconModule } from '@angular/material/icon'; +import { MatButtonModule } from '@angular/material/button'; +import { MatTooltipModule } from '@angular/material/tooltip'; +import { UiSettingsService } from '../../../services/ui-settings.service'; + +@Component({ + selector: 'app-dashboards-sidebar', + templateUrl: './dashboards-sidebar.component.html', + styleUrls: ['./dashboards-sidebar.component.css'], + imports: [ + CommonModule, + RouterModule, + MatListModule, + MatIconModule, + MatButtonModule, + MatTooltipModule, + ], +}) +export class DashboardsSidebarComponent implements OnInit, AfterViewInit { + connectionId = input.required(); + activeTab = input<'dashboards' | 'queries'>('dashboards'); + + collapsed = signal(false); + initialized = signal(false); + + private _uiSettings = inject(UiSettingsService); + + ngOnInit(): void { + this._loadCollapsedState(); + } + + ngAfterViewInit(): void { + // Enable transitions after initial render to prevent animation on page load + setTimeout(() => this.initialized.set(true), 0); + } + + toggleCollapsed(): void { + this.collapsed.update((v) => { + const newValue = !v; + this._uiSettings.updateConnectionSetting(this.connectionId(), 'dashboardsSidebarCollapsed', newValue); + return newValue; + }); + } + + private _loadCollapsedState(): void { + this._uiSettings.getUiSettings().subscribe((settings) => { + this.collapsed.set(settings?.connections?.[this.connectionId()]?.dashboardsSidebarCollapsed ?? false); + }); + } +} diff --git a/frontend/src/app/components/dashboards/widget-edit-dialog/widget-edit-dialog.component.css b/frontend/src/app/components/dashboards/widget-edit-dialog/widget-edit-dialog.component.css index 2e9f21fc6..5df989bc0 100644 --- a/frontend/src/app/components/dashboards/widget-edit-dialog/widget-edit-dialog.component.css +++ b/frontend/src/app/components/dashboards/widget-edit-dialog/widget-edit-dialog.component.css @@ -3,6 +3,7 @@ flex-direction: column; gap: 8px; min-width: 400px; + padding-top: 8px; } @media (width <= 600px) { @@ -14,3 +15,61 @@ .full-width { width: 100%; } + +.no-queries-message { + display: flex; + flex-direction: column; + align-items: center; + text-align: center; + padding: 24px; + min-width: 300px; +} + +.no-queries-icon { + font-size: 48px; + width: 48px; + height: 48px; + color: rgba(0, 0, 0, 0.26); + margin-bottom: 16px; +} + +@media (prefers-color-scheme: dark) { + .no-queries-icon { + color: rgba(255, 255, 255, 0.3); + } +} + +.no-queries-message h3 { + margin: 0 0 8px 0; + font-size: 18px; + font-weight: 500; +} + +.no-queries-message p { + margin: 0 0 20px 0; + color: rgba(0, 0, 0, 0.6); + font-size: 14px; +} + +@media (prefers-color-scheme: dark) { + .no-queries-message p { + color: rgba(255, 255, 255, 0.6); + } +} + +.create-option { + color: #673ab7; +} + +.create-option mat-icon { + margin-right: 8px; + font-size: 20px; + width: 20px; + height: 20px; +} + +@media (prefers-color-scheme: dark) { + .create-option { + color: #b388ff; + } +} diff --git a/frontend/src/app/components/dashboards/widget-edit-dialog/widget-edit-dialog.component.html b/frontend/src/app/components/dashboards/widget-edit-dialog/widget-edit-dialog.component.html index 2cacb6888..6f0848085 100644 --- a/frontend/src/app/components/dashboards/widget-edit-dialog/widget-edit-dialog.component.html +++ b/frontend/src/app/components/dashboards/widget-edit-dialog/widget-edit-dialog.component.html @@ -1,29 +1,51 @@ -

{{ isEdit ? 'Edit Widget' : 'Add Widget' }}

+
+

{{ isEdit ? 'Edit Widget' : 'Add Chart' }}

- - - - Saved Query - - @for (query of savedQueries(); track query.id) { - {{ query.name }} - } - - Select a saved query to display in this widget - @if (form.get('query_id')?.hasError('required')) { - Please select a saved query - } - - -
+ + @if (savedQueries().length > 0) { +
+ + Saved Query + + @for (query of savedQueries(); track query.id) { + {{ query.name }} + } + + + add + Create new query + + + Select a saved query to display in this widget + @if (form.get('query_id')?.hasError('required')) { + Please select a saved query + } + +
+ } @else { +
+ code +

No saved queries yet

+

Create a saved query first to add it as a chart on this dashboard.

+ + add + Create Query + +
+ } +
- - - - + + + @if (savedQueries().length > 0) { + + } + + diff --git a/frontend/src/app/components/dashboards/widget-edit-dialog/widget-edit-dialog.component.ts b/frontend/src/app/components/dashboards/widget-edit-dialog/widget-edit-dialog.component.ts index 30f4facac..d50adc11e 100644 --- a/frontend/src/app/components/dashboards/widget-edit-dialog/widget-edit-dialog.component.ts +++ b/frontend/src/app/components/dashboards/widget-edit-dialog/widget-edit-dialog.component.ts @@ -4,7 +4,10 @@ import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angula import { MatButtonModule } from '@angular/material/button'; import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/material/dialog'; import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatDividerModule } from '@angular/material/divider'; +import { MatIconModule } from '@angular/material/icon'; import { MatSelectModule } from '@angular/material/select'; +import { Router, RouterModule } from '@angular/router'; import { Angulartics2 } from 'angulartics2'; import { DashboardWidget } from 'src/app/models/dashboard'; import { DashboardsService } from 'src/app/services/dashboards.service'; @@ -14,7 +17,17 @@ import { SavedQueriesService } from 'src/app/services/saved-queries.service'; selector: 'app-widget-edit-dialog', templateUrl: './widget-edit-dialog.component.html', styleUrls: ['./widget-edit-dialog.component.css'], - imports: [CommonModule, ReactiveFormsModule, MatDialogModule, MatButtonModule, MatFormFieldModule, MatSelectModule], + imports: [ + CommonModule, + ReactiveFormsModule, + RouterModule, + MatDialogModule, + MatButtonModule, + MatDividerModule, + MatFormFieldModule, + MatIconModule, + MatSelectModule, + ], }) export class WidgetEditDialogComponent implements OnInit { protected submitting = signal(false); @@ -26,9 +39,10 @@ export class WidgetEditDialogComponent implements OnInit { constructor( @Inject(MAT_DIALOG_DATA) public data: { connectionId: string; dashboardId: string; widget: DashboardWidget | null }, - private dialogRef: MatDialogRef, + public dialogRef: MatDialogRef, private _dashboards: DashboardsService, private _savedQueries: SavedQueriesService, + private _router: Router, private fb: FormBuilder, private angulartics2: Angulartics2, ) { @@ -87,4 +101,9 @@ export class WidgetEditDialogComponent implements OnInit { } this.submitting.set(false); } + + navigateToCreateQuery(): void { + this.dialogRef.close(); + this._router.navigate(['/charts', this.data.connectionId, 'new']); + } } diff --git a/frontend/src/app/models/ui-settings.ts b/frontend/src/app/models/ui-settings.ts index e011be8bb..a4beffcb9 100644 --- a/frontend/src/app/models/ui-settings.ts +++ b/frontend/src/app/models/ui-settings.ts @@ -15,6 +15,7 @@ export interface ConnectionSettingsUI { shownTableTitles: boolean; tables: { [tableName: string]: TableSettingsUI }; tableFoldersExpanded?: string[]; + dashboardsSidebarCollapsed?: boolean; } export interface UiSettings { diff --git a/frontend/src/app/services/dashboards.service.ts b/frontend/src/app/services/dashboards.service.ts index 74a9e3580..67a173896 100644 --- a/frontend/src/app/services/dashboards.service.ts +++ b/frontend/src/app/services/dashboards.service.ts @@ -85,6 +85,10 @@ export class DashboardsService { this._dashboardsResource.reload(); } + clearDashboardsUpdated(): void { + this._dashboardsUpdated.set(''); + } + refreshDashboard(): void { this._dashboardResource.reload(); } @@ -149,6 +153,7 @@ export class DashboardsService { this._http.post(`/dashboard/${dashboardId}/widget/${connectionId}`, payload), ); this._notifications.showSuccessSnackbar('Widget created successfully'); + this._dashboardsUpdated.set('updated'); return widget; } catch (err: unknown) { const error = err as { error?: { message?: string } }; @@ -202,6 +207,7 @@ export class DashboardsService { this._http.delete(`/dashboard/${dashboardId}/widget/${widgetId}/${connectionId}`), ); this._notifications.showSuccessSnackbar('Widget deleted successfully'); + this._dashboardsUpdated.set('updated'); return widget; } catch (err: unknown) { const error = err as { error?: { message?: string } }; diff --git a/frontend/src/app/services/ui-settings.service.ts b/frontend/src/app/services/ui-settings.service.ts index 16b0dffec..2a40fc1bd 100644 --- a/frontend/src/app/services/ui-settings.service.ts +++ b/frontend/src/app/services/ui-settings.service.ts @@ -46,17 +46,17 @@ export class UiSettingsService { this.syncUiSettings().subscribe(); } - updateTableSetting(connectionId: string, tableName: string, key: string, value: any) { - console.log('updateTableSetting') - if (!this.settings.connections[connectionId]) { - this.settings.connections[connectionId] = { shownTableTitles: false, tables: {} }; - } - // if (!this.settings.connections[connectionId].tables[tableName]) { - // this.settings.connections[connectionId].tables[tableName] = { shownColumns: [] }; - // } - this.settings.connections[connectionId].tables[tableName][key] = value; - this.syncUiSettings().subscribe(); - } + updateTableSetting(connectionId: string, tableName: string, key: string, value: any) { + console.log('updateTableSetting') + if (!this.settings.connections[connectionId]) { + this.settings.connections[connectionId] = { shownTableTitles: false, tables: {} }; + } + // if (!this.settings.connections[connectionId].tables[tableName]) { + // this.settings.connections[connectionId].tables[tableName] = { shownColumns: [] }; + // } + this.settings.connections[connectionId].tables[tableName][key] = value; + this.syncUiSettings().subscribe(); + } getUiSettings(): Observable { if (!this.uiSettings) {