Browse Source

Add installed items count API, installed items button, and menu reorder

- Add getInstalledItemsCount endpoint through full stack (controller → DAO)
  with optional itemType filter
- Add getInstalledItemsCount to UI IotHubApiService
- Home page: show "Installed Items: N" stroked button at top-right when
  count > 0, white bg, primary border, card shadow; navigates to installed items
- Count updates on install/delete via API re-fetch and local decrement
- Move IoT Hub menu item right after Dashboards in sidebar
pull/15508/head
Igor Kulikov 6 months ago
parent
commit
1e70c4acad
  1. 7
      application/src/main/java/org/thingsboard/server/controller/IotHubController.java
  2. 2
      dao/src/main/java/org/thingsboard/server/dao/iot_hub/IotHubInstalledItemDao.java
  3. 2
      dao/src/main/java/org/thingsboard/server/dao/iot_hub/IotHubInstalledItemService.java
  4. 5
      dao/src/main/java/org/thingsboard/server/dao/iot_hub/IotHubInstalledItemServiceImpl.java
  5. 7
      dao/src/main/java/org/thingsboard/server/dao/sql/iot_hub/IotHubInstalledItemRepository.java
  6. 5
      dao/src/main/java/org/thingsboard/server/dao/sql/iot_hub/JpaIotHubInstalledItemDao.java
  7. 8
      ui-ngx/src/app/core/http/iot-hub-api.service.ts
  8. 8
      ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-home.component.html
  9. 18
      ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-home.component.scss
  10. 9
      ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-home.component.ts

7
application/src/main/java/org/thingsboard/server/controller/IotHubController.java

@ -87,6 +87,13 @@ public class IotHubController extends BaseController {
return iotHubInstalledItemService.findByTenantId(getTenantId(), itemType, pageLink); return iotHubInstalledItemService.findByTenantId(getTenantId(), itemType, pageLink);
} }
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@GetMapping("/installedItems/count")
@ResponseBody
public long getInstalledItemsCount(@RequestParam(required = false) String itemType) throws ThingsboardException {
return iotHubInstalledItemService.countByTenantId(getTenantId(), itemType);
}
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@GetMapping("/installedItems/itemIds") @GetMapping("/installedItems/itemIds")
@ResponseBody @ResponseBody

2
dao/src/main/java/org/thingsboard/server/dao/iot_hub/IotHubInstalledItemDao.java

@ -30,6 +30,8 @@ public interface IotHubInstalledItemDao extends Dao<IotHubInstalledItem> {
List<UUID> findInstalledItemIdsByTenantId(TenantId tenantId); List<UUID> findInstalledItemIdsByTenantId(TenantId tenantId);
long countByTenantId(TenantId tenantId, String itemType);
void deleteByTenantId(TenantId tenantId); void deleteByTenantId(TenantId tenantId);
} }

2
dao/src/main/java/org/thingsboard/server/dao/iot_hub/IotHubInstalledItemService.java

@ -34,6 +34,8 @@ public interface IotHubInstalledItemService {
List<UUID> findInstalledItemIdsByTenantId(TenantId tenantId); List<UUID> findInstalledItemIdsByTenantId(TenantId tenantId);
long countByTenantId(TenantId tenantId, String itemType);
void deleteById(TenantId tenantId, IotHubInstalledItemId id); void deleteById(TenantId tenantId, IotHubInstalledItemId id);
void deleteByTenantId(TenantId tenantId); void deleteByTenantId(TenantId tenantId);

5
dao/src/main/java/org/thingsboard/server/dao/iot_hub/IotHubInstalledItemServiceImpl.java

@ -55,6 +55,11 @@ class IotHubInstalledItemServiceImpl implements IotHubInstalledItemService {
return iotHubInstalledItemDao.findInstalledItemIdsByTenantId(tenantId); return iotHubInstalledItemDao.findInstalledItemIdsByTenantId(tenantId);
} }
@Override
public long countByTenantId(TenantId tenantId, String itemType) {
return iotHubInstalledItemDao.countByTenantId(tenantId, itemType);
}
@Override @Override
public void deleteById(TenantId tenantId, IotHubInstalledItemId id) { public void deleteById(TenantId tenantId, IotHubInstalledItemId id) {
iotHubInstalledItemDao.removeById(tenantId, id.getId()); iotHubInstalledItemDao.removeById(tenantId, id.getId());

7
dao/src/main/java/org/thingsboard/server/dao/sql/iot_hub/IotHubInstalledItemRepository.java

@ -43,6 +43,13 @@ interface IotHubInstalledItemRepository extends JpaRepository<IotHubInstalledIte
""") """)
Page<IotHubInstalledItemEntity> findByTenantId(@Param("tenantId") UUID tenantId, @Param("itemType") String itemType, @Param("textSearch") String textSearch, Pageable pageable); Page<IotHubInstalledItemEntity> findByTenantId(@Param("tenantId") UUID tenantId, @Param("itemType") String itemType, @Param("textSearch") String textSearch, Pageable pageable);
@Query("""
SELECT COUNT(item) FROM IotHubInstalledItemEntity item
WHERE item.tenantId = :tenantId
AND (:itemType IS NULL OR item.itemType = :itemType)
""")
long countByTenantId(@Param("tenantId") UUID tenantId, @Param("itemType") String itemType);
@Transactional @Transactional
@Modifying @Modifying
@Query(value = "DELETE FROM iot_hub_installed_item WHERE tenant_id = :tenantId", nativeQuery = true) @Query(value = "DELETE FROM iot_hub_installed_item WHERE tenant_id = :tenantId", nativeQuery = true)

5
dao/src/main/java/org/thingsboard/server/dao/sql/iot_hub/JpaIotHubInstalledItemDao.java

@ -57,6 +57,11 @@ class JpaIotHubInstalledItemDao extends JpaAbstractDao<IotHubInstalledItemEntity
return repository.findInstalledItemIdsByTenantId(tenantId.getId()); return repository.findInstalledItemIdsByTenantId(tenantId.getId());
} }
@Override
public long countByTenantId(TenantId tenantId, String itemType) {
return repository.countByTenantId(tenantId.getId(), itemType);
}
@Override @Override
public void deleteByTenantId(TenantId tenantId) { public void deleteByTenantId(TenantId tenantId) {
repository.deleteByTenantId(tenantId.getId()); repository.deleteByTenantId(tenantId.getId());

8
ui-ngx/src/app/core/http/iot-hub-api.service.ts

@ -150,6 +150,14 @@ export class IotHubApiService {
); );
} }
public getInstalledItemsCount(itemType?: string, config?: IotHubRequestConfig): Observable<number> {
let url = '/api/iot-hub/installedItems/count';
if (itemType) {
url += `?itemType=${itemType}`;
}
return this.http.get<number>(url, { params: this.buildParams(config) });
}
public deleteInstalledItem(installedItemId: string, config?: IotHubRequestConfig): Observable<void> { public deleteInstalledItem(installedItemId: string, config?: IotHubRequestConfig): Observable<void> {
return this.http.delete<void>( return this.http.delete<void>(
`/api/iot-hub/installedItems/${installedItemId}`, `/api/iot-hub/installedItems/${installedItemId}`,

8
ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-home.component.html

@ -16,6 +16,14 @@
--> -->
<div class="tb-iot-hub-home"> <div class="tb-iot-hub-home">
<!-- Installed Items button -->
@if (installedItemsCount > 0) {
<button mat-stroked-button color="primary" class="tb-iot-hub-installed-btn" (click)="navigateToInstalledItems()">
<mat-icon>inventory_2</mat-icon>
{{ 'iot-hub.installed-items' | translate }}: {{ installedItemsCount }}
</button>
}
<!-- Hero section --> <!-- Hero section -->
<div class="tb-iot-hub-hero"> <div class="tb-iot-hub-hero">
<!-- Background gradient overlay --> <!-- Background gradient overlay -->

18
ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-home.component.scss

@ -14,6 +14,8 @@
* limitations under the License. * limitations under the License.
*/ */
@import "../../../../../scss/constants";
// Outer page — 16px padding around the white container // Outer page — 16px padding around the white container
:host { :host {
display: block; display: block;
@ -43,12 +45,26 @@
z-index: 0; z-index: 0;
} }
> * { > *:not(.tb-iot-hub-installed-btn) {
position: relative; position: relative;
z-index: 1; z-index: 1;
} }
} }
// Installed Items button — Design: top-right, white bg, primary border, card shadow
.tb-iot-hub-installed-btn {
position: absolute;
top: 24px;
right: 24px;
z-index: 2;
background: white;
box-shadow: 0 1px 4px 0 rgba(0, 0, 0, 0.08), 0 4px 8px 0 rgba(0, 0, 0, 0.08);
&.mat-primary {
--mat-button-outlined-outline-color: #{$tb-primary-color};
}
}
// Hero section // Hero section
.tb-iot-hub-hero { .tb-iot-hub-hero {
text-align: center; text-align: center;

9
ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-home.component.ts

@ -106,6 +106,7 @@ export class TbIotHubHomeComponent implements OnInit, OnDestroy {
installedWidgets: IotHubInstalledItem[] = []; installedWidgets: IotHubInstalledItem[] = [];
installedSolutionTemplates: IotHubInstalledItem[] = []; installedSolutionTemplates: IotHubInstalledItem[] = [];
installedItemsCount = 0;
isLoading = true; isLoading = true;
@ -187,6 +188,9 @@ export class TbIotHubHomeComponent implements OnInit, OnDestroy {
private reloadInstalledItems(type: ItemType): void { private reloadInstalledItems(type: ItemType): void {
const config = { ignoreLoading: true }; const config = { ignoreLoading: true };
const pageLink = new PageLink(10000, 0); const pageLink = new PageLink(10000, 0);
this.iotHubApiService.getInstalledItemsCount(null, config).subscribe(count => {
this.installedItemsCount = count;
});
if (type === ItemType.WIDGET) { if (type === ItemType.WIDGET) {
this.iotHubApiService.getInstalledItems(pageLink, ItemType.WIDGET, config).subscribe(data => { this.iotHubApiService.getInstalledItems(pageLink, ItemType.WIDGET, config).subscribe(data => {
this.installedWidgets = data.data; this.installedWidgets = data.data;
@ -250,6 +254,7 @@ export class TbIotHubHomeComponent implements OnInit, OnDestroy {
dialogRef.afterClosed().subscribe(confirmed => { dialogRef.afterClosed().subscribe(confirmed => {
if (confirmed) { if (confirmed) {
this.iotHubApiService.deleteInstalledItem(installedItem.id.id).subscribe(() => { this.iotHubApiService.deleteInstalledItem(installedItem.id.id).subscribe(() => {
this.installedItemsCount = Math.max(0, this.installedItemsCount - 1);
if (item.type === ItemType.WIDGET) { if (item.type === ItemType.WIDGET) {
this.installedWidgets = this.installedWidgets.filter(i => i.id.id !== installedItem.id.id); this.installedWidgets = this.installedWidgets.filter(i => i.id.id !== installedItem.id.id);
} else if (item.type === ItemType.SOLUTION_TEMPLATE) { } else if (item.type === ItemType.SOLUTION_TEMPLATE) {
@ -281,7 +286,8 @@ export class TbIotHubHomeComponent implements OnInit, OnDestroy {
calcFields: this.iotHubApiService.getPublishedVersions(buildQuery(ItemType.CALCULATED_FIELD, 6), config), calcFields: this.iotHubApiService.getPublishedVersions(buildQuery(ItemType.CALCULATED_FIELD, 6), config),
ruleChains: this.iotHubApiService.getPublishedVersions(buildQuery(ItemType.RULE_CHAIN, 6), config), ruleChains: this.iotHubApiService.getPublishedVersions(buildQuery(ItemType.RULE_CHAIN, 6), config),
installedWidgets: this.iotHubApiService.getInstalledItems(installedPageLink, ItemType.WIDGET, config), installedWidgets: this.iotHubApiService.getInstalledItems(installedPageLink, ItemType.WIDGET, config),
installedSolutionTemplates: this.iotHubApiService.getInstalledItems(installedPageLink, ItemType.SOLUTION_TEMPLATE, config) installedSolutionTemplates: this.iotHubApiService.getInstalledItems(installedPageLink, ItemType.SOLUTION_TEMPLATE, config),
installedCount: this.iotHubApiService.getInstalledItemsCount(null, config)
}).subscribe({ }).subscribe({
next: (results) => { next: (results) => {
this.popularWidgets = results.widgets.data; this.popularWidgets = results.widgets.data;
@ -291,6 +297,7 @@ export class TbIotHubHomeComponent implements OnInit, OnDestroy {
this.popularRuleChains = results.ruleChains.data; this.popularRuleChains = results.ruleChains.data;
this.installedWidgets = results.installedWidgets.data; this.installedWidgets = results.installedWidgets.data;
this.installedSolutionTemplates = results.installedSolutionTemplates.data; this.installedSolutionTemplates = results.installedSolutionTemplates.data;
this.installedItemsCount = results.installedCount;
this.isLoading = false; this.isLoading = false;
}, },
error: () => { error: () => {

Loading…
Cancel
Save