diff --git a/ui-ngx/src/app/modules/home/pages/admin/admin.module.ts b/ui-ngx/src/app/modules/home/pages/admin/admin.module.ts
index a5f18122fd..10721ade5a 100644
--- a/ui-ngx/src/app/modules/home/pages/admin/admin.module.ts
+++ b/ui-ngx/src/app/modules/home/pages/admin/admin.module.ts
@@ -38,6 +38,7 @@ import { JsLibraryTableHeaderComponent } from '@home/pages/admin/resource/js-lib
import { JsResourceComponent } from '@home/pages/admin/resource/js-resource.component';
import { NgxFlowModule } from '@flowjs/ngx-flow';
import { TrendzSettingsComponent } from '@home/pages/admin/trendz-settings.component';
+import { ResourceLibraryTabsComponent } from '@home/pages/admin/resource/resource-library-tabs.component';
@NgModule({
declarations:
@@ -50,6 +51,7 @@ import { TrendzSettingsComponent } from '@home/pages/admin/trendz-settings.compo
HomeSettingsComponent,
ResourcesLibraryComponent,
ResourceTabsComponent,
+ ResourceLibraryTabsComponent,
ResourcesTableHeaderComponent,
JsResourceComponent,
JsLibraryTableHeaderComponent,
diff --git a/ui-ngx/src/app/modules/home/pages/admin/resource/resource-library-tabs.component.html b/ui-ngx/src/app/modules/home/pages/admin/resource/resource-library-tabs.component.html
new file mode 100644
index 0000000000..4effdaad53
--- /dev/null
+++ b/ui-ngx/src/app/modules/home/pages/admin/resource/resource-library-tabs.component.html
@@ -0,0 +1,23 @@
+
+
+
+
diff --git a/ui-ngx/src/app/modules/home/pages/admin/resource/resource-library-tabs.component.ts b/ui-ngx/src/app/modules/home/pages/admin/resource/resource-library-tabs.component.ts
new file mode 100644
index 0000000000..a85bf48db3
--- /dev/null
+++ b/ui-ngx/src/app/modules/home/pages/admin/resource/resource-library-tabs.component.ts
@@ -0,0 +1,36 @@
+///
+/// Copyright © 2016-2025 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 { EntityTabsComponent } from '@home/components/entity/entity-tabs.component';
+import { Resource } from '@shared/models/resource.models';
+import { Store } from '@ngrx/store';
+import { AppState } from '@core/core.state';
+import { NULL_UUID } from '@shared/models/id/has-uuid';
+
+@Component({
+ selector: 'tb-resource-library-tabs',
+ templateUrl: './resource-library-tabs.component.html',
+ styleUrls: []
+})
+export class ResourceLibraryTabsComponent extends EntityTabsComponent {
+
+ readonly NULL_UUID = NULL_UUID;
+
+ constructor(protected store: Store) {
+ super(store);
+ }
+}
diff --git a/ui-ngx/src/app/modules/home/pages/admin/resource/resources-library-table-config.resolve.ts b/ui-ngx/src/app/modules/home/pages/admin/resource/resources-library-table-config.resolve.ts
index 91327b7465..f39ed9ff7b 100644
--- a/ui-ngx/src/app/modules/home/pages/admin/resource/resources-library-table-config.resolve.ts
+++ b/ui-ngx/src/app/modules/home/pages/admin/resource/resources-library-table-config.resolve.ts
@@ -37,6 +37,7 @@ import { PageLink } from '@shared/models/page/page-link';
import { EntityAction } from '@home/models/entity/entity-component.models';
import { map } from 'rxjs/operators';
import { ResourcesTableHeaderComponent } from '@home/pages/admin/resource/resources-table-header.component';
+import { ResourceLibraryTabsComponent } from '@home/pages/admin/resource/resource-library-tabs.component';
@Injectable()
export class ResourcesLibraryTableConfigResolver {
@@ -55,6 +56,7 @@ export class ResourcesLibraryTableConfigResolver {
this.config.entityTranslations = entityTypeTranslations.get(EntityType.TB_RESOURCE);
this.config.entityResources = entityTypeResources.get(EntityType.TB_RESOURCE);
this.config.headerComponent = ResourcesTableHeaderComponent;
+ this.config.entityTabsComponent = ResourceLibraryTabsComponent;
this.config.entityTitle = (resource) => resource ?
resource.title : '';
diff --git a/ui-ngx/src/app/modules/home/pages/ota-update/ota-update-table-config.resolve.ts b/ui-ngx/src/app/modules/home/pages/ota-update/ota-update-table-config.resolve.ts
index cc3ca4046b..0dae778d03 100644
--- a/ui-ngx/src/app/modules/home/pages/ota-update/ota-update-table-config.resolve.ts
+++ b/ui-ngx/src/app/modules/home/pages/ota-update/ota-update-table-config.resolve.ts
@@ -36,6 +36,7 @@ import { PageLink } from '@shared/models/page/page-link';
import { OtaUpdateComponent } from '@home/pages/ota-update/ota-update.component';
import { EntityAction } from '@home/models/entity/entity-component.models';
import { FileSizePipe } from '@shared/pipe/file-size.pipe';
+import { OtaUpdateTabsComponent } from '@home/pages/ota-update/ota-update-tabs.component';
@Injectable()
export class OtaUpdateTableConfigResolve {
@@ -50,6 +51,7 @@ export class OtaUpdateTableConfigResolve {
private fileSize: FileSizePipe) {
this.config.entityType = EntityType.OTA_PACKAGE;
this.config.entityComponent = OtaUpdateComponent;
+ this.config.entityTabsComponent = OtaUpdateTabsComponent;
this.config.entityTranslations = entityTypeTranslations.get(EntityType.OTA_PACKAGE);
this.config.entityResources = entityTypeResources.get(EntityType.OTA_PACKAGE);
diff --git a/ui-ngx/src/app/modules/home/pages/ota-update/ota-update-tabs.component.html b/ui-ngx/src/app/modules/home/pages/ota-update/ota-update-tabs.component.html
new file mode 100644
index 0000000000..a8cdae4256
--- /dev/null
+++ b/ui-ngx/src/app/modules/home/pages/ota-update/ota-update-tabs.component.html
@@ -0,0 +1,23 @@
+
+
+
+
diff --git a/ui-ngx/src/app/modules/home/pages/ota-update/ota-update-tabs.component.ts b/ui-ngx/src/app/modules/home/pages/ota-update/ota-update-tabs.component.ts
new file mode 100644
index 0000000000..44d17f3c11
--- /dev/null
+++ b/ui-ngx/src/app/modules/home/pages/ota-update/ota-update-tabs.component.ts
@@ -0,0 +1,40 @@
+///
+/// Copyright © 2016-2025 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 { Store } from '@ngrx/store';
+import { AppState } from '@core/core.state';
+import { EntityTabsComponent } from '../../components/entity/entity-tabs.component';
+import { WidgetsBundle } from '@shared/models/widgets-bundle.model';
+import { NULL_UUID } from '@shared/models/id/has-uuid';
+import { OtaPackage } from '@shared/models/ota-package.models';
+
+@Component({
+ selector: 'tb-ota-update-tabs',
+ templateUrl: './ota-update-tabs.component.html',
+ styleUrls: []
+})
+export class OtaUpdateTabsComponent extends EntityTabsComponent {
+
+ constructor(protected store: Store) {
+ super(store);
+ }
+
+ isTenantOtaUpdate() {
+ return this.entity && this.entity.tenantId.id !== NULL_UUID;
+ }
+
+}
diff --git a/ui-ngx/src/app/modules/home/pages/ota-update/ota-update.module.ts b/ui-ngx/src/app/modules/home/pages/ota-update/ota-update.module.ts
index 139591f14f..fe24da31b2 100644
--- a/ui-ngx/src/app/modules/home/pages/ota-update/ota-update.module.ts
+++ b/ui-ngx/src/app/modules/home/pages/ota-update/ota-update.module.ts
@@ -20,10 +20,12 @@ import { SharedModule } from '@shared/shared.module';
import { HomeComponentsModule } from '@home/components/home-components.module';
import { OtaUpdateRoutingModule } from '@home/pages/ota-update/ota-update-routing.module';
import { OtaUpdateComponent } from '@home/pages/ota-update/ota-update.component';
+import { OtaUpdateTabsComponent } from '@home/pages/ota-update/ota-update-tabs.component';
@NgModule({
declarations: [
- OtaUpdateComponent
+ OtaUpdateComponent,
+ OtaUpdateTabsComponent
],
imports: [
CommonModule,
diff --git a/ui-ngx/src/app/shared/models/entity-type.models.ts b/ui-ngx/src/app/shared/models/entity-type.models.ts
index 48cad42e7b..60e2956e7c 100644
--- a/ui-ngx/src/app/shared/models/entity-type.models.ts
+++ b/ui-ngx/src/app/shared/models/entity-type.models.ts
@@ -346,6 +346,8 @@ export const entityTypeTranslations = new Map, HasTenantId {
+export interface OtaPackageInfo extends Omit, 'label'>, HasTenantId, ExportableEntity {
tenantId?: TenantId;
type: OtaUpdateType;
deviceProfileId?: DeviceProfileId;
diff --git a/ui-ngx/src/app/shared/models/vc.models.ts b/ui-ngx/src/app/shared/models/vc.models.ts
index 3795518ffc..ebd7840f61 100644
--- a/ui-ngx/src/app/shared/models/vc.models.ts
+++ b/ui-ngx/src/app/shared/models/vc.models.ts
@@ -33,16 +33,18 @@ export const exportableEntityTypes: Array = [
EntityType.WIDGET_TYPE,
EntityType.WIDGETS_BUNDLE,
EntityType.TB_RESOURCE,
+ EntityType.OTA_PACKAGE,
EntityType.NOTIFICATION_TEMPLATE,
EntityType.NOTIFICATION_TARGET,
EntityType.NOTIFICATION_RULE
];
-export const entityTypesWithoutRelatedData: Set = new Set([
+export const entityTypesWithoutRelatedData = new Set([
EntityType.NOTIFICATION_TEMPLATE,
EntityType.NOTIFICATION_TARGET,
EntityType.NOTIFICATION_RULE,
- EntityType.TB_RESOURCE
+ EntityType.TB_RESOURCE,
+ EntityType.OTA_PACKAGE,
]);
export interface VersionCreateConfig {
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 55e136e031..a63089bb6c 100644
--- a/ui-ngx/src/assets/locale/locale.constant-en_US.json
+++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json
@@ -2584,6 +2584,8 @@
"type-tb-resources": "Resources",
"list-of-tb-resources": "{ count, plural, =1 {One resource} other {List of # resources} }",
"type-ota-package": "OTA package",
+ "type-ota-packages": "OTA packages",
+ "list-of-ota-packages": "{ count, plural, =1 {One OTA package} other {List of # OTA packages} }",
"type-rpc": "RPC",
"type-queue": "Queue",
"type-queue-stats": "Queue statistics",