diff --git a/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-browse.component.ts b/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-browse.component.ts
index 4a9418eec8..083b858c70 100644
--- a/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-browse.component.ts
+++ b/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-browse.component.ts
@@ -30,7 +30,7 @@ import { IotHubInstalledItem } from '@shared/models/iot-hub/iot-hub-installed-it
import { IotHubApiService } from '@core/http/iot-hub-api.service';
import { MatDialog } from '@angular/material/dialog';
import { TranslateService } from '@ngx-translate/core';
-import { Router } from '@angular/router';
+import { ActivatedRoute, Router } from '@angular/router';
import { TbIotHubItemDetailDialogComponent, IotHubItemDetailDialogData } from './iot-hub-item-detail-dialog.component';
import { TbIotHubInstallDialogComponent, IotHubInstallDialogData } from './iot-hub-install-dialog.component';
import { TbIotHubUpdateDialogComponent, IotHubUpdateDialogData } from './iot-hub-update-dialog.component';
@@ -101,7 +101,8 @@ export class TbIotHubBrowseComponent implements OnInit, OnDestroy {
private iotHubApiService: IotHubApiService,
private dialog: MatDialog,
private translate: TranslateService,
- private router: Router
+ private router: Router,
+ private route: ActivatedRoute
) {}
ngOnInit(): void {
@@ -112,6 +113,15 @@ export class TbIotHubBrowseComponent implements OnInit, OnDestroy {
this.pageIndex = 0;
this.loadItems();
});
+ if (!this.embedded) {
+ const params = this.route.snapshot.queryParams;
+ if (params['type'] && Object.values(ItemType).includes(params['type'])) {
+ this.activeType = params['type'] as ItemType;
+ }
+ if (params['search']) {
+ this.textSearch = params['search'];
+ }
+ }
this.updateCategories();
this.loadItems();
}
diff --git a/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-home.component.html b/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-home.component.html
new file mode 100644
index 0000000000..8794fe53b1
--- /dev/null
+++ b/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-home.component.html
@@ -0,0 +1,200 @@
+
+
+
+
+
+
+
+
+ @for (heroType of heroTypes; track heroType.type) {
+ @if (heroType.icons.length) {
+
+ @for (iconUrl of heroType.icons; track iconUrl; let i = $index) {
+
![]()
+ }
+
+ }
+ }
+
+
+
{{ 'iot-hub.home-title' | translate }}
+
+ {{ 'iot-hub.home-subtitle-prefix' | translate }}
+ @for (ht of heroTypes; track ht.type; let last = $last) {
+ {{ ht.labelKey | translate }}@if (!last) {,}
+ }
+
+
+
+
+
+
+
+
+
+
+ @for (card of categoryCards; track card.type) {
+
+
{{ card.titleKey | translate }}
+
![]()
+
+ }
+
+
+ @if (!isLoading) {
+
+ @if (popularWidgets.length) {
+
+
+
+ @for (item of popularWidgets; track item.id) {
+
+
+ }
+
+
+ }
+
+
+ @if (popularDashboards.length) {
+
+
+
+ @for (item of popularDashboards; track item.id) {
+
+
+ }
+
+
+ }
+
+
+ @if (popularSolutionTemplates.length) {
+
+
+
+ @for (item of popularSolutionTemplates; track item.id) {
+
+
+ }
+
+
+ }
+
+
+ @if (popularCalcFields.length) {
+
+
+
+ @for (item of popularCalcFields; track item.id) {
+
+
+ }
+
+
+ }
+
+
+ @if (popularRuleChains.length) {
+
+
+
+ @for (item of popularRuleChains; track item.id) {
+
+
+ }
+
+
+ }
+
+
+
+
+
{{ 'iot-hub.become-a-creator' | translate }}
+
{{ 'iot-hub.become-creator-text' | translate }}
+
+
+ }
+
+ @if (isLoading) {
+
+
+
+ }
+
diff --git a/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-home.component.scss b/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-home.component.scss
new file mode 100644
index 0000000000..401d9ec234
--- /dev/null
+++ b/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-home.component.scss
@@ -0,0 +1,419 @@
+/**
+ * Copyright © 2016-2026 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.
+ */
+
+// Outer page — 16px padding around the white container
+:host {
+ display: block;
+ padding: 16px;
+}
+
+// Page container — white card with 8px radius, matches Figma "page-container"
+.tb-iot-hub-home {
+ position: relative;
+ background: white;
+ border-radius: 8px;
+ overflow: hidden;
+
+ // Dot grid pattern — covers top 800px, fades out via gradient mask
+ &::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ height: 800px;
+ background-image: radial-gradient(circle, rgba(0, 0, 0, 0.1) 1px, transparent 1px);
+ background-size: 20px 20px;
+ mask-image: linear-gradient(to bottom, black 0%, transparent 100%);
+ -webkit-mask-image: linear-gradient(to bottom, black 0%, transparent 100%);
+ pointer-events: none;
+ z-index: 0;
+ }
+
+ > * {
+ position: relative;
+ z-index: 1;
+ }
+}
+
+// Hero section
+.tb-iot-hub-hero {
+ text-align: center;
+ padding: 140px 40px 80px;
+ position: relative;
+ overflow: hidden;
+}
+
+// Animated background gradient
+.tb-iot-hub-hero-gradient {
+ position: absolute;
+ inset: 0;
+ transition: background 0.6s ease;
+ pointer-events: none;
+}
+
+// Floating icons container per type
+.tb-iot-hub-hero-icons {
+ position: absolute;
+ inset: 0;
+ pointer-events: none;
+
+ // Icons start near center (translate toward center, scaled down, invisible)
+ .tb-iot-hub-hero-float-icon {
+ opacity: 0;
+ transform: var(--icon-rotation) translate(var(--start-x), var(--start-y)) scale(0.9);
+ transition: opacity 0.5s ease, transform 0.6s cubic-bezier(0.34, 1.56, 0.64, 1);
+ }
+
+ // Active: icons move outward to final position, scale to 1, fully visible
+ &.active .tb-iot-hub-hero-float-icon {
+ opacity: 1;
+ transform: var(--icon-rotation) translate(0, 0) scale(1);
+ }
+}
+
+// Individual floating icon — final (resting) positions match Figma
+.tb-iot-hub-hero-float-icon {
+ position: absolute;
+ width: 64px;
+ height: 64px;
+
+ // Top-left: moves from center-right and center-down to final pos
+ &.icon-pos-1 {
+ top: 40px;
+ left: 23%;
+ --icon-rotation: rotate(-15deg);
+ --start-x: 40px;
+ --start-y: 12px;
+ }
+
+ // Top-right: moves from center-left and center-down
+ &.icon-pos-2 {
+ top: 40px;
+ right: 23%;
+ --icon-rotation: rotate(15deg);
+ --start-x: -40px;
+ --start-y: 12px;
+ }
+
+ // Bottom-left: moves from center-right and center-up
+ &.icon-pos-3 {
+ bottom: 100px;
+ left: 18%;
+ --icon-rotation: rotate(-30deg);
+ --start-x: 50px;
+ --start-y: -8px;
+ }
+
+ // Bottom-right: moves from center-left and center-up
+ &.icon-pos-4 {
+ bottom: 100px;
+ right: 18%;
+ --icon-rotation: rotate(30deg);
+ --start-x: -45px;
+ --start-y: -4px;
+ }
+}
+
+.tb-iot-hub-hero-content {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 40px;
+ position: relative;
+ z-index: 1;
+}
+
+.tb-iot-hub-hero-title {
+ font-size: 56px;
+ font-weight: 500;
+ line-height: 1.2;
+ letter-spacing: 0.14px;
+ color: rgba(0, 0, 0, 0.87);
+ margin: 0;
+}
+
+.tb-iot-hub-hero-subtitle {
+ font-size: 20px;
+ line-height: 24px;
+ letter-spacing: 0.1px;
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: center;
+ gap: 6px;
+}
+
+.tb-iot-hub-hero-prefix {
+ font-weight: 400;
+ color: rgba(0, 0, 0, 0.76);
+}
+
+.tb-iot-hub-hero-keyword {
+ font-weight: 600;
+ color: rgba(0, 0, 0, 0.54);
+ opacity: 0.48;
+ cursor: pointer;
+ transition: color 0.3s ease, opacity 0.3s ease;
+
+ &.active {
+ color: var(--keyword-color);
+ opacity: 1;
+ }
+
+ &:hover {
+ opacity: 0.8;
+ }
+}
+
+.tb-iot-hub-hero-search {
+ width: 600px;
+ max-width: 100%;
+
+ ::ng-deep .mat-mdc-text-field-wrapper {
+ background: white;
+ border-radius: 8px;
+ box-shadow:
+ 0 7px 16px 0 rgba(0, 0, 0, 0.04),
+ 0 29px 29px 0 rgba(0, 0, 0, 0.03),
+ 0 64px 38px 0 rgba(0, 0, 0, 0.02);
+ }
+
+ .mat-mdc-form-field-subscript-wrapper {
+ display: none;
+ }
+}
+
+// Category cards — Figma: centered 1200px container, flex-col gap=20, pb=48, 3-col rows
+.tb-iot-hub-categories {
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ gap: 20px;
+ max-width: 1200px;
+ margin: 0 auto;
+ padding-bottom: 48px;
+}
+
+// Card — Figma: flex-col, gap=24, pt=24, rounded-8, border, overflow-clip, h=220
+.tb-iot-hub-category-card {
+ height: 220px;
+ border-radius: 8px;
+ cursor: pointer;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 24px;
+ padding-top: 24px;
+ transition: transform 0.2s, box-shadow 0.2s;
+ border: 1px solid rgba(0, 0, 0, 0.12);
+ overflow: hidden;
+
+ &:hover {
+ transform: translateY(-2px);
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
+ }
+}
+
+// Title — Figma: 18px Medium, centered, tracking=0.15, flex child
+.tb-iot-hub-category-title {
+ font-size: 18px;
+ font-weight: 500;
+ line-height: 24px;
+ letter-spacing: 0.15px;
+ color: rgba(0, 0, 0, 0.87);
+ text-align: center;
+ width: 100%;
+ flex-shrink: 0;
+}
+
+// Card image — Figma: h=148, w=full, overflow-clip, fills remaining space
+.tb-iot-hub-category-img {
+ width: 100%;
+ height: 148px;
+ object-fit: cover;
+ flex-shrink: 0;
+}
+
+// Exact Figma gradients (104.75deg angle)
+.category-widgets {
+ background: linear-gradient(104.75deg, rgb(240, 255, 245) 0%, rgb(147, 246, 182) 100%);
+}
+
+.category-dashboards {
+ background: linear-gradient(104.75deg, rgb(245, 246, 255) 0%, rgb(189, 197, 255) 100%);
+}
+
+.category-solutions {
+ background: linear-gradient(104.75deg, rgb(245, 250, 255) 0%, rgb(149, 200, 255) 100%);
+}
+
+.category-calc-fields {
+ background: linear-gradient(104.75deg, rgb(245, 252, 255) 0%, rgb(149, 222, 248) 100%);
+}
+
+.category-rule-chains {
+ background: linear-gradient(104.75deg, rgb(255, 252, 245) 0%, rgb(255, 238, 194) 100%);
+}
+
+.category-devices {
+ background: linear-gradient(104.75deg, rgb(245, 255, 252) 0%, rgb(147, 240, 213) 100%);
+}
+
+// Sections — matches Figma "items-section" (px=40, gap between sections ~48px)
+.tb-iot-hub-section {
+ padding: 0 40px;
+ margin-top: 48px;
+}
+
+// Section header — matches Figma "button" (height=40, font-size=20, font-weight=500)
+.tb-iot-hub-section-header {
+ display: inline-flex;
+ align-items: center;
+ gap: 4px;
+ font-size: 20px;
+ font-weight: 500;
+ line-height: 40px;
+ color: rgba(0, 0, 0, 0.87);
+ cursor: pointer;
+ text-decoration: none;
+
+ mat-icon {
+ font-size: 20px;
+ width: 20px;
+ height: 20px;
+ }
+
+ &:hover {
+ color: #00695c;
+ }
+}
+
+// Big card row — matches Figma "cards-row" (5 cards, gap=20, mt=12 from header)
+.tb-iot-hub-big-cards-row {
+ display: grid;
+ grid-template-columns: repeat(5, 1fr);
+ gap: 20px;
+ margin-top: 12px;
+}
+
+// Compact card grid — matches Figma "cards" (3 columns, row gap=20, col gap=20)
+.tb-iot-hub-compact-cards-grid {
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ gap: 20px;
+ margin-top: 12px;
+}
+
+// Divider — matches Figma "divider" (1px, rgba(0,0,0,0.12))
+.tb-iot-hub-divider {
+ height: 1px;
+ background: rgba(0, 0, 0, 0.12);
+ margin: 48px 0 0;
+}
+
+// Become a Creator — matches Figma (pt=64, text centered, button at y=156)
+.tb-iot-hub-become-creator {
+ text-align: center;
+ padding: 64px 40px;
+
+ h2 {
+ font-size: 24px;
+ font-weight: 500;
+ line-height: 32px;
+ color: rgba(0, 0, 0, 0.87);
+ margin: 0 0 8px;
+ }
+
+ p {
+ font-size: 14px;
+ line-height: 20px;
+ color: rgba(0, 0, 0, 0.54);
+ margin: 0 0 32px;
+ }
+}
+
+// Loading
+.tb-iot-hub-loading {
+ display: flex;
+ justify-content: center;
+ padding: 80px 0;
+}
+
+// Page load animation — Figma: Default→appear-first, Smart Animate 1.02s, Gentle easing
+// Hero elements fade in + slide DOWN 20px; categories fade in + slide UP 20px
+@keyframes iot-hub-fade-slide-down {
+ from {
+ opacity: 0;
+ transform: translateY(-20px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+@keyframes iot-hub-fade-slide-up {
+ from {
+ opacity: 0;
+ transform: translateY(20px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+.tb-iot-hub-hero-title,
+.tb-iot-hub-hero-subtitle,
+.tb-iot-hub-hero-search {
+ animation: iot-hub-fade-slide-down 1s cubic-bezier(0.19, 1, 0.22, 1) 0.2s both;
+}
+
+.tb-iot-hub-categories {
+ animation: iot-hub-fade-slide-up 1s cubic-bezier(0.19, 1, 0.22, 1) 0.2s both;
+}
+
+// Responsive
+@media (max-width: 1200px) {
+ .tb-iot-hub-big-cards-row {
+ grid-template-columns: repeat(3, 1fr);
+ }
+
+ .tb-iot-hub-compact-cards-grid {
+ grid-template-columns: repeat(2, 1fr);
+ }
+}
+
+@media (max-width: 900px) {
+ .tb-iot-hub-categories {
+ grid-template-columns: repeat(2, 1fr);
+
+ .tb-iot-hub-category-card {
+ height: 180px;
+ }
+ }
+
+ .tb-iot-hub-big-cards-row {
+ grid-template-columns: repeat(2, 1fr);
+ }
+
+ .tb-iot-hub-hero-title {
+ font-size: 36px;
+ }
+
+ .tb-iot-hub-hero {
+ padding: 80px 24px 40px;
+ }
+}
diff --git a/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-home.component.ts b/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-home.component.ts
new file mode 100644
index 0000000000..e0633aa243
--- /dev/null
+++ b/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-home.component.ts
@@ -0,0 +1,222 @@
+///
+/// Copyright © 2016-2026 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, OnInit, OnDestroy } from '@angular/core';
+import { Router } from '@angular/router';
+import { MatDialog } from '@angular/material/dialog';
+import { forkJoin } from 'rxjs';
+import { PageLink } from '@shared/models/page/page-link';
+import { Direction, SortOrder } from '@shared/models/page/sort-order';
+import { MpItemVersionQuery, MpItemVersionView } from '@shared/models/iot-hub/iot-hub-version.models';
+import { ItemType, itemTypeTranslations } from '@shared/models/iot-hub/iot-hub-item.models';
+import { IotHubApiService } from '@core/http/iot-hub-api.service';
+import { TbIotHubItemDetailDialogComponent, IotHubItemDetailDialogData } from './iot-hub-item-detail-dialog.component';
+import { TbIotHubInstallDialogComponent, IotHubInstallDialogData } from './iot-hub-install-dialog.component';
+
+interface CategoryCard {
+ type: ItemType;
+ titleKey: string;
+ icon: string;
+ cssClass: string;
+ image: string;
+}
+
+interface HeroTypeConfig {
+ type: ItemType;
+ labelKey: string;
+ color: string;
+ gradientColor: string;
+ icons: string[];
+}
+
+@Component({
+ selector: 'tb-iot-hub-home',
+ standalone: false,
+ templateUrl: './iot-hub-home.component.html',
+ styleUrls: ['./iot-hub-home.component.scss']
+})
+export class TbIotHubHomeComponent implements OnInit, OnDestroy {
+
+ readonly ItemType = ItemType;
+
+ searchText = '';
+
+ heroTypes: HeroTypeConfig[] = [
+ {
+ type: ItemType.WIDGET, labelKey: 'item.type-widget-plural', color: '#2c9755',
+ gradientColor: 'rgba(44, 151, 85, 0.1)',
+ icons: ['assets/iot-hub/hero-widget-icon-1.svg', 'assets/iot-hub/hero-widget-icon-2.svg', 'assets/iot-hub/hero-widget-icon-3.svg', 'assets/iot-hub/hero-widget-icon-4.svg']
+ },
+ {
+ type: ItemType.DASHBOARD, labelKey: 'item.type-dashboard-plural', color: '#4d5fd0',
+ gradientColor: 'rgba(77, 95, 208, 0.1)',
+ icons: ['assets/iot-hub/hero-dashboard-icon-1.svg', 'assets/iot-hub/hero-dashboard-icon-2.svg', 'assets/iot-hub/hero-dashboard-icon-3.svg', 'assets/iot-hub/hero-dashboard-icon-4.svg']
+ },
+ {
+ type: ItemType.SOLUTION_TEMPLATE, labelKey: 'item.type-solution-template-plural', color: '#2666a9',
+ gradientColor: 'rgba(38, 102, 169, 0.1)', icons: []
+ },
+ {
+ type: ItemType.CALCULATED_FIELD, labelKey: 'item.type-calculated-field-plural', color: '#006d92',
+ gradientColor: 'rgba(0, 109, 146, 0.1)', icons: []
+ },
+ {
+ type: ItemType.RULE_CHAIN, labelKey: 'item.type-rule-chain-plural', color: '#95694b',
+ gradientColor: 'rgba(149, 105, 75, 0.1)', icons: []
+ },
+ {
+ type: ItemType.DEVICE, labelKey: 'iot-hub.and-devices', color: '#4b8a79',
+ gradientColor: 'rgba(75, 138, 121, 0.1)', icons: []
+ }
+ ];
+
+ activeHeroType: HeroTypeConfig = this.heroTypes[0];
+ heroIconsReady = false;
+ private heroInterval: any;
+
+ categoryCards: CategoryCard[] = [
+ { type: ItemType.WIDGET, titleKey: 'item.type-widget-plural', icon: 'widgets', cssClass: 'category-widgets', image: 'assets/iot-hub/category-widgets-img.svg' },
+ { type: ItemType.DASHBOARD, titleKey: 'item.type-dashboard-plural', icon: 'dashboard', cssClass: 'category-dashboards', image: 'assets/iot-hub/category-dashboards-img.svg' },
+ { type: ItemType.SOLUTION_TEMPLATE, titleKey: 'item.type-solution-template-plural', icon: 'integration_instructions', cssClass: 'category-solutions', image: 'assets/iot-hub/category-solution-templates-img.png' },
+ { type: ItemType.CALCULATED_FIELD, titleKey: 'item.type-calculated-field-plural', icon: 'functions', cssClass: 'category-calc-fields', image: 'assets/iot-hub/category-calculated-fields-img.svg' },
+ { type: ItemType.RULE_CHAIN, titleKey: 'item.type-rule-chain-plural', icon: 'account_tree', cssClass: 'category-rule-chains', image: 'assets/iot-hub/category-rule-chains-img.svg' },
+ { type: ItemType.DEVICE, titleKey: 'iot-hub.device-library', icon: 'memory', cssClass: 'category-devices', image: 'assets/iot-hub/category-device-library-img.svg' }
+ ];
+
+ popularWidgets: MpItemVersionView[] = [];
+ popularDashboards: MpItemVersionView[] = [];
+ popularSolutionTemplates: MpItemVersionView[] = [];
+ popularCalcFields: MpItemVersionView[] = [];
+ popularRuleChains: MpItemVersionView[] = [];
+
+ isLoading = true;
+
+ constructor(
+ private router: Router,
+ private dialog: MatDialog,
+ private iotHubApiService: IotHubApiService
+ ) {}
+
+ ngOnInit(): void {
+ this.loadPopularItems();
+ // One-tick delay so Angular renders icons in hidden state first, then triggers transition
+ requestAnimationFrame(() => {
+ this.heroIconsReady = true;
+ this.startHeroCycle();
+ });
+ }
+
+ ngOnDestroy(): void {
+ this.stopHeroCycle();
+ }
+
+ onHeroTypeHover(config: HeroTypeConfig): void {
+ this.stopHeroCycle();
+ this.activeHeroType = config;
+ }
+
+ onHeroTypeLeave(): void {
+ this.startHeroCycle();
+ }
+
+ private startHeroCycle(): void {
+ this.stopHeroCycle();
+ this.heroInterval = setInterval(() => {
+ const idx = this.heroTypes.indexOf(this.activeHeroType);
+ this.activeHeroType = this.heroTypes[(idx + 1) % this.heroTypes.length];
+ }, 3000);
+ }
+
+ private stopHeroCycle(): void {
+ if (this.heroInterval) {
+ clearInterval(this.heroInterval);
+ this.heroInterval = null;
+ }
+ }
+
+ onSearch(): void {
+ if (this.searchText?.trim()) {
+ this.router.navigate(['/iot-hub/browse'], { queryParams: { search: this.searchText.trim() } });
+ }
+ }
+
+ navigateToBrowse(type: ItemType): void {
+ this.router.navigate(['/iot-hub/browse'], { queryParams: { type } });
+ }
+
+ navigateToInstalledItems(): void {
+ this.router.navigate(['/iot-hub/installed']);
+ }
+
+ openItemDetail(item: MpItemVersionView): void {
+ this.dialog.open(TbIotHubItemDetailDialogComponent, {
+ panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
+ autoFocus: false,
+ data: {
+ item,
+ iotHubApiService: this.iotHubApiService
+ } as IotHubItemDetailDialogData
+ });
+ }
+
+ installItem(item: MpItemVersionView): void {
+ this.dialog.open(TbIotHubInstallDialogComponent, {
+ panelClass: ['tb-dialog'],
+ data: {
+ item,
+ iotHubApiService: this.iotHubApiService
+ } as IotHubInstallDialogData
+ });
+ }
+
+ navigateToCreator(creatorId: string): void {
+ this.router.navigate(['/iot-hub/creator', creatorId]);
+ }
+
+ openSignup(): void {
+ window.open('https://iothub.thingsboard.io/signup', '_blank');
+ }
+
+ private loadPopularItems(): void {
+ const sortOrder: SortOrder = { property: 'totalInstallCount', direction: Direction.DESC };
+ const config = { ignoreLoading: true };
+
+ const buildQuery = (type: ItemType, size: number): MpItemVersionQuery => {
+ const pageLink = new PageLink(size, 0, null, sortOrder);
+ return new MpItemVersionQuery(pageLink, type);
+ };
+
+ forkJoin({
+ widgets: this.iotHubApiService.getPublishedVersions(buildQuery(ItemType.WIDGET, 5), config),
+ dashboards: this.iotHubApiService.getPublishedVersions(buildQuery(ItemType.DASHBOARD, 5), config),
+ solutionTemplates: this.iotHubApiService.getPublishedVersions(buildQuery(ItemType.SOLUTION_TEMPLATE, 5), config),
+ calcFields: this.iotHubApiService.getPublishedVersions(buildQuery(ItemType.CALCULATED_FIELD, 6), config),
+ ruleChains: this.iotHubApiService.getPublishedVersions(buildQuery(ItemType.RULE_CHAIN, 6), config)
+ }).subscribe({
+ next: (results) => {
+ this.popularWidgets = results.widgets.data;
+ this.popularDashboards = results.dashboards.data;
+ this.popularSolutionTemplates = results.solutionTemplates.data;
+ this.popularCalcFields = results.calcFields.data;
+ this.popularRuleChains = results.ruleChains.data;
+ this.isLoading = false;
+ },
+ error: () => {
+ this.isLoading = false;
+ }
+ });
+ }
+}
diff --git a/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-routing.module.ts b/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-routing.module.ts
index 33580a26bd..040bf2ef5c 100644
--- a/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-routing.module.ts
+++ b/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-routing.module.ts
@@ -18,6 +18,7 @@ import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { Authority } from '@shared/models/authority.enum';
+import { TbIotHubHomeComponent } from './iot-hub-home.component';
import { TbIotHubBrowseComponent } from './iot-hub-browse.component';
import { TbIotHubCreatorProfileComponent } from './iot-hub-creator-profile.component';
import { TbIotHubInstalledItemsComponent } from './iot-hub-installed-items.component';
@@ -35,10 +36,22 @@ const routes: Routes = [
children: [
{
path: '',
+ component: TbIotHubHomeComponent,
+ data: {
+ auth: [Authority.TENANT_ADMIN],
+ title: 'iot-hub.iot-hub'
+ }
+ },
+ {
+ path: 'browse',
component: TbIotHubBrowseComponent,
data: {
auth: [Authority.TENANT_ADMIN],
- title: 'iot-hub.browse'
+ title: 'iot-hub.browse',
+ breadcrumb: {
+ label: 'iot-hub.browse',
+ icon: 'search'
+ }
}
},
{
diff --git a/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub.module.ts b/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub.module.ts
index 94110fb092..092dbc401c 100644
--- a/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub.module.ts
+++ b/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub.module.ts
@@ -18,6 +18,7 @@ import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { SharedModule } from '@shared/shared.module';
import { IotHubRoutingModule } from './iot-hub-routing.module';
+import { TbIotHubHomeComponent } from './iot-hub-home.component';
import { TbIotHubBrowseComponent } from './iot-hub-browse.component';
import { TbIotHubCreatorProfileComponent } from './iot-hub-creator-profile.component';
import { TbIotHubItemDetailDialogComponent } from './iot-hub-item-detail-dialog.component';
@@ -28,6 +29,7 @@ import { TbIotHubUpdateDialogComponent } from './iot-hub-update-dialog.component
@NgModule({
declarations: [
+ TbIotHubHomeComponent,
TbIotHubBrowseComponent,
TbIotHubCreatorProfileComponent,
TbIotHubItemDetailDialogComponent,
diff --git a/ui-ngx/src/assets/iot-hub/category-calculated-fields-img.svg b/ui-ngx/src/assets/iot-hub/category-calculated-fields-img.svg
new file mode 100644
index 0000000000..e74a12f489
--- /dev/null
+++ b/ui-ngx/src/assets/iot-hub/category-calculated-fields-img.svg
@@ -0,0 +1,233 @@
+
diff --git a/ui-ngx/src/assets/iot-hub/category-dashboards-img.svg b/ui-ngx/src/assets/iot-hub/category-dashboards-img.svg
new file mode 100644
index 0000000000..2db1d0fafa
--- /dev/null
+++ b/ui-ngx/src/assets/iot-hub/category-dashboards-img.svg
@@ -0,0 +1,84 @@
+
diff --git a/ui-ngx/src/assets/iot-hub/category-device-library-img.svg b/ui-ngx/src/assets/iot-hub/category-device-library-img.svg
new file mode 100644
index 0000000000..57b90cde55
--- /dev/null
+++ b/ui-ngx/src/assets/iot-hub/category-device-library-img.svg
@@ -0,0 +1,95 @@
+
diff --git a/ui-ngx/src/assets/iot-hub/category-rule-chains-img.svg b/ui-ngx/src/assets/iot-hub/category-rule-chains-img.svg
new file mode 100644
index 0000000000..09bd6882da
--- /dev/null
+++ b/ui-ngx/src/assets/iot-hub/category-rule-chains-img.svg
@@ -0,0 +1,107 @@
+
diff --git a/ui-ngx/src/assets/iot-hub/category-solution-templates-img.png b/ui-ngx/src/assets/iot-hub/category-solution-templates-img.png
new file mode 100644
index 0000000000..efcd683abd
Binary files /dev/null and b/ui-ngx/src/assets/iot-hub/category-solution-templates-img.png differ
diff --git a/ui-ngx/src/assets/iot-hub/category-widgets-img.svg b/ui-ngx/src/assets/iot-hub/category-widgets-img.svg
new file mode 100644
index 0000000000..607a9c4911
--- /dev/null
+++ b/ui-ngx/src/assets/iot-hub/category-widgets-img.svg
@@ -0,0 +1,181 @@
+
diff --git a/ui-ngx/src/assets/iot-hub/hero-dashboard-icon-1.svg b/ui-ngx/src/assets/iot-hub/hero-dashboard-icon-1.svg
new file mode 100644
index 0000000000..471177f37b
--- /dev/null
+++ b/ui-ngx/src/assets/iot-hub/hero-dashboard-icon-1.svg
@@ -0,0 +1,30 @@
+
diff --git a/ui-ngx/src/assets/iot-hub/hero-dashboard-icon-2.svg b/ui-ngx/src/assets/iot-hub/hero-dashboard-icon-2.svg
new file mode 100644
index 0000000000..3e453e4b17
--- /dev/null
+++ b/ui-ngx/src/assets/iot-hub/hero-dashboard-icon-2.svg
@@ -0,0 +1,25 @@
+
diff --git a/ui-ngx/src/assets/iot-hub/hero-dashboard-icon-3.svg b/ui-ngx/src/assets/iot-hub/hero-dashboard-icon-3.svg
new file mode 100644
index 0000000000..fc733a6dc8
--- /dev/null
+++ b/ui-ngx/src/assets/iot-hub/hero-dashboard-icon-3.svg
@@ -0,0 +1,30 @@
+
diff --git a/ui-ngx/src/assets/iot-hub/hero-dashboard-icon-4.svg b/ui-ngx/src/assets/iot-hub/hero-dashboard-icon-4.svg
new file mode 100644
index 0000000000..949f7c267d
--- /dev/null
+++ b/ui-ngx/src/assets/iot-hub/hero-dashboard-icon-4.svg
@@ -0,0 +1,30 @@
+
diff --git a/ui-ngx/src/assets/iot-hub/hero-widget-icon-1.svg b/ui-ngx/src/assets/iot-hub/hero-widget-icon-1.svg
new file mode 100644
index 0000000000..9740f9c55a
--- /dev/null
+++ b/ui-ngx/src/assets/iot-hub/hero-widget-icon-1.svg
@@ -0,0 +1,27 @@
+
diff --git a/ui-ngx/src/assets/iot-hub/hero-widget-icon-2.svg b/ui-ngx/src/assets/iot-hub/hero-widget-icon-2.svg
new file mode 100644
index 0000000000..1d02f2d107
--- /dev/null
+++ b/ui-ngx/src/assets/iot-hub/hero-widget-icon-2.svg
@@ -0,0 +1,25 @@
+
diff --git a/ui-ngx/src/assets/iot-hub/hero-widget-icon-3.svg b/ui-ngx/src/assets/iot-hub/hero-widget-icon-3.svg
new file mode 100644
index 0000000000..0bd4d29731
--- /dev/null
+++ b/ui-ngx/src/assets/iot-hub/hero-widget-icon-3.svg
@@ -0,0 +1,35 @@
+
diff --git a/ui-ngx/src/assets/iot-hub/hero-widget-icon-4.svg b/ui-ngx/src/assets/iot-hub/hero-widget-icon-4.svg
new file mode 100644
index 0000000000..37d0c227b0
--- /dev/null
+++ b/ui-ngx/src/assets/iot-hub/hero-widget-icon-4.svg
@@ -0,0 +1,35 @@
+
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 4ebba5fd38..7b82928c68 100644
--- a/ui-ngx/src/assets/locale/locale.constant-en_US.json
+++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json
@@ -3684,14 +3684,32 @@
"item": {
"selected": "Selected",
"type-widget": "Widget",
+ "type-widget-plural": "Widgets",
"type-dashboard": "Dashboard",
+ "type-dashboard-plural": "Dashboards",
"type-solution-template": "Solution Template",
+ "type-solution-template-plural": "Solution Templates",
"type-calculated-field": "Calculated Field",
+ "type-calculated-field-plural": "Calculated Fields",
"type-rule-chain": "Rule Chain",
- "type-device": "Device"
+ "type-rule-chain-plural": "Rule Chains",
+ "type-device": "Device",
+ "type-device-plural": "Devices"
},
"iot-hub": {
"iot-hub": "IoT Hub",
+ "home-title": "ThingsBoard IoT Hub",
+ "home-subtitle-prefix": "Discover ready-to-use",
+ "and-devices": "& Devices",
+ "search-placeholder": "Search in IoT Hub...",
+ "device-library": "Device Library",
+ "popular-widgets": "Popular Widgets",
+ "popular-dashboards": "Popular Dashboards",
+ "popular-solution-templates": "Popular Solution Templates",
+ "popular-calculated-fields": "Popular Calculated Fields",
+ "popular-rule-chains": "Popular Rule Chains",
+ "become-creator-text": "Submit your templates to the ThingsBoard IoT Hub to get featured and showcase your solutions to our global community.",
+ "submit-template": "Submit Template",
"browse": "Browse IoT Hub",
"creator-profile": "Creator Profile",
"title-widgets": "Widgets",