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 new file mode 100644 index 0000000000..f84d4cbbc6 --- /dev/null +++ b/application/src/main/data/json/system/widget_bundles/home_page_widgets.json @@ -0,0 +1,31 @@ +{ + "widgetsBundle": { + "alias": "home_page_widgets", + "title": "Home page widgets", + "image": null, + "description": null, + "externalId": null, + "name": "Home page widgets" + }, + "widgetTypes": [ + { + "alias": "documentation_links", + "name": "Documentation links", + "image": null, + "description": null, + "descriptor": { + "type": "static", + "sizeX": 7.5, + "sizeY": 3, + "resources": [], + "templateHtml": "\n\n", + "templateCss": "", + "controllerScript": "self.onInit = function() {\n}", + "settingsSchema": "", + "dataKeySettingsSchema": "", + "settingsDirective": "tb-doc-links-widget-settings", + "defaultConfig": "{\"datasources\":[{\"type\":\"static\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"columns\":3},\"title\":\"Documentation links\",\"dropShadow\":true}" + } + } + ] +} \ No newline at end of file diff --git a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java index bd68aad316..f1183bea8b 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java @@ -508,6 +508,7 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { this.deleteSystemWidgetBundle("entity_admin_widgets"); this.deleteSystemWidgetBundle("navigation_widgets"); this.deleteSystemWidgetBundle("edge_widgets"); + this.deleteSystemWidgetBundle("home_page_widgets"); installScripts.loadSystemWidgets(); } diff --git a/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/NewPlatformVersionTriggerProcessor.java b/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/NewPlatformVersionTriggerProcessor.java index 030409d014..699ef8b312 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/NewPlatformVersionTriggerProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/NewPlatformVersionTriggerProcessor.java @@ -15,8 +15,10 @@ */ package org.thingsboard.server.service.notification.rule.trigger; +import com.fasterxml.jackson.core.type.TypeReference; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.notification.info.NewPlatformVersionNotificationInfo; import org.thingsboard.server.common.data.notification.info.NotificationInfo; @@ -44,7 +46,7 @@ public class NewPlatformVersionTriggerProcessor implements NotificationRuleTrigg @Override public NotificationInfo constructNotificationInfo(NewPlatformVersionTrigger trigger, NewPlatformVersionNotificationRuleTriggerConfig triggerConfig) { return NewPlatformVersionNotificationInfo.builder() - .message("New version available - " + trigger.getMessage().getLatestVersion()) + .message(JacksonUtil.convertValue(trigger.getMessage(), new TypeReference<>() {})) .build(); } diff --git a/application/src/main/java/org/thingsboard/server/service/update/DefaultUpdateService.java b/application/src/main/java/org/thingsboard/server/service/update/DefaultUpdateService.java index fc1288f561..536f836a50 100644 --- a/application/src/main/java/org/thingsboard/server/service/update/DefaultUpdateService.java +++ b/application/src/main/java/org/thingsboard/server/service/update/DefaultUpdateService.java @@ -15,7 +15,6 @@ */ package org.thingsboard.server.service.update; -import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import lombok.extern.slf4j.Slf4j; @@ -131,16 +130,8 @@ public class DefaultUpdateService implements UpdateService { request.put(PLATFORM_PARAM, platform); request.put(VERSION_PARAM, version); request.put(INSTANCE_ID_PARAM, instanceId.toString()); - JsonNode response = restClient.postForObject(UPDATE_SERVER_BASE_URL + "/api/thingsboard/updates", request, JsonNode.class); UpdateMessage prevUpdateMessage = updateMessage; - updateMessage = new UpdateMessage( - response.get("updateAvailable").asBoolean(), - version, - "3.5.0", - "https://thingsboard.io/docs/user-guide/install/upgrade-instructions", - "https://thingsboard.io/docs/reference/releases", - "https://thingsboard.io/docs/reference/releases" - ); + updateMessage = restClient.postForObject(UPDATE_SERVER_BASE_URL + "/api/v2/thingsboard/updates", request, UpdateMessage.class); if (updateMessage.isUpdateAvailable() && !updateMessage.equals(prevUpdateMessage)) { notificationRuleProcessingService.process(TenantId.SYS_TENANT_ID, NewPlatformVersionTrigger.builder() .message(updateMessage) diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/NewPlatformVersionNotificationInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/NewPlatformVersionNotificationInfo.java index 0e85d95fd0..2dd9ae7e11 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/NewPlatformVersionNotificationInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/NewPlatformVersionNotificationInfo.java @@ -19,6 +19,7 @@ import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; +import org.thingsboard.server.common.data.UpdateMessage; import java.util.Map; @@ -30,13 +31,11 @@ import static org.thingsboard.server.common.data.util.CollectionsUtil.mapOf; @Builder public class NewPlatformVersionNotificationInfo implements NotificationInfo { - private String message; + private Map message; @Override public Map getTemplateData() { - return mapOf( - "message", message - ); + return message; } } 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 8aed9d1345..82bc726f72 100644 --- a/ui-ngx/src/app/core/http/user-settings.service.ts +++ b/ui-ngx/src/app/core/http/user-settings.service.ts @@ -16,10 +16,15 @@ import { Injectable } from '@angular/core'; import { Observable } from 'rxjs'; -import { initialUserSettings, UserSettings } from '@shared/models/user-settings.models'; +import { + DocumentationLink, DocumentationLinks, + initialUserSettings, + UserSettings, + UserSettingsType +} from '@shared/models/user-settings.models'; import { map } from 'rxjs/operators'; import { HttpClient } from '@angular/common/http'; -import { defaultHttpOptionsFromConfig } from '@core/http/http-utils'; +import { defaultHttpOptionsFromConfig, RequestConfig } from '@core/http/http-utils'; @Injectable({ providedIn: 'root' @@ -51,4 +56,14 @@ export class UserSettingsService { defaultHttpOptionsFromConfig({ignoreLoading: true, ignoreErrors: true})); } + public getDocumentationLinks(config?: RequestConfig): Observable { + return this.http.get(`/api/user/settings/${UserSettingsType.DOC_LINKS}`, + defaultHttpOptionsFromConfig(config)); + } + + public updateDocumentationLinks(documentationLinks: DocumentationLinks, config?: RequestConfig): Observable { + return this.http.put(`/api/user/settings/${UserSettingsType.DOC_LINKS}`, documentationLinks, + defaultHttpOptionsFromConfig(config)); + } + } 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 index 804e61d229..937955b12b 100644 --- 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 @@ -22,8 +22,8 @@ matTooltipPosition="above" class="tb-info-icon">info -
-
+ +
Email
+
+
SMS
-
- -
+ + 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 index 5dab5e20bb..60711bd6a0 100644 --- 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 @@ -37,8 +37,12 @@ vertical-align: middle; } + .mat-grid-tile.tb-feature-tile { + overflow: visible; + } + .tb-feature-button { - flex: 1; + height: 100%; display: flex; flex-direction: column; justify-content: center; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/home-page/doc-links-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/home-page/doc-links-widget.component.html new file mode 100644 index 0000000000..139316f0d5 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/home-page/doc-links-widget.component.html @@ -0,0 +1,50 @@ + + diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/home-page/doc-links-widget.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/home-page/doc-links-widget.component.scss new file mode 100644 index 0000000000..9a0c4b618f --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/home-page/doc-links-widget.component.scss @@ -0,0 +1,124 @@ +/** + * 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-link { + font-style: normal; + font-weight: 500; + font-size: 14px; + line-height: 20px; + letter-spacing: 0.25px; + color: rgba(0, 0, 0, 0.54); + 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-title-icon { + color: rgba(0, 0, 0, 0.38); + width: 32px; + height: 32px; + padding: 4px; + } + + .mat-grid-tile.tb-docs-tile { + overflow: visible; + } + + .tb-doc-button { + height: 100%; + 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-doc-container { + display: flex; + flex-direction: row; + align-items: center; + .tb-doc-icon-container { + height: 40px; + padding: 8px; + background: #F3F6FA; + border-radius: 6px; + margin-right: 8px; + } + .tb-doc-text { + font-weight: 400; + font-size: 14px; + line-height: 20px; + letter-spacing: 0.2px; + color: rgba(0, 0, 0, 0.87); + } + } + } + + .tb-add-doc-button { + height: 100%; + cursor: pointer; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + background: #FFFFFF; + padding: 12px; + border: 2px dashed rgba(0, 0, 0, 0.08); + border-radius: 10px; + .tb-add-icon { + color: rgba(0, 0, 0, 0.12); + } + &:hover { + .tb-add-icon { + color: rgba(0, 0, 0, 0.38); + } + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/home-page/doc-links-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/home-page/doc-links-widget.component.ts new file mode 100644 index 0000000000..5b4ab7556f --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/home-page/doc-links-widget.component.ts @@ -0,0 +1,136 @@ +/// +/// 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, Input, OnInit } 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 { map } from 'rxjs'; +import { DocumentationLinks } from '@shared/models/user-settings.models'; +import { UserSettingsService } from '@core/http/user-settings.service'; +import { getCurrentAuthUser } from '@core/auth/auth.selectors'; +import { WidgetContext } from '@home/models/widget-component.models'; + +const defaultDocLinksMap = new Map( + [ + [Authority.SYS_ADMIN, { + links: [ + { + icon: 'rocket', + name: 'Getting started', + link: 'https://thingsboard.io/docs/getting-started-guides/helloworld/' + }, + { + icon: 'title', + name: 'Tenant profiles', + link: 'https://thingsboard.io/docs/user-guide/tenant-profiles/' + }, + { + icon: 'insert_chart', + name: 'API', + link: 'https://thingsboard.io/docs/api/' + }, + { + icon: 'now_widgets', + name: 'Widgets Library', + link: 'https://thingsboard.io/docs/user-guide/ui/widget-library/' + } + ] + }], + [Authority.TENANT_ADMIN, { + links: [ + { + icon: 'rocket', + name: 'Getting started', + link: 'https://thingsboard.io/docs/getting-started-guides/helloworld/' + }, + { + icon: 'settings_ethernet', + name: 'Rule engine', + link: 'https://thingsboard.io/docs/user-guide/rule-engine-2-0/re-getting-started/' + }, + { + icon: 'insert_chart', + name: 'API', + link: 'https://thingsboard.io/docs/api/' + }, + { + icon: 'devices', + name: 'Device profiles', + link: 'https://thingsboard.io/docs/user-guide/device-profiles/' + } + ] + }], + [Authority.CUSTOMER_USER, { + links: [] + }] + ] +); + +interface DocLinksWidgetSettings { + columns: number; +} + +@Component({ + selector: 'tb-doc-links-widget', + templateUrl: './doc-links-widget.component.html', + styleUrls: ['./doc-links-widget.component.scss'] +}) +export class DocLinksWidgetComponent extends PageComponent implements OnInit { + + @Input() + ctx: WidgetContext; + + settings: DocLinksWidgetSettings; + columns: number; + + documentationLinks: DocumentationLinks; + authUser = getCurrentAuthUser(this.store); + + constructor(protected store: Store, + private cd: ChangeDetectorRef, + private userSettingsService: UserSettingsService) { + super(store); + } + + ngOnInit() { + this.settings = this.ctx.settings; + this.columns = this.settings.columns || 3; + this.userSettingsService.getDocumentationLinks().pipe( + map((documentationLinks) => { + if (!documentationLinks || !documentationLinks.links) { + return defaultDocLinksMap.get(this.authUser.authority); + } else { + return documentationLinks; + } + }) + ).subscribe( + (documentationLinks) => { + this.documentationLinks = documentationLinks; + this.cd.markForCheck(); + } + ); + } + + edit() { + + } + + addLink() { + + } +} 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 6aeb9710fd..feea87a664 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 @@ -20,13 +20,15 @@ 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'; +import { DocLinksWidgetComponent } from '@home/components/widget/lib/home-page/doc-links-widget.component'; @NgModule({ declarations: [ ClusterInfoTableComponent, ConfiguredFeaturesComponent, - VersionInfoComponent + VersionInfoComponent, + DocLinksWidgetComponent ], imports: [ CommonModule, @@ -35,7 +37,8 @@ import { VersionInfoComponent } from '@home/components/widget/lib/home-page/vers exports: [ ClusterInfoTableComponent, ConfiguredFeaturesComponent, - VersionInfoComponent + VersionInfoComponent, + DocLinksWidgetComponent ] }) export class HomePageWidgetsModule { } 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 index 25dc0fde78..27acef15f4 100644 --- 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 @@ -49,12 +49,4 @@ export class VersionInfoComponent extends PageComponent implements OnInit { } ); } - - 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/settings/home-page/doc-links-widget-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/home-page/doc-links-widget-settings.component.html new file mode 100644 index 0000000000..025beb45dc --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/home-page/doc-links-widget-settings.component.html @@ -0,0 +1,23 @@ + +
+ + Columns + + +
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/home-page/doc-links-widget-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/home-page/doc-links-widget-settings.component.ts new file mode 100644 index 0000000000..7e0205a1e2 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/home-page/doc-links-widget-settings.component.ts @@ -0,0 +1,52 @@ +/// +/// 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 { Component } from '@angular/core'; +import { WidgetSettings, WidgetSettingsComponent } from '@shared/models/widget.models'; +import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; + +@Component({ + selector: 'tb-doc-links-widget-settings', + templateUrl: './doc-links-widget-settings.component.html', + styleUrls: ['./../widget-settings.scss'] +}) +export class DocLinksWidgetSettingsComponent extends WidgetSettingsComponent { + + docLinksWidgetSettingsForm: UntypedFormGroup; + + constructor(protected store: Store, + private fb: UntypedFormBuilder) { + super(store); + } + + protected settingsForm(): UntypedFormGroup { + return this.docLinksWidgetSettingsForm; + } + + protected defaultSettings(): WidgetSettings { + return { + columns: 3 + }; + } + + protected onSettingsSet(settings: WidgetSettings) { + this.docLinksWidgetSettingsForm = this.fb.group({ + columns: [settings.columns, [Validators.required, Validators.min(1), Validators.max(20)]] + }); + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/widget-settings.module.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/widget-settings.module.ts index ef7668f306..73ff1433a9 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/widget-settings.module.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/widget-settings.module.ts @@ -259,6 +259,9 @@ import { import { TripAnimationPointSettingsComponent } from '@home/components/widget/lib/settings/map/trip-animation-point-settings.component'; +import { + DocLinksWidgetSettingsComponent +} from '@home/components/widget/lib/settings/home-page/doc-links-widget-settings.component'; @NgModule({ declarations: [ @@ -357,7 +360,8 @@ import { TripAnimationPointSettingsComponent, MapWidgetSettingsComponent, RouteMapWidgetSettingsComponent, - TripAnimationWidgetSettingsComponent + TripAnimationWidgetSettingsComponent, + DocLinksWidgetSettingsComponent ], imports: [ CommonModule, @@ -460,7 +464,8 @@ import { TripAnimationPointSettingsComponent, MapWidgetSettingsComponent, RouteMapWidgetSettingsComponent, - TripAnimationWidgetSettingsComponent + TripAnimationWidgetSettingsComponent, + DocLinksWidgetSettingsComponent ] }) export class WidgetSettingsModule { @@ -527,5 +532,6 @@ export const widgetSettingsComponentsMap: {[key: string]: Type