diff --git a/application/src/main/data/json/system/widget_bundles/home_page_widgets.json b/application/src/main/data/json/system/widget_bundles/home_page_widgets.json index 62eb3d4d6e..eeede28b39 100644 --- a/application/src/main/data/json/system/widget_bundles/home_page_widgets.json +++ b/application/src/main/data/json/system/widget_bundles/home_page_widgets.json @@ -83,6 +83,25 @@ "settingsDirective": "tb-quick-links-widget-settings", "defaultConfig": "{\"showTitle\":false,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"columns\":3},\"title\":\"Quick links\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"widgetCss\":\"\",\"pageSize\":1024,\"noDataDisplayMessage\":\"\",\"showLegend\":false}" } + }, + { + "alias": "dashboards", + "name": "Dashboards", + "image": null, + "description": null, + "descriptor": { + "type": "static", + "sizeX": 7.5, + "sizeY": 6.5, + "resources": [], + "templateHtml": "\n\n", + "templateCss": "", + "controllerScript": "self.onInit = function() {\n}", + "settingsSchema": "", + "dataKeySettingsSchema": "", + "settingsDirective": "", + "defaultConfig": "{\"showTitle\":false,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Dashboards\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"widgetCss\":\"\",\"pageSize\":1024,\"noDataDisplayMessage\":\"\",\"showLegend\":false}" + } } ] } \ No newline at end of file diff --git a/ui-ngx/src/app/core/http/user-settings.service.ts b/ui-ngx/src/app/core/http/user-settings.service.ts index ed6fc42a0c..a9adbccdac 100644 --- a/ui-ngx/src/app/core/http/user-settings.service.ts +++ b/ui-ngx/src/app/core/http/user-settings.service.ts @@ -19,7 +19,7 @@ import { Observable } from 'rxjs'; import { DocumentationLink, DocumentationLinks, GettingStarted, - initialUserSettings, QuickLinks, + initialUserSettings, QuickLinks, UserDashboardAction, UserDashboardsInfo, UserSettings, UserSettingsType } from '@shared/models/user-settings.models'; @@ -87,4 +87,15 @@ export class UserSettingsService { defaultHttpOptionsFromConfig(config)); } + public getUserDashboardsInfo(config?: RequestConfig): Observable { + return this.http.get('/api/user/dashboards', + defaultHttpOptionsFromConfig(config)); + } + + public reportUserDashboardAction(dashboardId: string, action: UserDashboardAction, + config?: RequestConfig): Observable { + return this.http.get(`/api/user/dashboards/${dashboardId}/${action}`, + defaultHttpOptionsFromConfig(config)); + } + } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/home-page/home-page-widgets.module.ts b/ui-ngx/src/app/modules/home/components/widget/lib/home-page/home-page-widgets.module.ts index 6de881288a..84aeef4118 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/home-page/home-page-widgets.module.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/home-page/home-page-widgets.module.ts @@ -33,6 +33,9 @@ import { UsageInfoWidgetComponent } from '@home/components/widget/lib/home-page/ import { QuickLinksWidgetComponent } from '@home/components/widget/lib/home-page/quick-links-widget.component'; import { QuickLinkComponent } from '@home/components/widget/lib/home-page/quick-link.component'; import { AddQuickLinkDialogComponent } from '@home/components/widget/lib/home-page/add-quick-link-dialog.component'; +import { + RecentDashboardsWidgetComponent +} from '@home/components/widget/lib/home-page/recent-dashboards-widget.component'; @NgModule({ declarations: @@ -50,7 +53,8 @@ import { AddQuickLinkDialogComponent } from '@home/components/widget/lib/home-pa UsageInfoWidgetComponent, QuickLinksWidgetComponent, QuickLinkComponent, - AddQuickLinkDialogComponent + AddQuickLinkDialogComponent, + RecentDashboardsWidgetComponent ], imports: [ CommonModule, @@ -70,7 +74,8 @@ import { AddQuickLinkDialogComponent } from '@home/components/widget/lib/home-pa UsageInfoWidgetComponent, QuickLinksWidgetComponent, QuickLinkComponent, - AddQuickLinkDialogComponent + AddQuickLinkDialogComponent, + RecentDashboardsWidgetComponent ] }) export class HomePageWidgetsModule { } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/home-page/recent-dashboards-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/home-page/recent-dashboards-widget.component.html new file mode 100644 index 0000000000..ba6518194f --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/home-page/recent-dashboards-widget.component.html @@ -0,0 +1,106 @@ + +
+ + + +
+ + + + + + {{ lastVisitedDashboard.starred ? 'star' : 'star_border' }} + + + + + {{ 'widgets.recent-dashboards.name' | translate }} + + + {{ lastVisitedDashboard.title }} + + + + + {{ 'widgets.recent-dashboards.last-viewed' | translate }} + + + {{ lastVisitedDashboard.lastVisited | dateAgo:{applyAgo: true} }} + + + + +
+
+ +
+
+
{{ 'widgets.recent-dashboards.no-last-viewed-dashboards' | translate }}
+
+
+
+ +
+
+
+ {{ dashboard.starred ? 'star' : 'star_border' }} +
+ +
+
+ +
+
+ +
+ +
+
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/home-page/recent-dashboards-widget.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/home-page/recent-dashboards-widget.component.scss new file mode 100644 index 0000000000..c3578b2ad5 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/home-page/recent-dashboards-widget.component.scss @@ -0,0 +1,235 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +@import "../../../../../../../scss/constants"; + +:host { + .tb-card-header { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + } + .tb-no-dashboards { + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + } + .tb-no-last-visited-bg { + width: 60px; + height: 60px; + margin: 10px; + position: relative; + &:before { + content: ""; + border-radius: 8px; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: #305680; + -webkit-mask-image: url(/assets/home/no_data_bg.svg); + -webkit-mask-repeat: no-repeat; + mask-image: url(/assets/home/no_data_bg.svg); + mask-repeat: no-repeat; + } + } + .tb-no-last-visited-dashboards-text { + font-weight: 500; + font-size: 14px; + line-height: 20px; + letter-spacing: 0.25px; + color: rgba(0, 0, 0, 0.54); + } + + .tb-starred-dashboard-row { + height: 40px; + display: flex; + flex-direction: row; + align-items: center; + @media #{$mat-md-lg} { + height: 34px; + } + } + + .tb-cell { + box-sizing: content-box; + &:first-child { + padding: 0 12px; + } + &:nth-child(n+2):nth-last-child(n+2) { + padding: 0 28px 0 0; + } + } + + .tb-star-dashboard-autocomplete { + margin-left: 64px; + margin-right: 64px; + @media #{$mat-md-lg} { + margin-left: 48px; + margin-right: 48px; + } + } +} + +:host ::ng-deep { + .mat-mdc-cell, .mat-mdc-header-cell, .mdc-data-table__row:last-child .mdc-data-table__cell { + border-bottom: none; + } + + .mat-mdc-table { + .mdc-data-table__row, .mdc-data-table__header-row { + height: 40px; + @media #{$mat-md-lg} { + height: 34px; + } + + &:hover { + background-color: inherit; + } + } + } + + .tb-cell:nth-child(n+2):nth-last-child(n+2) { + padding: 0 28px 0 0; + } + + .mat-mdc-row:not(.mat-row-select) .mat-mdc-cell:nth-child(n+2):nth-last-child(n+2) { + @media #{$mat-md-lg} { + padding: 0 12px 0 0; + } + } + + .mat-mdc-header-cell, .mat-mdc-cell, .tb-cell { + &.star-cell { + width: 40px; + min-width: 40px; + @media #{$mat-md-lg} { + width: 24px; + min-width: 24px; + max-width: 24px; + } + } + + &.title { + width: 100%; + } + + &.last-visited { + width: 80px; + @media #{$mat-md-lg} { + width: 50px; + } + } + } + + .mat-mdc-header-cell { + font-weight: 400; + font-size: 12px; + line-height: 16px; + letter-spacing: 0.25px; + color: rgba(0, 0, 0, 0.38); + @media #{$mat-md-lg} { + font-size: 11px; + } + } + .mat-mdc-cell.title { + max-width: 0; + } + .mat-mdc-cell, .tb-cell { + font-weight: 400; + font-size: 14px; + line-height: 20px; + letter-spacing: 0.2px; + @media #{$mat-md-lg} { + font-size: 11px; + line-height: 16px; + } + &.title { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + a { + color: rgba(0, 0, 0, 0.87); + border-bottom: none; + &:hover, &:focus { + border-bottom: none; + } + } + } + &.last-visited { + cursor: default; + color: rgba(0, 0, 0, 0.54); + } + .star { + cursor: pointer; + vertical-align: middle; + color: rgba(0, 0, 0, 0.12); + &:hover { + color: rgba(0, 0, 0, 0.18); + } + &.starred { + color: #FAE205; + &:hover { + color: #CDBD2C; + } + } + } + } + .tb-star-dashboard-autocomplete { + .mat-mdc-form-field { + font-weight: 400; + font-size: 14px; + line-height: 20px; + letter-spacing: 0.2px; + color: rgba(0, 0, 0, 0.54); + @media #{$mat-md-lg} { + font-size: 11px; + line-height: 16px; + } + } + .mat-mdc-text-field-wrapper.mdc-text-field--outlined .mat-mdc-form-field-infix { + padding-top: 4px; + padding-bottom: 4px; + min-height: 28px; + @media #{$mat-md-lg} { + min-height: 24px; + } + } + .mat-mdc-form-field-hint-wrapper { + height: 0; + } + .mdc-text-field--outlined .mdc-notched-outline { + .mdc-notched-outline__leading, .mdc-notched-outline__trailing { + border-top-style: dashed; + border-bottom-style: dashed; + } + .mdc-notched-outline__leading { + border-left-style: dashed; + } + .mdc-notched-outline__trailing { + border-right-style: dashed; + } + } + .mat-mdc-icon-button.mat-mdc-button-base { + width: 28px; + height: 28px; + padding: 2px; + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/home-page/recent-dashboards-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/home-page/recent-dashboards-widget.component.ts new file mode 100644 index 0000000000..33b273ec9f --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/home-page/recent-dashboards-widget.component.ts @@ -0,0 +1,222 @@ +/// +/// Copyright © 2016-2023 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { + AfterViewInit, + ChangeDetectorRef, + Component, + Input, + OnDestroy, + OnInit, + QueryList, ViewChild, + ViewChildren +} from '@angular/core'; +import { PageComponent } from '@shared/components/page.component'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { Authority } from '@shared/models/authority.enum'; +import { BehaviorSubject, Observable, of } from 'rxjs'; +import { getCurrentAuthUser } from '@core/auth/auth.selectors'; +import { WidgetContext } from '@home/models/widget-component.models'; +import { + AbstractUserDashboardInfo, + LastVisitedDashboardInfo, StarredDashboardInfo, + UserDashboardAction, + UserDashboardsInfo +} from '@shared/models/user-settings.models'; +import { UserSettingsService } from '@core/http/user-settings.service'; +import { CollectionViewer, DataSource } from '@angular/cdk/collections'; +import { emptyPageData, PageData } from '@shared/models/page/page-data'; +import { MAX_SAFE_PAGE_SIZE, PageLink } from '@shared/models/page/page-link'; +import { map, share } from 'rxjs/operators'; +import { Direction, SortOrder } from '@shared/models/page/sort-order'; +import { MatSort } from '@angular/material/sort'; +import { DashboardInfo } from '@shared/models/dashboard.models'; +import { DashboardAutocompleteComponent } from '@shared/components/dashboard-autocomplete.component'; + +@Component({ + selector: 'tb-recent-dashboards-widget', + templateUrl: './recent-dashboards-widget.component.html', + styleUrls: ['./home-page-widget.scss', './recent-dashboards-widget.component.scss'] +}) +export class RecentDashboardsWidgetComponent extends PageComponent implements OnInit, AfterViewInit, OnDestroy { + + @Input() + ctx: WidgetContext; + + @ViewChildren(MatSort) lastVisitedDashboardsSort: QueryList; + + @ViewChild('starDashboardAutocomplete', {static: false}) + starDashboardAutocomplete: DashboardAutocompleteComponent; + + authority = Authority; + + userDashboardsInfo: UserDashboardsInfo; + authUser = getCurrentAuthUser(this.store); + + toggleValue: 'last' | 'starred' = 'last'; + + lastVisitedDashboardsColumns = ['starred', 'title', 'lastVisited']; + lastVisitedDashboardsDataSource: LastVisitedDashboardsDataSource; + lastVisitedDashboardsPageLink: PageLink; + + starredDashboardValue = null; + + dirty = false; + + constructor(protected store: Store, + private cd: ChangeDetectorRef, + private userSettingService: UserSettingsService) { + super(store); + } + + ngOnInit() { + this.reload(); + } + + reload() { + this.userDashboardsInfo = null; + this.cd.markForCheck(); + (this.authUser.authority !== Authority.SYS_ADMIN ? + this.userSettingService.getUserDashboardsInfo() : of({last: [], starred: []})).subscribe( + (userDashboardsInfo) => { + this.userDashboardsInfo = userDashboardsInfo; + for (const starredDashboard of this.userDashboardsInfo?.starred) { + starredDashboard.starred = true; + } + this.userDashboardsInfo?.starred.sort((a, b) => a.starredAt - b.starredAt); + if (this.hasLastVisitedDashboards()) { + this.initLastVisitedDashboardsDataSource(); + } + this.cd.markForCheck(); + } + ); + } + + toggleValueChange(value: 'last' | 'starred') { + this.toggleValue = value; + if (this.dirty) { + this.dirty = false; + this.reload(); + } else { + if (value === 'last' && this.hasLastVisitedDashboards()) { + this.initLastVisitedDashboardsDataSource(); + } + } + } + + private initLastVisitedDashboardsDataSource() { + this.lastVisitedDashboardsDataSource = new LastVisitedDashboardsDataSource(this.userDashboardsInfo.last); + const sortOrder: SortOrder = { + property: 'lastVisited', + direction: Direction.DESC + }; + this.lastVisitedDashboardsPageLink = new PageLink(MAX_SAFE_PAGE_SIZE, 0, null, sortOrder); + this.lastVisitedDashboardsDataSource.loadData(this.lastVisitedDashboardsPageLink); + } + + ngAfterViewInit() { + this.lastVisitedDashboardsSort.changes.subscribe(() => { + if (this.lastVisitedDashboardsSort.length) { + this.lastVisitedDashboardsSort.get(0).sortChange.subscribe(() => + this.updateLastVisitedDashboardsData(this.lastVisitedDashboardsSort.get(0))); + } + }); + } + + updateLastVisitedDashboardsData(sort: MatSort) { + this.lastVisitedDashboardsPageLink.sortOrder.property = sort.active; + this.lastVisitedDashboardsPageLink.sortOrder.direction = Direction[sort.direction.toUpperCase()]; + this.lastVisitedDashboardsDataSource.loadData(this.lastVisitedDashboardsPageLink); + } + + hasLastVisitedDashboards(): boolean { + return !!(this.userDashboardsInfo && this.userDashboardsInfo.last && this.userDashboardsInfo.last.length); + } + + toggleDashboardStar(dashboard: AbstractUserDashboardInfo): void { + const action: UserDashboardAction = dashboard.starred ? UserDashboardAction.UNSTAR : UserDashboardAction.STAR; + dashboard.starred = !dashboard.starred; + this.userSettingService.reportUserDashboardAction(dashboard.id, action, {ignoreLoading: true}).subscribe(); + this.dirty = true; + if (this.toggleValue === 'starred') { + const index = this.userDashboardsInfo.starred.findIndex((d) => d.id === dashboard.id); + if (index > -1) { + this.userDashboardsInfo.starred.splice(index, 1); + this.cd.markForCheck(); + } + } + } + + onStarDashboard(dashboard: DashboardInfo) { + if (dashboard) { + this.starDashboardAutocomplete.clear(); + const index = this.userDashboardsInfo.starred.findIndex((d) => d.id === dashboard.id.id); + if (index === -1) { + const starredDashboard: StarredDashboardInfo = { + starredAt: Date.now(), + id: dashboard.id.id, + starred: true, + title: dashboard.title + }; + this.userDashboardsInfo.starred.push(starredDashboard); + this.userSettingService.reportUserDashboardAction(dashboard.id.id, UserDashboardAction.STAR, {ignoreLoading: true}).subscribe(); + } + this.cd.markForCheck(); + } + } + +} + +export class LastVisitedDashboardsDataSource implements DataSource { + + private lastVisitedDashboardsSubject = new BehaviorSubject([]); + private pageDataSubject = new BehaviorSubject>(emptyPageData()); + + public pageData$ = this.pageDataSubject.asObservable(); + + constructor(private lastVisitedDashboards: Array) { + } + + connect(collectionViewer: CollectionViewer): Observable> { + return this.lastVisitedDashboardsSubject.asObservable(); + } + + disconnect(collectionViewer: CollectionViewer): void { + this.lastVisitedDashboardsSubject.complete(); + this.pageDataSubject.complete(); + } + + loadData(pageLink: PageLink): void { + const result = pageLink.filterData(this.lastVisitedDashboards); + this.lastVisitedDashboardsSubject.next(result.data); + this.pageDataSubject.next(result); + } + + isEmpty(): Observable { + return this.lastVisitedDashboardsSubject.pipe( + map((entities) => !entities.length), + share() + ); + } + + total(): Observable { + return this.pageDataSubject.pipe( + map((pageData) => pageData.totalElements), + share() + ); + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/home-page/toggle-header.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/home-page/toggle-header.component.html index fadcadd72b..ed39fca25d 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/home-page/toggle-header.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/home-page/toggle-header.component.html @@ -15,12 +15,13 @@ limitations under the License. --> - + {{ option.name }} - + {{ option.name }} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/home-page/toggle-header.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/home-page/toggle-header.component.ts index e797e10061..b6bb189f15 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/home-page/toggle-header.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/home-page/toggle-header.component.ts @@ -19,9 +19,9 @@ import { AfterViewInit, ChangeDetectorRef, Component, - ContentChildren, + ContentChildren, EventEmitter, Input, - OnInit, + OnInit, Output, QueryList, ViewChild } from '@angular/core'; @@ -54,6 +54,9 @@ export class ToggleHeaderComponent extends PageComponent implements OnInit { @Input() value: any; + @Output() + valueChange = new EventEmitter(); + @Input() options: ToggleHeaderOption[]; diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-routing.module.ts b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-routing.module.ts index 6b85ade8de..17686602a3 100644 --- a/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-routing.module.ts +++ b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-routing.module.ts @@ -22,22 +22,29 @@ import { Authority } from '@shared/models/authority.enum'; import { DashboardsTableConfigResolver } from './dashboards-table-config.resolver'; import { DashboardPageComponent } from '@home/components/dashboard-page/dashboard-page.component'; import { BreadCrumbConfig, BreadCrumbLabelFunction } from '@shared/components/breadcrumb'; -import { Observable } from 'rxjs'; +import { mergeMap, Observable } from 'rxjs'; import { Dashboard } from '@app/shared/models/dashboard.models'; import { DashboardService } from '@core/http/dashboard.service'; import { DashboardUtilsService } from '@core/services/dashboard-utils.service'; import { map } from 'rxjs/operators'; +import { UserSettingsService } from '@core/http/user-settings.service'; +import { UserDashboardAction } from '@shared/models/user-settings.models'; @Injectable() export class DashboardResolver implements Resolve { constructor(private dashboardService: DashboardService, + private userSettingService: UserSettingsService, private dashboardUtils: DashboardUtilsService) { } resolve(route: ActivatedRouteSnapshot): Observable { const dashboardId = route.params.dashboardId; return this.dashboardService.getDashboard(dashboardId).pipe( + mergeMap((dashboard) => + this.userSettingService.reportUserDashboardAction(dashboardId, UserDashboardAction.VISIT, {ignoreLoading: true}).pipe( + map(() => dashboard) + )), map((dashboard) => this.dashboardUtils.validateAndUpdateDashboard(dashboard)) ); } diff --git a/ui-ngx/src/app/modules/home/pages/home-links/tenant_admin_home_page.raw b/ui-ngx/src/app/modules/home/pages/home-links/tenant_admin_home_page.raw index 074269c742..5679671ab4 100644 --- a/ui-ngx/src/app/modules/home/pages/home-links/tenant_admin_home_page.raw +++ b/ui-ngx/src/app/modules/home/pages/home-links/tenant_admin_home_page.raw @@ -508,6 +508,36 @@ "row": 0, "col": 0, "id": "9e3ef045-d8bc-1640-a3f4-2dd10b19d50e" + }, + "6d6e2b1d-6ce7-4678-3745-c6b0897b2674": { + "isSystemType": true, + "bundleAlias": "home_page_widgets", + "typeAlias": "dashboards", + "type": "static", + "title": "New widget", + "image": null, + "description": null, + "sizeX": 7.5, + "sizeY": 6.5, + "config": { + "showTitle": false, + "backgroundColor": "rgb(255, 255, 255)", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "16px", + "settings": {}, + "title": "Dashboards", + "dropShadow": true, + "enableFullscreen": false, + "widgetStyle": {}, + "widgetCss": "", + "pageSize": 1024, + "noDataDisplayMessage": "", + "showLegend": false, + "datasources": [] + }, + "row": 0, + "col": 0, + "id": "6d6e2b1d-6ce7-4678-3745-c6b0897b2674" } }, "states": { @@ -519,8 +549,8 @@ "widgets": { "d70cc256-4c7b-ee06-9905-b8c5e546605f": { "sizeX": 41, - "sizeY": 16, - "row": 26, + "sizeY": 29, + "row": 13, "col": 44, "mobileOrder": 7, "mobileHeight": 8 @@ -550,6 +580,12 @@ "sizeY": 16, "row": 42, "col": 0 + }, + "6d6e2b1d-6ce7-4678-3745-c6b0897b2674": { + "sizeX": 44, + "sizeY": 29, + "row": 13, + "col": 0 } }, "gridSettings": { diff --git a/ui-ngx/src/app/shared/components/dashboard-autocomplete.component.html b/ui-ngx/src/app/shared/components/dashboard-autocomplete.component.html index af62ca189a..b8783470db 100644 --- a/ui-ngx/src/app/shared/components/dashboard-autocomplete.component.html +++ b/ui-ngx/src/app/shared/components/dashboard-autocomplete.component.html @@ -15,7 +15,8 @@ limitations under the License. --> - + {{ label }} ; + starred: Array; +} + +export enum UserDashboardAction { + VISIT = 'VISIT', + STAR = 'STAR', + UNSTAR = 'UNSTAR' +} diff --git a/ui-ngx/src/app/shared/pipe/date-ago.pipe.ts b/ui-ngx/src/app/shared/pipe/date-ago.pipe.ts index c0f4b5c026..4fb964eae8 100644 --- a/ui-ngx/src/app/shared/pipe/date-ago.pipe.ts +++ b/ui-ngx/src/app/shared/pipe/date-ago.pipe.ts @@ -37,18 +37,23 @@ export class DateAgoPipe implements PipeTransform { } - transform(value: number): string { + transform(value: number, args?: any): string { if (value) { + const applyAgo = !!args?.applyAgo; const ms = Math.floor((+new Date() - +new Date(value))); if (ms < 29 * SECOND) { // less than 30 seconds ago will show as 'Just now' return this.translate.instant('timewindow.just-now'); } let counter; - // tslint:disable-next-line:forin + // eslint-disable-next-line guard-for-in for (const i in intervals) { counter = Math.floor(ms / intervals[i]); if (counter > 0) { - return this.translate.instant(`timewindow.${i}`, {[i]: counter}); + let res = this.translate.instant(`timewindow.${i}`, {[i]: counter}); + if (applyAgo) { + res += ' ' + this.translate.instant('timewindow.ago'); + } + return res; } } } diff --git a/ui-ngx/src/assets/home/no_data_bg.svg b/ui-ngx/src/assets/home/no_data_bg.svg new file mode 100644 index 0000000000..3ad0285c44 --- /dev/null +++ b/ui-ngx/src/assets/home/no_data_bg.svg @@ -0,0 +1,3 @@ + + + diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 28c097121f..86104c961b 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -3739,7 +3739,8 @@ "time-period": "Time period", "hide": "Hide", "interval": "Interval", - "just-now": "Just now" + "just-now": "Just now", + "ago": "ago" }, "user": { "user": "User", @@ -5152,6 +5153,14 @@ "no-links-matching": "No links matching '{{name}}' were found.", "columns": "Columns" }, + "recent-dashboards": { + "title": "Dashboards", + "last": "Last viewed", + "starred": "Starred", + "name": "Name", + "last-viewed": "Last viewed", + "no-last-viewed-dashboards": "No last viewed dashboards yet" + }, "configured-features": { "title": "Configured features", "info": "Status of features that require configuration",