diff --git a/application/src/main/java/org/thingsboard/server/service/system/DefaultSystemInfoService.java b/application/src/main/java/org/thingsboard/server/service/system/DefaultSystemInfoService.java index a2e9f0a72a..245cd72d6e 100644 --- a/application/src/main/java/org/thingsboard/server/service/system/DefaultSystemInfoService.java +++ b/application/src/main/java/org/thingsboard/server/service/system/DefaultSystemInfoService.java @@ -26,6 +26,7 @@ import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.server.common.data.AdminSettings; import org.thingsboard.server.common.data.ApiUsageState; import org.thingsboard.server.common.data.FeaturesInfo; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.SystemInfo; import org.thingsboard.server.common.data.SystemInfoData; import org.thingsboard.server.common.data.id.TenantId; @@ -145,9 +146,7 @@ public class DefaultSystemInfoService extends TbApplicationEventListener { - if (updateMessage && updateMessage.updateAvailable) { - this.store.dispatch(new ActionNotificationShow( - {message: updateMessage.message, - type: 'info', - verticalPosition: 'bottom', - horizontalPosition: 'right'})); - } - }); } } } else { diff --git a/ui-ngx/src/app/core/http/admin.service.ts b/ui-ngx/src/app/core/http/admin.service.ts index 6874d21d74..cd83c3c8c9 100644 --- a/ui-ngx/src/app/core/http/admin.service.ts +++ b/ui-ngx/src/app/core/http/admin.service.ts @@ -21,6 +21,7 @@ import { HttpClient } from '@angular/common/http'; import { AdminSettings, AutoCommitSettings, + FeaturesInfo, JwtSettings, MailServerSettings, RepositorySettings, @@ -131,4 +132,8 @@ export class AdminService { public checkUpdates(config?: RequestConfig): Observable { return this.http.get(`/api/admin/updates`, defaultHttpOptionsFromConfig(config)); } + + public getFeaturesInfo(config?: RequestConfig): Observable { + return this.http.get('/api/admin/featuresInfo', defaultHttpOptionsFromConfig(config)); + } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/home-page/cluster-info-table.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/home-page/cluster-info-table.component.ts index 527612ccc0..b5695a113a 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/home-page/cluster-info-table.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/home-page/cluster-info-table.component.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { AfterViewInit, ChangeDetectionStrategy, Component, NgZone, OnInit, ViewChild } from '@angular/core'; +import { AfterViewInit, Component, NgZone, OnInit, ViewChild } from '@angular/core'; import { PageComponent } from '@shared/components/page.component'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; @@ -44,8 +44,7 @@ export interface SystemInfoData { @Component({ selector: 'tb-cluster-info-table', templateUrl: './cluster-info-table.component.html', - styleUrls: ['./cluster-info-table.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush + styleUrls: ['./cluster-info-table.component.scss'] }) export class ClusterInfoTableComponent extends PageComponent implements OnInit, AfterViewInit { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/home-page/configured-features.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/home-page/configured-features.component.html new file mode 100644 index 0000000000..804e61d229 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/home-page/configured-features.component.html @@ -0,0 +1,66 @@ + +
+
Configured features + info +
+ +
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/home-page/configured-features.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/home-page/configured-features.component.scss new file mode 100644 index 0000000000..5dab5e20bb --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/home-page/configured-features.component.scss @@ -0,0 +1,85 @@ +/** + * 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. + */ +:host { + .tb-card-content { + width: 100%; + height: 100%; + } + + .tb-title { + font-style: normal; + font-weight: 500; + font-size: 14px; + line-height: 20px; + letter-spacing: 0.25px; + color: rgba(0, 0, 0, 0.54); + } + + .tb-info-icon { + color: rgba(0, 0, 0, 0.12); + font-size: 16px; + width: 16px; + height: 16px; + line-height: 15px; + vertical-align: middle; + } + + .tb-feature-button { + flex: 1; + display: flex; + flex-direction: column; + justify-content: center; + padding: 12px; + background: #FFFFFF; + border: 1px solid rgba(0, 0, 0, 0.05); + box-shadow: 0 5px 16px rgba(0, 0, 0, 0.04); + border-radius: 10px; + &:hover { + border: 1px solid rgba(0, 0, 0, 0.12); + box-shadow: 0 4px 10px rgba(23, 33, 90, 0.08); + } + .tb-feature-text { + font-weight: 400; + font-size: 14px; + line-height: 20px; + color: rgba(0, 0, 0, 0.87); + &:before { + content: "close"; + font-family: 'Material Icons Round'; + font-size: 20px; + line-height: 1; + color: rgba(0, 0, 0, 0.12); + font-weight: 600; + position: relative; + background: #F4F4F4; + border-radius: 4px; + width: 24px; + height: 24px; + margin-right: 8px; + vertical-align: bottom; + } + } + &.configured { + .tb-feature-text { + &:before { + content: "check"; + color: #198038; + background: #F3F6FA; + } + } + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/home-page/configured-features.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/home-page/configured-features.component.ts new file mode 100644 index 0000000000..aba469b86c --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/home-page/configured-features.component.ts @@ -0,0 +1,60 @@ +/// +/// 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 { ChangeDetectorRef, Component, OnInit } from '@angular/core'; +import { PageComponent } from '@shared/components/page.component'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { AdminService } from '@core/http/admin.service'; +import { FeaturesInfo } from '@shared/models/settings.models'; +import { getCurrentAuthUser } from '@core/auth/auth.selectors'; +import { Authority } from '@shared/models/authority.enum'; +import { of } from 'rxjs'; + +@Component({ + selector: 'tb-configured-features', + templateUrl: './configured-features.component.html', + styleUrls: ['./configured-features.component.scss'] +}) +export class ConfiguredFeaturesComponent extends PageComponent implements OnInit { + + authUser = getCurrentAuthUser(this.store); + featuresInfo: FeaturesInfo; + + constructor(protected store: Store, + private cd: ChangeDetectorRef, + private adminService: AdminService) { + super(store); + } + + ngOnInit() { + (this.authUser.authority === Authority.SYS_ADMIN ? + this.adminService.getFeaturesInfo() : of(null)).subscribe( + (featuresInfo) => { + this.featuresInfo = featuresInfo; + this.cd.markForCheck(); + } + ); + } + + featureTooltip(configured: boolean): string { + if (configured) { + return 'Feature is configured.\nClick to setup'; + } else { + return 'Feature is not configured.\nClick to setup'; + } + } +} 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 0e14236720..6aeb9710fd 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 @@ -18,18 +18,24 @@ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { SharedModule } from '@app/shared/shared.module'; import { ClusterInfoTableComponent } from '@home/components/widget/lib/home-page/cluster-info-table.component'; +import { ConfiguredFeaturesComponent } from '@home/components/widget/lib/home-page/configured-features.component'; +import { VersionInfoComponent } from '@home/components/widget/lib/home-page/version-info.component'; @NgModule({ declarations: [ - ClusterInfoTableComponent + ClusterInfoTableComponent, + ConfiguredFeaturesComponent, + VersionInfoComponent ], imports: [ CommonModule, SharedModule ], exports: [ - ClusterInfoTableComponent + ClusterInfoTableComponent, + ConfiguredFeaturesComponent, + VersionInfoComponent ] }) export class HomePageWidgetsModule { } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/home-page/version-info.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/home-page/version-info.component.html new file mode 100644 index 0000000000..54eee0db70 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/home-page/version-info.component.html @@ -0,0 +1,44 @@ + +
+
+
Version
+ Contact us +
+
+
+ +
{{ updateMessage?.currentVersion }}
+
+
+ +
{{ updateMessage?.latestVersion }}
+
+
+
+ +
+
check
+
Version is up to date
+
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/home-page/version-info.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/home-page/version-info.component.scss new file mode 100644 index 0000000000..823dfb5baf --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/home-page/version-info.component.scss @@ -0,0 +1,102 @@ +/** + * 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. + */ +:host { + .tb-card-content { + width: 100%; + height: 100%; + } + + .tb-card-header { + display: flex; + flex-direction: row; + justify-content: space-between; + } + + .tb-title { + font-style: normal; + font-weight: 500; + font-size: 14px; + line-height: 20px; + letter-spacing: 0.25px; + color: rgba(0, 0, 0, 0.54); + } + + .tb-version-info-container { + border: 1px solid rgba(0, 0, 0, 0.05); + box-shadow: 0 5px 16px rgba(0, 0, 0, 0.04); + border-radius: 10px; + padding: 16px; + display: flex; + flex-direction: column; + justify-content: space-evenly; + &.up-to-date { + box-shadow: none; + border: none; + align-items: center; + background: #F3F6FA; + } + } + + .tb-version-link { + font-weight: 400; + font-size: 14px; + line-height: 20px; + letter-spacing: 0.2px; + color: rgba(0, 0, 0, 0.76); + position: relative; + border-bottom: none; + &:hover, &:focus { + border-bottom: none; + } + &::after { + content: 'arrow_forward'; + display: inline-block; + position: absolute; + top: -2px; + right: -28px; + transform: rotate(315deg); + font-family: 'Material Icons'; + font-weight: normal; + font-style: normal; + font-size: 18px; + color: rgba(0, 0, 0, 0.12); + } + &:hover::after { + color: inherit; + } + } + + .tb-version { + font-weight: 500; + font-size: 18px; + line-height: 24px; + letter-spacing: 0.15px; + color: rgba(0, 0, 0, 0.87); + } + + .check-icon { + color: #198038; + font-weight: 600; + } + + .up-to-date-text { + font-weight: 400; + font-size: 14px; + line-height: 20px; + letter-spacing: 0.2px; + color: rgba(0, 0, 0, 0.76); + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/home-page/version-info.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/home-page/version-info.component.ts new file mode 100644 index 0000000000..25dc0fde78 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/home-page/version-info.component.ts @@ -0,0 +1,60 @@ +/// +/// 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 { ChangeDetectorRef, Component, OnInit } from '@angular/core'; +import { PageComponent } from '@shared/components/page.component'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { AdminService } from '@core/http/admin.service'; +import { UpdateMessage } from '@shared/models/settings.models'; +import { getCurrentAuthUser } from '@core/auth/auth.selectors'; +import { Authority } from '@shared/models/authority.enum'; +import { of } from 'rxjs'; + +@Component({ + selector: 'tb-version-info', + templateUrl: './version-info.component.html', + styleUrls: ['./version-info.component.scss'] +}) +export class VersionInfoComponent extends PageComponent implements OnInit { + + authUser = getCurrentAuthUser(this.store); + updateMessage: UpdateMessage; + + constructor(protected store: Store, + private cd: ChangeDetectorRef, + private adminService: AdminService) { + super(store); + } + + ngOnInit() { + (this.authUser.authority === Authority.SYS_ADMIN ? + this.adminService.checkUpdates() : of(null)).subscribe( + (updateMessage) => { + this.updateMessage = updateMessage; + this.cd.markForCheck(); + } + ); + } + + featureTooltip(configured: boolean): string { + if (configured) { + return 'Feature is configured.\nClick to setup'; + } else { + return 'Feature is not configured.\nClick to setup'; + } + } +} diff --git a/ui-ngx/src/app/modules/home/pages/home-links/sys_admin_home_page.raw b/ui-ngx/src/app/modules/home/pages/home-links/sys_admin_home_page.raw index 4aaa1d7d81..4e75dbcfd7 100644 --- a/ui-ngx/src/app/modules/home/pages/home-links/sys_admin_home_page.raw +++ b/ui-ngx/src/app/modules/home/pages/home-links/sys_admin_home_page.raw @@ -1561,30 +1561,7 @@ "sizeX": 5, "sizeY": 3.5, "config": { - "datasources": [ - { - "type": "entityCount", - "name": null, - "entityAliasId": "ae870700-071f-b3bc-406c-16ba554c5a55", - "filterId": null, - "dataKeys": [ - { - "name": "count", - "type": "count", - "label": "tenantProfilesCount", - "color": "#2196f3", - "settings": {}, - "_hash": 0.8491768696709192, - "aggregationType": null, - "units": null, - "decimals": null, - "funcBody": null, - "usePostProcessing": null, - "postFuncBody": null - } - ] - } - ], + "datasources": [], "timewindow": { "displayValue": "", "selectedTab": 0, @@ -1640,6 +1617,140 @@ "row": 0, "col": 0, "id": "8acbf5df-f9fc-114d-216f-86f081aa4779" + }, + "d2784f6c-0518-fd95-1d28-b21f70bdcb10": { + "isSystemType": true, + "bundleAlias": "cards", + "typeAlias": "markdown_card", + "type": "latest", + "title": "New widget", + "image": null, + "description": null, + "sizeX": 5, + "sizeY": 3.5, + "config": { + "datasources": [], + "timewindow": { + "displayValue": "", + "selectedTab": 0, + "realtime": { + "realtimeType": 1, + "interval": 1000, + "timewindowMs": 60000, + "quickInterval": "CURRENT_DAY" + }, + "history": { + "historyType": 0, + "interval": 1000, + "timewindowMs": 60000, + "fixedTimewindow": { + "startTimeMs": 1680168340431, + "endTimeMs": 1680254740431 + }, + "quickInterval": "CURRENT_DAY" + }, + "aggregation": { + "type": "AVG", + "limit": 25000 + } + }, + "showTitle": false, + "backgroundColor": "#fff", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "16px", + "settings": { + "useMarkdownTextFunction": false, + "markdownTextPattern": "
\n \n
", + "applyDefaultMarkdownStyle": false + }, + "title": "Configured features", + "showTitleIcon": false, + "iconColor": "rgba(0, 0, 0, 0.87)", + "iconSize": "24px", + "titleTooltip": "", + "dropShadow": false, + "enableFullscreen": false, + "widgetStyle": {}, + "titleStyle": { + "fontSize": "16px", + "fontWeight": 400 + }, + "showLegend": false, + "useDashboardTimewindow": true, + "widgetCss": "", + "pageSize": 1024, + "noDataDisplayMessage": "" + }, + "row": 0, + "col": 0, + "id": "d2784f6c-0518-fd95-1d28-b21f70bdcb10" + }, + "66dcf1a2-9d83-6873-c693-d6a5d989b3f8": { + "isSystemType": true, + "bundleAlias": "cards", + "typeAlias": "markdown_card", + "type": "latest", + "title": "New widget", + "image": null, + "description": null, + "sizeX": 5, + "sizeY": 3.5, + "config": { + "datasources": [], + "timewindow": { + "displayValue": "", + "selectedTab": 0, + "realtime": { + "realtimeType": 1, + "interval": 1000, + "timewindowMs": 60000, + "quickInterval": "CURRENT_DAY" + }, + "history": { + "historyType": 0, + "interval": 1000, + "timewindowMs": 60000, + "fixedTimewindow": { + "startTimeMs": 1680168340431, + "endTimeMs": 1680254740431 + }, + "quickInterval": "CURRENT_DAY" + }, + "aggregation": { + "type": "AVG", + "limit": 25000 + } + }, + "showTitle": false, + "backgroundColor": "#fff", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "16px", + "settings": { + "useMarkdownTextFunction": false, + "markdownTextPattern": "
\n \n
", + "applyDefaultMarkdownStyle": false + }, + "title": "Version", + "showTitleIcon": false, + "iconColor": "rgba(0, 0, 0, 0.87)", + "iconSize": "24px", + "titleTooltip": "", + "dropShadow": false, + "enableFullscreen": false, + "widgetStyle": {}, + "titleStyle": { + "fontSize": "16px", + "fontWeight": 400 + }, + "showLegend": false, + "useDashboardTimewindow": true, + "widgetCss": "", + "pageSize": 1024, + "noDataDisplayMessage": "" + }, + "row": 0, + "col": 0, + "id": "66dcf1a2-9d83-6873-c693-d6a5d989b3f8" } }, "states": { @@ -1708,6 +1819,18 @@ "sizeY": 7, "row": 19, "col": 11 + }, + "d2784f6c-0518-fd95-1d28-b21f70bdcb10": { + "sizeX": 20, + "sizeY": 7, + "row": 12, + "col": 0 + }, + "66dcf1a2-9d83-6873-c693-d6a5d989b3f8": { + "sizeX": 11, + "sizeY": 7, + "row": 19, + "col": 0 } }, "gridSettings": { @@ -1810,7 +1933,7 @@ "filter": { "type": "entityType", "resolveMultiple": true, - "entityType": "TENANT" + "entityType": "TENANT_PROFILE" } }, "a1ddb8fa-90ff-5598-e7f2-e254194d055d": { @@ -1908,4 +2031,4 @@ }, "externalId": null, "name": "System Administrator Home Page" -} \ No newline at end of file +} diff --git a/ui-ngx/src/app/shared/models/settings.models.ts b/ui-ngx/src/app/shared/models/settings.models.ts index a673e0cacc..0cb02d6257 100644 --- a/ui-ngx/src/app/shared/models/settings.models.ts +++ b/ui-ngx/src/app/shared/models/settings.models.ts @@ -71,8 +71,12 @@ export interface JwtSettings { } export interface UpdateMessage { - message: string; updateAvailable: boolean; + currentVersion: string; + latestVersion: string; + upgradeInstructionsUrl: string; + currentVersionReleaseNotesUrl: string; + latestVersionReleaseNotesUrl: string; } export const phoneNumberPattern = /^\+[1-9]\d{1,14}$/; @@ -437,3 +441,11 @@ export interface AutoVersionCreateConfig extends VersionCreateConfig { } export type AutoCommitSettings = {[entityType: string]: AutoVersionCreateConfig}; + +export interface FeaturesInfo { + emailEnabled: boolean; + smsEnabled: boolean; + notificationEnabled: boolean; + oauthEnabled: boolean; + twoFaEnabled: boolean; +}