Browse Source
- Full entity stack: IotHubInstalledItem with ID, descriptors, JPA entity, DAO (extends JpaAbstractDao), service - Controller endpoints: install, list (paginated), list infos, get by itemId, delete - Install flow saves descriptor with entity IDs, delete cascades to installed entities - Frontend installed items page with search, pagination, type chips, action buttons - Browse page shows "Installed" label on cards, detail dialog shows "Open details" for installed items - Installed item infos fetched on browse page init to track installation statepull/15347/head
40 changed files with 1763 additions and 69 deletions
@ -0,0 +1,40 @@ |
|||||
|
/** |
||||
|
* 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. |
||||
|
*/ |
||||
|
package org.thingsboard.server.service.iot_hub; |
||||
|
|
||||
|
import lombok.AllArgsConstructor; |
||||
|
import lombok.Data; |
||||
|
import lombok.NoArgsConstructor; |
||||
|
import org.thingsboard.server.common.data.iot_hub.IotHubInstalledItemDescriptor; |
||||
|
|
||||
|
@Data |
||||
|
@NoArgsConstructor |
||||
|
@AllArgsConstructor |
||||
|
public class InstallItemVersionResult { |
||||
|
|
||||
|
private boolean success; |
||||
|
private String errorMessage; |
||||
|
private IotHubInstalledItemDescriptor descriptor; |
||||
|
|
||||
|
public static InstallItemVersionResult success(IotHubInstalledItemDescriptor descriptor) { |
||||
|
return new InstallItemVersionResult(true, null, descriptor); |
||||
|
} |
||||
|
|
||||
|
public static InstallItemVersionResult error(String errorMessage) { |
||||
|
return new InstallItemVersionResult(false, errorMessage, null); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,34 @@ |
|||||
|
/** |
||||
|
* 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. |
||||
|
*/ |
||||
|
package org.thingsboard.server.common.data.id; |
||||
|
|
||||
|
import com.fasterxml.jackson.annotation.JsonCreator; |
||||
|
import com.fasterxml.jackson.annotation.JsonProperty; |
||||
|
|
||||
|
import java.util.UUID; |
||||
|
|
||||
|
public class IotHubInstalledItemId extends UUIDBased { |
||||
|
|
||||
|
@JsonCreator |
||||
|
public IotHubInstalledItemId(@JsonProperty("id") UUID id) { |
||||
|
super(id); |
||||
|
} |
||||
|
|
||||
|
public static IotHubInstalledItemId fromString(String id) { |
||||
|
return new IotHubInstalledItemId(UUID.fromString(id)); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,26 @@ |
|||||
|
/** |
||||
|
* 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. |
||||
|
*/ |
||||
|
package org.thingsboard.server.common.data.iot_hub; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
import org.thingsboard.server.common.data.id.CalculatedFieldId; |
||||
|
|
||||
|
@Data |
||||
|
public class CalculatedFieldInstalledItemDescriptor implements IotHubInstalledItemDescriptor { |
||||
|
|
||||
|
private CalculatedFieldId calculatedFieldId; |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,26 @@ |
|||||
|
/** |
||||
|
* 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. |
||||
|
*/ |
||||
|
package org.thingsboard.server.common.data.iot_hub; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
import org.thingsboard.server.common.data.id.DashboardId; |
||||
|
|
||||
|
@Data |
||||
|
public class DashboardInstalledItemDescriptor implements IotHubInstalledItemDescriptor { |
||||
|
|
||||
|
private DashboardId dashboardId; |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,23 @@ |
|||||
|
/** |
||||
|
* 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. |
||||
|
*/ |
||||
|
package org.thingsboard.server.common.data.iot_hub; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
|
||||
|
@Data |
||||
|
public class DeviceInstalledItemDescriptor implements IotHubInstalledItemDescriptor { |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,47 @@ |
|||||
|
/** |
||||
|
* 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. |
||||
|
*/ |
||||
|
package org.thingsboard.server.common.data.iot_hub; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
import lombok.EqualsAndHashCode; |
||||
|
import org.thingsboard.server.common.data.BaseData; |
||||
|
import org.thingsboard.server.common.data.HasTenantId; |
||||
|
import org.thingsboard.server.common.data.id.IotHubInstalledItemId; |
||||
|
import org.thingsboard.server.common.data.id.TenantId; |
||||
|
|
||||
|
import java.util.UUID; |
||||
|
|
||||
|
@Data |
||||
|
@EqualsAndHashCode(callSuper = true) |
||||
|
public class IotHubInstalledItem extends BaseData<IotHubInstalledItemId> implements HasTenantId { |
||||
|
|
||||
|
private TenantId tenantId; |
||||
|
private UUID itemId; |
||||
|
private UUID itemVersionId; |
||||
|
private String itemName; |
||||
|
private String itemType; |
||||
|
private String version; |
||||
|
private IotHubInstalledItemDescriptor descriptor; |
||||
|
|
||||
|
public IotHubInstalledItem() { |
||||
|
super(); |
||||
|
} |
||||
|
|
||||
|
public IotHubInstalledItem(IotHubInstalledItemId id) { |
||||
|
super(id); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,38 @@ |
|||||
|
/** |
||||
|
* 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. |
||||
|
*/ |
||||
|
package org.thingsboard.server.common.data.iot_hub; |
||||
|
|
||||
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; |
||||
|
import com.fasterxml.jackson.annotation.JsonSubTypes; |
||||
|
import com.fasterxml.jackson.annotation.JsonSubTypes.Type; |
||||
|
import com.fasterxml.jackson.annotation.JsonTypeInfo; |
||||
|
|
||||
|
import java.io.Serializable; |
||||
|
|
||||
|
@JsonIgnoreProperties(ignoreUnknown = true) |
||||
|
@JsonTypeInfo( |
||||
|
use = JsonTypeInfo.Id.NAME, |
||||
|
property = "type") |
||||
|
@JsonSubTypes({ |
||||
|
@Type(name = "WIDGET", value = WidgetInstalledItemDescriptor.class), |
||||
|
@Type(name = "DASHBOARD", value = DashboardInstalledItemDescriptor.class), |
||||
|
@Type(name = "CALCULATED_FIELD", value = CalculatedFieldInstalledItemDescriptor.class), |
||||
|
@Type(name = "RULE_CHAIN", value = RuleChainInstalledItemDescriptor.class), |
||||
|
@Type(name = "DEVICE", value = DeviceInstalledItemDescriptor.class) |
||||
|
}) |
||||
|
public interface IotHubInstalledItemDescriptor extends Serializable { |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,32 @@ |
|||||
|
/** |
||||
|
* 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. |
||||
|
*/ |
||||
|
package org.thingsboard.server.common.data.iot_hub; |
||||
|
|
||||
|
import lombok.AllArgsConstructor; |
||||
|
import lombok.Data; |
||||
|
import lombok.NoArgsConstructor; |
||||
|
|
||||
|
import java.util.UUID; |
||||
|
|
||||
|
@Data |
||||
|
@AllArgsConstructor |
||||
|
@NoArgsConstructor |
||||
|
public class IotHubInstalledItemInfo { |
||||
|
|
||||
|
private UUID itemId; |
||||
|
private UUID itemVersionId; |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,26 @@ |
|||||
|
/** |
||||
|
* 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. |
||||
|
*/ |
||||
|
package org.thingsboard.server.common.data.iot_hub; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
import org.thingsboard.server.common.data.id.RuleChainId; |
||||
|
|
||||
|
@Data |
||||
|
public class RuleChainInstalledItemDescriptor implements IotHubInstalledItemDescriptor { |
||||
|
|
||||
|
private RuleChainId ruleChainId; |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,26 @@ |
|||||
|
/** |
||||
|
* 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. |
||||
|
*/ |
||||
|
package org.thingsboard.server.common.data.iot_hub; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
import org.thingsboard.server.common.data.id.WidgetTypeId; |
||||
|
|
||||
|
@Data |
||||
|
public class WidgetInstalledItemDescriptor implements IotHubInstalledItemDescriptor { |
||||
|
|
||||
|
private WidgetTypeId widgetTypeId; |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,41 @@ |
|||||
|
/** |
||||
|
* 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. |
||||
|
*/ |
||||
|
package org.thingsboard.server.dao.iot_hub; |
||||
|
|
||||
|
import org.thingsboard.server.common.data.id.TenantId; |
||||
|
import org.thingsboard.server.common.data.iot_hub.IotHubInstalledItem; |
||||
|
import org.thingsboard.server.common.data.iot_hub.IotHubInstalledItemInfo; |
||||
|
import org.thingsboard.server.common.data.page.PageData; |
||||
|
import org.thingsboard.server.common.data.page.PageLink; |
||||
|
import org.thingsboard.server.dao.Dao; |
||||
|
|
||||
|
import java.util.List; |
||||
|
import java.util.Optional; |
||||
|
import java.util.UUID; |
||||
|
|
||||
|
public interface IotHubInstalledItemDao extends Dao<IotHubInstalledItem> { |
||||
|
|
||||
|
Optional<IotHubInstalledItem> findByTenantIdAndItemId(TenantId tenantId, UUID itemId); |
||||
|
|
||||
|
PageData<IotHubInstalledItem> findByTenantId(TenantId tenantId, PageLink pageLink); |
||||
|
|
||||
|
List<IotHubInstalledItemInfo> findInstalledItemInfosByTenantId(TenantId tenantId); |
||||
|
|
||||
|
boolean deleteByTenantIdAndItemId(TenantId tenantId, UUID itemId); |
||||
|
|
||||
|
void deleteByTenantId(TenantId tenantId); |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,42 @@ |
|||||
|
/** |
||||
|
* 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. |
||||
|
*/ |
||||
|
package org.thingsboard.server.dao.iot_hub; |
||||
|
|
||||
|
import org.thingsboard.server.common.data.id.TenantId; |
||||
|
import org.thingsboard.server.common.data.iot_hub.IotHubInstalledItem; |
||||
|
import org.thingsboard.server.common.data.iot_hub.IotHubInstalledItemInfo; |
||||
|
import org.thingsboard.server.common.data.page.PageData; |
||||
|
import org.thingsboard.server.common.data.page.PageLink; |
||||
|
|
||||
|
import java.util.List; |
||||
|
import java.util.Optional; |
||||
|
import java.util.UUID; |
||||
|
|
||||
|
public interface IotHubInstalledItemService { |
||||
|
|
||||
|
IotHubInstalledItem save(TenantId tenantId, IotHubInstalledItem item); |
||||
|
|
||||
|
Optional<IotHubInstalledItem> findByTenantIdAndItemId(TenantId tenantId, UUID itemId); |
||||
|
|
||||
|
PageData<IotHubInstalledItem> findByTenantId(TenantId tenantId, PageLink pageLink); |
||||
|
|
||||
|
List<IotHubInstalledItemInfo> findInstalledItemInfosByTenantId(TenantId tenantId); |
||||
|
|
||||
|
boolean deleteByTenantIdAndItemId(TenantId tenantId, UUID itemId); |
||||
|
|
||||
|
void deleteByTenantId(TenantId tenantId); |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,69 @@ |
|||||
|
/** |
||||
|
* 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. |
||||
|
*/ |
||||
|
package org.thingsboard.server.dao.iot_hub; |
||||
|
|
||||
|
import lombok.RequiredArgsConstructor; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.springframework.stereotype.Service; |
||||
|
import org.thingsboard.server.common.data.id.TenantId; |
||||
|
import org.thingsboard.server.common.data.iot_hub.IotHubInstalledItem; |
||||
|
import org.thingsboard.server.common.data.iot_hub.IotHubInstalledItemInfo; |
||||
|
import org.thingsboard.server.common.data.page.PageData; |
||||
|
import org.thingsboard.server.common.data.page.PageLink; |
||||
|
|
||||
|
import java.util.List; |
||||
|
import java.util.Optional; |
||||
|
import java.util.UUID; |
||||
|
|
||||
|
@Service |
||||
|
@RequiredArgsConstructor |
||||
|
@Slf4j |
||||
|
class IotHubInstalledItemServiceImpl implements IotHubInstalledItemService { |
||||
|
|
||||
|
private final IotHubInstalledItemDao iotHubInstalledItemDao; |
||||
|
|
||||
|
@Override |
||||
|
public IotHubInstalledItem save(TenantId tenantId, IotHubInstalledItem item) { |
||||
|
log.debug("[{}] Saving IoT Hub installed item: {}", tenantId, item); |
||||
|
return iotHubInstalledItemDao.save(tenantId, item); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public Optional<IotHubInstalledItem> findByTenantIdAndItemId(TenantId tenantId, UUID itemId) { |
||||
|
return iotHubInstalledItemDao.findByTenantIdAndItemId(tenantId, itemId); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public PageData<IotHubInstalledItem> findByTenantId(TenantId tenantId, PageLink pageLink) { |
||||
|
return iotHubInstalledItemDao.findByTenantId(tenantId, pageLink); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public List<IotHubInstalledItemInfo> findInstalledItemInfosByTenantId(TenantId tenantId) { |
||||
|
return iotHubInstalledItemDao.findInstalledItemInfosByTenantId(tenantId); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public boolean deleteByTenantIdAndItemId(TenantId tenantId, UUID itemId) { |
||||
|
return iotHubInstalledItemDao.deleteByTenantIdAndItemId(tenantId, itemId); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void deleteByTenantId(TenantId tenantId) { |
||||
|
iotHubInstalledItemDao.deleteByTenantId(tenantId); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,110 @@ |
|||||
|
/** |
||||
|
* 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. |
||||
|
*/ |
||||
|
package org.thingsboard.server.dao.model.sql; |
||||
|
|
||||
|
import io.hypersistence.utils.hibernate.type.json.JsonBinaryType; |
||||
|
import jakarta.persistence.Column; |
||||
|
import jakarta.persistence.Entity; |
||||
|
import jakarta.persistence.Table; |
||||
|
import lombok.Getter; |
||||
|
import lombok.Setter; |
||||
|
import lombok.ToString; |
||||
|
import org.hibernate.annotations.Type; |
||||
|
import org.hibernate.proxy.HibernateProxy; |
||||
|
import org.thingsboard.server.common.data.id.IotHubInstalledItemId; |
||||
|
import org.thingsboard.server.common.data.id.TenantId; |
||||
|
import org.thingsboard.server.common.data.iot_hub.IotHubInstalledItem; |
||||
|
import org.thingsboard.server.common.data.iot_hub.IotHubInstalledItemDescriptor; |
||||
|
import org.thingsboard.server.dao.model.BaseSqlEntity; |
||||
|
import org.thingsboard.server.dao.model.ModelConstants; |
||||
|
|
||||
|
import java.util.Objects; |
||||
|
import java.util.UUID; |
||||
|
|
||||
|
@Getter |
||||
|
@Setter |
||||
|
@ToString |
||||
|
@Entity |
||||
|
@Table(name = ModelConstants.IOT_HUB_INSTALLED_ITEM_TABLE_NAME) |
||||
|
public class IotHubInstalledItemEntity extends BaseSqlEntity<IotHubInstalledItem> { |
||||
|
|
||||
|
@Column(name = ModelConstants.IOT_HUB_INSTALLED_ITEM_TENANT_ID_COLUMN, nullable = false, columnDefinition = "UUID") |
||||
|
private UUID tenantId; |
||||
|
|
||||
|
@Column(name = ModelConstants.IOT_HUB_INSTALLED_ITEM_ITEM_ID_COLUMN, nullable = false, columnDefinition = "UUID") |
||||
|
private UUID itemId; |
||||
|
|
||||
|
@Column(name = ModelConstants.IOT_HUB_INSTALLED_ITEM_ITEM_VERSION_ID_COLUMN, nullable = false, columnDefinition = "UUID") |
||||
|
private UUID itemVersionId; |
||||
|
|
||||
|
@Column(name = ModelConstants.IOT_HUB_INSTALLED_ITEM_ITEM_NAME_COLUMN, nullable = false) |
||||
|
private String itemName; |
||||
|
|
||||
|
@Column(name = ModelConstants.IOT_HUB_INSTALLED_ITEM_ITEM_TYPE_COLUMN, nullable = false) |
||||
|
private String itemType; |
||||
|
|
||||
|
@Column(name = ModelConstants.IOT_HUB_INSTALLED_ITEM_VERSION_COLUMN, nullable = false) |
||||
|
private String version; |
||||
|
|
||||
|
@Type(JsonBinaryType.class) |
||||
|
@Column(name = ModelConstants.IOT_HUB_INSTALLED_ITEM_DESCRIPTOR_COLUMN, nullable = false, columnDefinition = "JSONB") |
||||
|
private IotHubInstalledItemDescriptor descriptor; |
||||
|
|
||||
|
public IotHubInstalledItemEntity() { |
||||
|
} |
||||
|
|
||||
|
public IotHubInstalledItemEntity(IotHubInstalledItem item) { |
||||
|
super(item); |
||||
|
tenantId = getTenantUuid(item.getTenantId()); |
||||
|
itemId = item.getItemId(); |
||||
|
itemVersionId = item.getItemVersionId(); |
||||
|
itemName = item.getItemName(); |
||||
|
itemType = item.getItemType(); |
||||
|
version = item.getVersion(); |
||||
|
descriptor = item.getDescriptor(); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public IotHubInstalledItem toData() { |
||||
|
var item = new IotHubInstalledItem(new IotHubInstalledItemId(id)); |
||||
|
item.setCreatedTime(createdTime); |
||||
|
item.setTenantId(TenantId.fromUUID(tenantId)); |
||||
|
item.setItemId(itemId); |
||||
|
item.setItemVersionId(itemVersionId); |
||||
|
item.setItemName(itemName); |
||||
|
item.setItemType(itemType); |
||||
|
item.setVersion(version); |
||||
|
item.setDescriptor(descriptor); |
||||
|
return item; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public final boolean equals(Object o) { |
||||
|
if (this == o) return true; |
||||
|
if (o == null) return false; |
||||
|
Class<?> oEffectiveClass = o instanceof HibernateProxy ? ((HibernateProxy) o).getHibernateLazyInitializer().getPersistentClass() : o.getClass(); |
||||
|
Class<?> thisEffectiveClass = this instanceof HibernateProxy ? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass() : this.getClass(); |
||||
|
if (thisEffectiveClass != oEffectiveClass) return false; |
||||
|
IotHubInstalledItemEntity that = (IotHubInstalledItemEntity) o; |
||||
|
return getId() != null && Objects.equals(getId(), that.getId()); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public final int hashCode() { |
||||
|
return this instanceof HibernateProxy ? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass().hashCode() : getClass().hashCode(); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,60 @@ |
|||||
|
/** |
||||
|
* 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. |
||||
|
*/ |
||||
|
package org.thingsboard.server.dao.sql.iot_hub; |
||||
|
|
||||
|
import org.springframework.data.domain.Page; |
||||
|
import org.springframework.data.domain.Pageable; |
||||
|
import org.springframework.data.jpa.repository.JpaRepository; |
||||
|
import org.springframework.data.jpa.repository.Modifying; |
||||
|
import org.springframework.data.jpa.repository.Query; |
||||
|
import org.springframework.data.repository.query.Param; |
||||
|
import org.springframework.transaction.annotation.Transactional; |
||||
|
import org.thingsboard.server.common.data.iot_hub.IotHubInstalledItemInfo; |
||||
|
import org.thingsboard.server.dao.model.sql.IotHubInstalledItemEntity; |
||||
|
|
||||
|
import java.util.List; |
||||
|
import java.util.Optional; |
||||
|
import java.util.UUID; |
||||
|
|
||||
|
interface IotHubInstalledItemRepository extends JpaRepository<IotHubInstalledItemEntity, UUID> { |
||||
|
|
||||
|
Optional<IotHubInstalledItemEntity> findByTenantIdAndItemId(UUID tenantId, UUID itemId); |
||||
|
|
||||
|
@Query("SELECT new org.thingsboard.server.common.data.iot_hub.IotHubInstalledItemInfo(item.itemId, item.itemVersionId) " + |
||||
|
"FROM IotHubInstalledItemEntity item WHERE item.tenantId = :tenantId") |
||||
|
List<IotHubInstalledItemInfo> findInstalledItemInfosByTenantId(@Param("tenantId") UUID tenantId); |
||||
|
|
||||
|
@Query(""" |
||||
|
SELECT item FROM IotHubInstalledItemEntity item |
||||
|
WHERE item.tenantId = :tenantId |
||||
|
AND (:textSearch IS NULL |
||||
|
OR ilike(item.itemName, CONCAT('%', :textSearch, '%')) = true |
||||
|
OR ilike(item.itemType, CONCAT('%', :textSearch, '%')) = true |
||||
|
OR ilike(item.version, CONCAT('%', :textSearch, '%')) = true) |
||||
|
""") |
||||
|
Page<IotHubInstalledItemEntity> findByTenantId(@Param("tenantId") UUID tenantId, @Param("textSearch") String textSearch, Pageable pageable); |
||||
|
|
||||
|
@Transactional |
||||
|
@Modifying |
||||
|
@Query("DELETE FROM IotHubInstalledItemEntity item WHERE item.tenantId = :tenantId AND item.itemId = :itemId") |
||||
|
int deleteByTenantIdAndItemId(@Param("tenantId") UUID tenantId, @Param("itemId") UUID itemId); |
||||
|
|
||||
|
@Transactional |
||||
|
@Modifying |
||||
|
@Query(value = "DELETE FROM iot_hub_installed_item WHERE tenant_id = :tenantId", nativeQuery = true) |
||||
|
void deleteByTenantId(@Param("tenantId") UUID tenantId); |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,100 @@ |
|||||
|
/** |
||||
|
* 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. |
||||
|
*/ |
||||
|
package org.thingsboard.server.dao.sql.iot_hub; |
||||
|
|
||||
|
import lombok.RequiredArgsConstructor; |
||||
|
import org.apache.commons.lang3.StringUtils; |
||||
|
import org.springframework.data.domain.PageRequest; |
||||
|
import org.springframework.data.domain.Sort; |
||||
|
import org.springframework.data.jpa.repository.JpaRepository; |
||||
|
import org.springframework.stereotype.Component; |
||||
|
import org.thingsboard.server.common.data.id.TenantId; |
||||
|
import org.thingsboard.server.common.data.iot_hub.IotHubInstalledItem; |
||||
|
import org.thingsboard.server.common.data.iot_hub.IotHubInstalledItemInfo; |
||||
|
import org.thingsboard.server.common.data.page.PageData; |
||||
|
import org.thingsboard.server.common.data.page.PageLink; |
||||
|
import org.thingsboard.server.common.data.page.SortOrder; |
||||
|
import org.thingsboard.server.dao.DaoUtil; |
||||
|
import org.thingsboard.server.dao.iot_hub.IotHubInstalledItemDao; |
||||
|
import org.thingsboard.server.dao.model.sql.IotHubInstalledItemEntity; |
||||
|
import org.thingsboard.server.dao.sql.JpaAbstractDao; |
||||
|
import org.thingsboard.server.dao.util.SqlDao; |
||||
|
|
||||
|
import java.util.List; |
||||
|
import java.util.Optional; |
||||
|
import java.util.UUID; |
||||
|
|
||||
|
@SqlDao |
||||
|
@Component |
||||
|
@RequiredArgsConstructor |
||||
|
class JpaIotHubInstalledItemDao extends JpaAbstractDao<IotHubInstalledItemEntity, IotHubInstalledItem> implements IotHubInstalledItemDao { |
||||
|
|
||||
|
private final IotHubInstalledItemRepository repository; |
||||
|
|
||||
|
@Override |
||||
|
public Optional<IotHubInstalledItem> findByTenantIdAndItemId(TenantId tenantId, UUID itemId) { |
||||
|
return repository.findByTenantIdAndItemId(tenantId.getId(), itemId).map(DaoUtil::getData); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public PageData<IotHubInstalledItem> findByTenantId(TenantId tenantId, PageLink pageLink) { |
||||
|
return DaoUtil.toPageData(repository.findByTenantId( |
||||
|
tenantId.getId(), |
||||
|
StringUtils.defaultIfEmpty(pageLink.getTextSearch(), null), |
||||
|
toPageRequest(pageLink)) |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public boolean deleteByTenantIdAndItemId(TenantId tenantId, UUID itemId) { |
||||
|
return repository.deleteByTenantIdAndItemId(tenantId.getId(), itemId) > 0; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public List<IotHubInstalledItemInfo> findInstalledItemInfosByTenantId(TenantId tenantId) { |
||||
|
return repository.findInstalledItemInfosByTenantId(tenantId.getId()); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void deleteByTenantId(TenantId tenantId) { |
||||
|
repository.deleteByTenantId(tenantId.getId()); |
||||
|
} |
||||
|
|
||||
|
private static PageRequest toPageRequest(PageLink pageLink) { |
||||
|
Sort sort; |
||||
|
SortOrder sortOrder = pageLink.getSortOrder(); |
||||
|
if (sortOrder == null) { |
||||
|
sort = Sort.by(Sort.Direction.DESC, "createdTime"); |
||||
|
} else { |
||||
|
sort = Sort.by( |
||||
|
Sort.Direction.fromString(sortOrder.getDirection().name()), |
||||
|
sortOrder.getProperty() |
||||
|
).and(Sort.by(Sort.Direction.ASC, "id")); |
||||
|
} |
||||
|
return PageRequest.of(pageLink.getPage(), pageLink.getPageSize(), sort); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected Class<IotHubInstalledItemEntity> getEntityClass() { |
||||
|
return IotHubInstalledItemEntity.class; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected JpaRepository<IotHubInstalledItemEntity, UUID> getRepository() { |
||||
|
return repository; |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,116 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
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. |
||||
|
|
||||
|
--> |
||||
|
<div class="tb-iot-hub-installed-items-container"> |
||||
|
<a class="tb-iot-hub-back-link" (click)="navigateToMarketplace()"> |
||||
|
<mat-icon>arrow_back</mat-icon> |
||||
|
{{ 'iot-hub.back-to-marketplace' | translate }} |
||||
|
</a> |
||||
|
<div class="tb-iot-hub-installed-items-header"> |
||||
|
<h2>{{ 'iot-hub.installed-items' | translate }}</h2> |
||||
|
</div> |
||||
|
|
||||
|
<div class="tb-iot-hub-installed-search-row"> |
||||
|
<mat-form-field appearance="outline" class="tb-iot-hub-installed-search-field"> |
||||
|
<mat-icon matPrefix>search</mat-icon> |
||||
|
<input matInput |
||||
|
[placeholder]="'iot-hub.search-installed-items' | translate" |
||||
|
[value]="textSearch" |
||||
|
(input)="onSearchChange($any($event.target).value)"> |
||||
|
@if (textSearch) { |
||||
|
<button matSuffix mat-icon-button (click)="onSearchChange('')"> |
||||
|
<mat-icon>close</mat-icon> |
||||
|
</button> |
||||
|
} |
||||
|
</mat-form-field> |
||||
|
</div> |
||||
|
|
||||
|
<div class="tb-iot-hub-installed-table-container"> |
||||
|
@if (isLoading) { |
||||
|
<mat-progress-bar mode="indeterminate"></mat-progress-bar> |
||||
|
} |
||||
|
|
||||
|
<table mat-table [dataSource]="dataSource" matSort> |
||||
|
<ng-container matColumnDef="itemName"> |
||||
|
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'iot-hub.item-name' | translate }}</th> |
||||
|
<td mat-cell *matCellDef="let item">{{ item.itemName }}</td> |
||||
|
</ng-container> |
||||
|
|
||||
|
<ng-container matColumnDef="itemType"> |
||||
|
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'iot-hub.item-type' | translate }}</th> |
||||
|
<td mat-cell *matCellDef="let item"> |
||||
|
<span class="tb-iot-hub-type-chip" [ngClass]="getItemTypeChipClass(item.itemType)">{{ getItemTypeLabel(item.itemType) }}</span> |
||||
|
</td> |
||||
|
</ng-container> |
||||
|
|
||||
|
<ng-container matColumnDef="version"> |
||||
|
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'iot-hub.item-version' | translate }}</th> |
||||
|
<td mat-cell *matCellDef="let item">{{ item.version }}</td> |
||||
|
</ng-container> |
||||
|
|
||||
|
<ng-container matColumnDef="createdTime"> |
||||
|
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'iot-hub.installed-date' | translate }}</th> |
||||
|
<td mat-cell *matCellDef="let item">{{ item.createdTime | date:'medium' }}</td> |
||||
|
</ng-container> |
||||
|
|
||||
|
<ng-container matColumnDef="actions"> |
||||
|
<th mat-header-cell *matHeaderCellDef></th> |
||||
|
<td mat-cell *matCellDef="let item"> |
||||
|
<button mat-icon-button |
||||
|
[matTooltip]="'iot-hub.view-item-details' | translate" |
||||
|
matTooltipPosition="above" |
||||
|
(click)="viewItemDetails(item); $event.stopPropagation()"> |
||||
|
<mat-icon>info_outline</mat-icon> |
||||
|
</button> |
||||
|
@if (getEntityId(item)) { |
||||
|
<button mat-icon-button color="primary" |
||||
|
[matTooltip]="'iot-hub.open-installed-entity' | translate" |
||||
|
matTooltipPosition="above" |
||||
|
(click)="openEntity(item); $event.stopPropagation()"> |
||||
|
<mat-icon>open_in_new</mat-icon> |
||||
|
</button> |
||||
|
} |
||||
|
<button mat-icon-button color="warn" |
||||
|
[matTooltip]="'action.delete' | translate" |
||||
|
matTooltipPosition="above" |
||||
|
(click)="deleteItem(item); $event.stopPropagation()"> |
||||
|
<mat-icon>delete</mat-icon> |
||||
|
</button> |
||||
|
</td> |
||||
|
</ng-container> |
||||
|
|
||||
|
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr> |
||||
|
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr> |
||||
|
|
||||
|
<tr class="mat-row" *matNoDataRow> |
||||
|
<td class="mat-cell" [attr.colspan]="displayedColumns.length"> |
||||
|
<div class="tb-iot-hub-installed-empty"> |
||||
|
{{ 'iot-hub.no-installed-items' | translate }} |
||||
|
</div> |
||||
|
</td> |
||||
|
</tr> |
||||
|
</table> |
||||
|
|
||||
|
<mat-paginator |
||||
|
[length]="totalElements" |
||||
|
[pageSize]="pageSize" |
||||
|
[pageIndex]="pageIndex" |
||||
|
[pageSizeOptions]="[10, 20, 50]" |
||||
|
(page)="onPageChange($event)"> |
||||
|
</mat-paginator> |
||||
|
</div> |
||||
|
</div> |
||||
@ -0,0 +1,202 @@ |
|||||
|
/** |
||||
|
* 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. |
||||
|
*/ |
||||
|
|
||||
|
:host { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
height: 100%; |
||||
|
padding: 16px 24px; |
||||
|
overflow: auto; |
||||
|
} |
||||
|
|
||||
|
.tb-iot-hub-installed-items-container { |
||||
|
max-width: 1400px; |
||||
|
margin: 0 auto; |
||||
|
width: 100%; |
||||
|
background: white; |
||||
|
padding: 24px; |
||||
|
border-radius: 4px; |
||||
|
box-shadow: 0 0 1px rgba(0, 0, 0, 0.14), 0 1px 1px rgba(0, 0, 0, 0.12); |
||||
|
} |
||||
|
|
||||
|
.tb-iot-hub-back-link { |
||||
|
display: inline-flex; |
||||
|
align-items: center; |
||||
|
gap: 4px; |
||||
|
font-size: 13px; |
||||
|
color: rgba(0, 0, 0, 0.54); |
||||
|
cursor: pointer; |
||||
|
text-decoration: none; |
||||
|
border-bottom: none; |
||||
|
margin-bottom: 12px; |
||||
|
|
||||
|
mat-icon { |
||||
|
font-size: 18px; |
||||
|
width: 18px; |
||||
|
height: 18px; |
||||
|
} |
||||
|
|
||||
|
&:hover { |
||||
|
color: rgba(0, 0, 0, 0.87); |
||||
|
text-decoration: none; |
||||
|
border-bottom: none; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.tb-iot-hub-installed-items-header { |
||||
|
margin-bottom: 24px; |
||||
|
|
||||
|
h2 { |
||||
|
font-size: 28px; |
||||
|
font-weight: 700; |
||||
|
margin: 0; |
||||
|
color: rgba(0, 0, 0, 0.87); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.tb-iot-hub-installed-search-row { |
||||
|
display: flex; |
||||
|
gap: 12px; |
||||
|
margin-bottom: 16px; |
||||
|
align-items: center; |
||||
|
} |
||||
|
|
||||
|
.tb-iot-hub-installed-search-field { |
||||
|
flex: 1; |
||||
|
|
||||
|
::ng-deep { |
||||
|
.mat-mdc-text-field-wrapper { |
||||
|
height: 42px; |
||||
|
padding: 0 12px; |
||||
|
} |
||||
|
|
||||
|
.mat-mdc-form-field-flex { |
||||
|
height: 42px; |
||||
|
align-items: center; |
||||
|
} |
||||
|
|
||||
|
.mdc-notched-outline__leading, |
||||
|
.mdc-notched-outline__notch, |
||||
|
.mdc-notched-outline__trailing { |
||||
|
border-color: #d5dbe3; |
||||
|
} |
||||
|
|
||||
|
.mdc-notched-outline__leading { |
||||
|
border-radius: 8px 0 0 8px; |
||||
|
} |
||||
|
|
||||
|
.mdc-notched-outline__trailing { |
||||
|
border-radius: 0 8px 8px 0; |
||||
|
} |
||||
|
|
||||
|
.mat-mdc-form-field-infix { |
||||
|
padding-top: 0; |
||||
|
padding-bottom: 0; |
||||
|
min-height: unset; |
||||
|
} |
||||
|
|
||||
|
.mdc-notched-outline__notch { |
||||
|
display: none; |
||||
|
} |
||||
|
|
||||
|
.mat-mdc-floating-label { |
||||
|
display: none; |
||||
|
} |
||||
|
|
||||
|
.mat-mdc-form-field-subscript-wrapper { |
||||
|
display: none; |
||||
|
} |
||||
|
|
||||
|
input.mat-mdc-input-element { |
||||
|
font-size: 14px; |
||||
|
color: #3e4a59; |
||||
|
|
||||
|
&::placeholder { |
||||
|
color: #8b95a2; |
||||
|
opacity: 1; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.mat-icon { |
||||
|
color: #8b95a2; |
||||
|
font-size: 20px; |
||||
|
width: 20px; |
||||
|
height: 20px; |
||||
|
} |
||||
|
|
||||
|
.mat-mdc-form-field-icon-prefix { |
||||
|
padding: 0 8px 0 0; |
||||
|
|
||||
|
> .mat-icon { |
||||
|
padding: 0; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.mat-mdc-form-field-icon-suffix { |
||||
|
padding: 0; |
||||
|
|
||||
|
.mat-mdc-icon-button { |
||||
|
width: 32px; |
||||
|
height: 32px; |
||||
|
padding: 4px; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
&.mat-focused ::ng-deep { |
||||
|
.mdc-notched-outline__leading, |
||||
|
.mdc-notched-outline__notch, |
||||
|
.mdc-notched-outline__trailing { |
||||
|
border-color: #8b95a2; |
||||
|
border-width: 1px; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.tb-iot-hub-installed-table-container { |
||||
|
border-radius: 4px; |
||||
|
overflow: hidden; |
||||
|
|
||||
|
table { |
||||
|
width: 100%; |
||||
|
} |
||||
|
|
||||
|
.mat-column-actions { |
||||
|
text-align: right; |
||||
|
white-space: nowrap; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.tb-iot-hub-type-chip { |
||||
|
padding: 2px 10px; |
||||
|
border-radius: 12px; |
||||
|
font-size: 12px; |
||||
|
font-weight: 500; |
||||
|
white-space: nowrap; |
||||
|
|
||||
|
&.tb-type-widget { background: #e3f2fd; color: #1565c0; } |
||||
|
&.tb-type-dashboard { background: #e8f5e9; color: #2e7d32; } |
||||
|
&.tb-type-calc-field { background: #fff3e0; color: #e65100; } |
||||
|
&.tb-type-rule-chain { background: #f3e5f5; color: #7b1fa2; } |
||||
|
&.tb-type-device { background: #eceff1; color: #455a64; } |
||||
|
} |
||||
|
|
||||
|
.tb-iot-hub-installed-empty { |
||||
|
padding: 48px 24px; |
||||
|
text-align: center; |
||||
|
color: rgba(0, 0, 0, 0.54); |
||||
|
font-size: 14px; |
||||
|
} |
||||
@ -0,0 +1,210 @@ |
|||||
|
///
|
||||
|
/// 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, AfterViewInit, ViewChild } from '@angular/core'; |
||||
|
import { Router } from '@angular/router'; |
||||
|
import { MatDialog } from '@angular/material/dialog'; |
||||
|
import { MatSort } from '@angular/material/sort'; |
||||
|
import { MatPaginator } from '@angular/material/paginator'; |
||||
|
import { IotHubApiService } from '@core/http/iot-hub-api.service'; |
||||
|
import { DialogService } from '@core/services/dialog.service'; |
||||
|
import { TranslateService } from '@ngx-translate/core'; |
||||
|
import { Store } from '@ngrx/store'; |
||||
|
import { AppState } from '@core/core.state'; |
||||
|
import { ActionNotificationShow } from '@core/notification/notification.actions'; |
||||
|
import { Subject } from 'rxjs'; |
||||
|
import { debounceTime } from 'rxjs/operators'; |
||||
|
import { PageLink } from '@shared/models/page/page-link'; |
||||
|
import { Direction, SortOrder } from '@shared/models/page/sort-order'; |
||||
|
import { IotHubInstalledItem } from '@shared/models/iot-hub/iot-hub-installed-item.models'; |
||||
|
import { ItemType, itemTypeTranslations } from '@shared/models/iot-hub/iot-hub-item.models'; |
||||
|
import { EntityType } from '@shared/models/entity-type.models'; |
||||
|
import { getEntityDetailsPageURL } from '@core/utils'; |
||||
|
import { TbIotHubItemDetailDialogComponent, IotHubItemDetailDialogData } from './iot-hub-item-detail-dialog.component'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'tb-iot-hub-installed-items', |
||||
|
standalone: false, |
||||
|
templateUrl: './iot-hub-installed-items.component.html', |
||||
|
styleUrls: ['./iot-hub-installed-items.component.scss'] |
||||
|
}) |
||||
|
export class TbIotHubInstalledItemsComponent implements OnInit, AfterViewInit { |
||||
|
|
||||
|
displayedColumns: string[] = ['itemName', 'itemType', 'version', 'createdTime', 'actions']; |
||||
|
dataSource: IotHubInstalledItem[] = []; |
||||
|
totalElements = 0; |
||||
|
pageSize = 10; |
||||
|
pageIndex = 0; |
||||
|
isLoading = false; |
||||
|
textSearch = ''; |
||||
|
|
||||
|
private searchSubject = new Subject<string>(); |
||||
|
|
||||
|
@ViewChild(MatSort, {static: true}) sort: MatSort; |
||||
|
@ViewChild(MatPaginator, {static: true}) paginator: MatPaginator; |
||||
|
|
||||
|
private static readonly ITEM_TYPE_TO_ENTITY_TYPE: Record<string, EntityType> = { |
||||
|
'WIDGET': EntityType.WIDGET_TYPE, |
||||
|
'DASHBOARD': EntityType.DASHBOARD, |
||||
|
'CALCULATED_FIELD': EntityType.CALCULATED_FIELD, |
||||
|
'RULE_CHAIN': EntityType.RULE_CHAIN, |
||||
|
'DEVICE': EntityType.DEVICE_PROFILE |
||||
|
}; |
||||
|
|
||||
|
constructor( |
||||
|
private iotHubApiService: IotHubApiService, |
||||
|
private dialogService: DialogService, |
||||
|
private translate: TranslateService, |
||||
|
private store: Store<AppState>, |
||||
|
private router: Router, |
||||
|
private dialog: MatDialog |
||||
|
) {} |
||||
|
|
||||
|
ngOnInit(): void { |
||||
|
this.searchSubject.pipe( |
||||
|
debounceTime(300) |
||||
|
).subscribe(() => { |
||||
|
this.pageIndex = 0; |
||||
|
this.paginator.pageIndex = 0; |
||||
|
this.loadData(); |
||||
|
}); |
||||
|
this.loadData(); |
||||
|
} |
||||
|
|
||||
|
ngAfterViewInit(): void { |
||||
|
this.sort.sortChange.subscribe(() => { |
||||
|
this.pageIndex = 0; |
||||
|
this.paginator.pageIndex = 0; |
||||
|
this.loadData(); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
navigateToMarketplace(): void { |
||||
|
this.router.navigate(['/iot-hub']); |
||||
|
} |
||||
|
|
||||
|
onSearchChange(value: string): void { |
||||
|
this.textSearch = value; |
||||
|
this.searchSubject.next(value); |
||||
|
} |
||||
|
|
||||
|
onPageChange(event: any): void { |
||||
|
this.pageIndex = event.pageIndex; |
||||
|
this.pageSize = event.pageSize; |
||||
|
this.loadData(); |
||||
|
} |
||||
|
|
||||
|
deleteItem(item: IotHubInstalledItem): void { |
||||
|
this.dialogService.confirm( |
||||
|
this.translate.instant('iot-hub.delete-installed-item-title'), |
||||
|
this.translate.instant('iot-hub.delete-installed-item-text', {name: item.itemName}), |
||||
|
this.translate.instant('action.no'), |
||||
|
this.translate.instant('action.yes') |
||||
|
).subscribe(result => { |
||||
|
if (result) { |
||||
|
this.iotHubApiService.deleteInstalledItem(item.itemId).subscribe({ |
||||
|
next: () => { |
||||
|
this.store.dispatch(new ActionNotificationShow({ |
||||
|
message: this.translate.instant('iot-hub.installed-item-deleted', {name: item.itemName}), |
||||
|
type: 'success', |
||||
|
duration: 3000 |
||||
|
})); |
||||
|
this.loadData(); |
||||
|
}, |
||||
|
error: () => { |
||||
|
this.store.dispatch(new ActionNotificationShow({ |
||||
|
message: this.translate.instant('iot-hub.installed-item-delete-error', {name: item.itemName}), |
||||
|
type: 'error', |
||||
|
duration: 5000 |
||||
|
})); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
getItemTypeLabel(itemType: string): string { |
||||
|
const key = itemTypeTranslations.get(itemType as ItemType); |
||||
|
return key ? this.translate.instant(key) : itemType; |
||||
|
} |
||||
|
|
||||
|
getItemTypeChipClass(itemType: string): string { |
||||
|
switch (itemType) { |
||||
|
case 'WIDGET': return 'tb-type-widget'; |
||||
|
case 'DASHBOARD': return 'tb-type-dashboard'; |
||||
|
case 'CALCULATED_FIELD': return 'tb-type-calc-field'; |
||||
|
case 'RULE_CHAIN': return 'tb-type-rule-chain'; |
||||
|
case 'DEVICE': return 'tb-type-device'; |
||||
|
default: return ''; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
viewItemDetails(item: IotHubInstalledItem): void { |
||||
|
this.iotHubApiService.getVersionInfo(item.itemVersionId, {ignoreLoading: true}).subscribe(versionView => { |
||||
|
this.dialog.open(TbIotHubItemDetailDialogComponent, { |
||||
|
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], |
||||
|
data: { |
||||
|
item: versionView, |
||||
|
iotHubApiService: this.iotHubApiService, |
||||
|
installedDescriptor: item.descriptor |
||||
|
} as IotHubItemDetailDialogData |
||||
|
}); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
getEntityId(item: IotHubInstalledItem): string | null { |
||||
|
const descriptor = item.descriptor; |
||||
|
switch (descriptor.type) { |
||||
|
case 'WIDGET': return descriptor.widgetTypeId?.id; |
||||
|
case 'DASHBOARD': return descriptor.dashboardId?.id; |
||||
|
case 'CALCULATED_FIELD': return descriptor.calculatedFieldId?.id; |
||||
|
case 'RULE_CHAIN': return descriptor.ruleChainId?.id; |
||||
|
default: return null; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
openEntity(item: IotHubInstalledItem): void { |
||||
|
const entityType = TbIotHubInstalledItemsComponent.ITEM_TYPE_TO_ENTITY_TYPE[item.itemType]; |
||||
|
const entityId = this.getEntityId(item); |
||||
|
if (entityType && entityId) { |
||||
|
const url = getEntityDetailsPageURL(entityId, entityType); |
||||
|
if (url) { |
||||
|
this.router.navigateByUrl(url); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private loadData(): void { |
||||
|
this.isLoading = true; |
||||
|
const sortOrder: SortOrder = { |
||||
|
property: this.sort?.active || 'createdTime', |
||||
|
direction: this.sort?.direction === 'asc' ? Direction.ASC : Direction.DESC |
||||
|
}; |
||||
|
const pageLink = new PageLink(this.pageSize, this.pageIndex, this.textSearch || null, sortOrder); |
||||
|
this.iotHubApiService.getInstalledItems(pageLink, {ignoreLoading: true}).subscribe({ |
||||
|
next: (data) => { |
||||
|
this.dataSource = data.data; |
||||
|
this.totalElements = data.totalElements; |
||||
|
this.isLoading = false; |
||||
|
}, |
||||
|
error: () => { |
||||
|
this.dataSource = []; |
||||
|
this.totalElements = 0; |
||||
|
this.isLoading = false; |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,63 @@ |
|||||
|
///
|
||||
|
/// 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 { BaseData } from '@shared/models/base-data'; |
||||
|
|
||||
|
export interface WidgetInstalledItemDescriptor { |
||||
|
type: 'WIDGET'; |
||||
|
widgetTypeId: { id: string }; |
||||
|
} |
||||
|
|
||||
|
export interface DashboardInstalledItemDescriptor { |
||||
|
type: 'DASHBOARD'; |
||||
|
dashboardId: { id: string }; |
||||
|
} |
||||
|
|
||||
|
export interface CalculatedFieldInstalledItemDescriptor { |
||||
|
type: 'CALCULATED_FIELD'; |
||||
|
calculatedFieldId: { id: string }; |
||||
|
} |
||||
|
|
||||
|
export interface RuleChainInstalledItemDescriptor { |
||||
|
type: 'RULE_CHAIN'; |
||||
|
ruleChainId: { id: string }; |
||||
|
} |
||||
|
|
||||
|
export interface DeviceInstalledItemDescriptor { |
||||
|
type: 'DEVICE'; |
||||
|
} |
||||
|
|
||||
|
export type IotHubInstalledItemDescriptor = |
||||
|
| WidgetInstalledItemDescriptor |
||||
|
| DashboardInstalledItemDescriptor |
||||
|
| CalculatedFieldInstalledItemDescriptor |
||||
|
| RuleChainInstalledItemDescriptor |
||||
|
| DeviceInstalledItemDescriptor; |
||||
|
|
||||
|
export interface IotHubInstalledItemInfo { |
||||
|
itemId: string; |
||||
|
itemVersionId: string; |
||||
|
} |
||||
|
|
||||
|
export interface IotHubInstalledItem extends BaseData<{id: string}> { |
||||
|
tenantId: { id: string }; |
||||
|
itemId: string; |
||||
|
itemVersionId: string; |
||||
|
itemName: string; |
||||
|
itemType: string; |
||||
|
version: string; |
||||
|
descriptor: IotHubInstalledItemDescriptor; |
||||
|
} |
||||
Loading…
Reference in new issue