diff --git a/ui-ngx/src/app/modules/home/components/iot-hub/device-install-dialog/device-install-dialog.component.ts b/ui-ngx/src/app/modules/home/components/iot-hub/device-install-dialog/device-install-dialog.component.ts index bcf03100f7..b392c995c4 100644 --- a/ui-ngx/src/app/modules/home/components/iot-hub/device-install-dialog/device-install-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/iot-hub/device-install-dialog/device-install-dialog.component.ts @@ -30,6 +30,9 @@ import { DeviceProfileService } from '@core/http/device-profile.service'; import { DeviceService } from '@core/http/device.service'; import { DashboardService } from '@core/http/dashboard.service'; import { RuleChainService } from '@core/http/rule-chain.service'; +import { AttributeService } from '@core/http/attribute.service'; +import { AttributeScope } from '@shared/models/telemetry/telemetry.models'; +import { EntityId } from '@shared/models/id/entity-id'; import { connectivityTypeTranslations, DeviceInstallStep, @@ -108,6 +111,7 @@ export class TbDeviceInstallDialogComponent extends DialogComponent a.key === 'active_connectors'); + if (existing?.value && Array.isArray(existing.value)) { + activeConnectors = existing.value; + } + } catch (_e) { + // First connector — no existing attributes + } + + // Append connector name + if (!activeConnectors.includes(connectorName)) { + activeConnectors.push(connectorName); + } + + // Save attributes + await firstValueFrom(this.attributeService.saveEntityAttributes( + gatewayEntityId, AttributeScope.SHARED_SCOPE, + [ + { key: 'active_connectors', value: activeConnectors }, + { key: connectorName, value: template } + ], + {ignoreErrors: true} + )); + + return { id: gatewayOutput.id, name: connectorName }; + } case InstallStepType.DASHBOARD: { const result = await firstValueFrom(this.dashboardService.saveDashboard(template, {ignoreErrors: true})); return { id: result.id.id, name: result.title }; @@ -522,10 +577,40 @@ export class TbDeviceInstallDialogComponent extends DialogComponent { + const entityType = this.stepTypeToEntityType(step.type); + if (!entityType) { + return; + } + const entityId = { entityType, id: output.id } as EntityId; + if (step.serverAttributes) { + const raw = this.zipFiles.get(step.serverAttributes); + if (raw) { + const resolved = JSON.parse(this.resolveVariables(raw)); + const attrs = Object.entries(resolved).map(([key, value]) => ({ key, value })); + await firstValueFrom(this.attributeService.saveEntityAttributes( + entityId, AttributeScope.SERVER_SCOPE, attrs, {ignoreErrors: true} + )); + } + } + if (step.sharedAttributes) { + const raw = this.zipFiles.get(step.sharedAttributes); + if (raw) { + const resolved = JSON.parse(this.resolveVariables(raw)); + const attrs = Object.entries(resolved).map(([key, value]) => ({ key, value })); + await firstValueFrom(this.attributeService.saveEntityAttributes( + entityId, AttributeScope.SHARED_SCOPE, attrs, {ignoreErrors: true} + )); + } + } + } + private stepTypeToEntityType(stepType: InstallStepType): string | null { switch (stepType) { case InstallStepType.DEVICE_PROFILE: return 'DEVICE_PROFILE'; case InstallStepType.DEVICE: return 'DEVICE'; + case InstallStepType.GATEWAY: return 'DEVICE'; + case InstallStepType.GATEWAY_CONNECTOR: return 'DEVICE'; case InstallStepType.DASHBOARD: return 'DASHBOARD'; case InstallStepType.RULE_CHAIN: return 'RULE_CHAIN'; default: return null; diff --git a/ui-ngx/src/app/shared/models/iot-hub/device-package.models.ts b/ui-ngx/src/app/shared/models/iot-hub/device-package.models.ts index 38cfe637a4..06561bfd9a 100644 --- a/ui-ngx/src/app/shared/models/iot-hub/device-package.models.ts +++ b/ui-ngx/src/app/shared/models/iot-hub/device-package.models.ts @@ -53,6 +53,8 @@ export enum InstallStepType { CONVERTER = 'CONVERTER', INTEGRATION = 'INTEGRATION', DEVICE = 'DEVICE', + GATEWAY = 'GATEWAY', + GATEWAY_CONNECTOR = 'GATEWAY_CONNECTOR', DASHBOARD = 'DASHBOARD', RULE_CHAIN = 'RULE_CHAIN' } @@ -60,6 +62,8 @@ export enum InstallStepType { export const ENTITY_STEP_TYPES: Set = new Set([ InstallStepType.DEVICE_PROFILE, InstallStepType.DEVICE, + InstallStepType.GATEWAY, + InstallStepType.GATEWAY_CONNECTOR, InstallStepType.DASHBOARD, InstallStepType.RULE_CHAIN ]); @@ -67,6 +71,8 @@ export const ENTITY_STEP_TYPES: Set = new Set([ export const stepTypeAliasMap: Record = { [InstallStepType.DEVICE_PROFILE]: 'deviceProfile', [InstallStepType.DEVICE]: 'device', + [InstallStepType.GATEWAY]: 'gateway', + [InstallStepType.GATEWAY_CONNECTOR]: 'gatewayConnector', [InstallStepType.DASHBOARD]: 'dashboard', [InstallStepType.RULE_CHAIN]: 'ruleChain' }; @@ -76,6 +82,8 @@ export interface DeviceInstallStep { name: string; file?: string; template?: string; + serverAttributes?: string; + sharedAttributes?: string; } export interface DevicePackageInfo { @@ -121,6 +129,7 @@ export interface EntityStepOutput { id: string; name: string; token?: string; + dockerComposeUrl?: string; } export type EntityStepStatus = 'pending' | 'running' | 'success' | 'error'; 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 a4cebfedc3..fa55e3cea0 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -3892,6 +3892,8 @@ "device-install-step-type-DASHBOARD": "Dashboard", "device-install-step-type-DEVICE": "Device", "device-install-step-type-DEVICE_PROFILE": "Device Profile", + "device-install-step-type-GATEWAY": "Gateway", + "device-install-step-type-GATEWAY_CONNECTOR": "Gateway Connector", "device-install-step-type-RULE_CHAIN": "Rule Chain", "device-install-title": "Install \"{{name}}\"", "device-install-complete-title": "Install \"{{name}}\" — Complete",