diff --git a/src/AzureIoTHub.Portal.Application/Mappers/AWS/ThingTypeProfile.cs b/src/AzureIoTHub.Portal.Application/Mappers/AWS/ThingTypeProfile.cs deleted file mode 100644 index 379b7dedf..000000000 --- a/src/AzureIoTHub.Portal.Application/Mappers/AWS/ThingTypeProfile.cs +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (c) CGI France. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -namespace AzureIoTHub.Portal.Application.Mappers.AWS -{ - using AutoMapper; - using AzureIoTHub.Portal.Domain.Entities.AWS; - using AzureIoTHub.Portal.Models.v10.AWS; - using Amazon.IoT.Model; - public class ThingTypeProfile : Profile - { - public ThingTypeProfile() - { - _ = CreateMap(); - - _ = CreateMap() - .ForMember(dest => dest.Id, opts => opts.MapFrom(src => src.ThingTypeID)) - .ForMember(dest => dest.Name, opts => opts.MapFrom(src => src.ThingTypeName)) - .ForMember(dest => dest.Description, opts => opts.MapFrom(src => src.ThingTypeDescription)) - .ForMember(dest => dest.Deprecated, opts => opts.MapFrom(src => src.Deprecated)) - .ForMember(dest => dest.ThingTypeSearchableAttributes, opts => opts.MapFrom(src => src.ThingTypeSearchableAttDtos.Select(pair => new ThingTypeSearchableAtt - { - Name = pair.Name - }))) - .ForMember(dest => dest.Tags, opts => opts.MapFrom(src => src.Tags.Select(pair => new ThingTypeTag - { - Key = pair.Key, - Value = pair.Value - }))); - - _ = CreateMap() - .ForMember(dest => dest.ThingTypeID, opts => opts.MapFrom(src => src.Id)) - .ForMember(dest => dest.ThingTypeName, opts => opts.MapFrom(src => src.Name)) - .ForMember(dest => dest.ThingTypeDescription, opts => opts.MapFrom(src => src.Description)) - .ForMember(dest => dest.Deprecated, opts => opts.MapFrom(src => src.Deprecated)) - .ForMember(dest => dest.Tags, opts => opts.MapFrom(src => src.Tags != null ? src.Tags.ToList() : null)) - .ForMember(dest => dest.ThingTypeSearchableAttDtos, opts => opts.MapFrom( - src => src.ThingTypeSearchableAttributes != null ? src.ThingTypeSearchableAttributes.ToList() : null)); - - _ = CreateMap() - .ForMember(dest => dest.ThingTypeName, opts => opts.MapFrom(src => src.ThingTypeName)) - .ForMember(dest => dest.ThingTypeProperties, opts => opts.MapFrom(src => new ThingTypeProperties - { - ThingTypeDescription = src.ThingTypeDescription, - SearchableAttributes = src.ThingTypeSearchableAttDtos.Select(pair => pair.Name).ToList() ?? new List() - })) - .ForMember(dest => dest.Tags, opts => opts.MapFrom(src => src.Tags.Select(pair => new Tag - { - Key = pair.Key, - Value = pair.Value - }).ToList() ?? new List())); - - } - } -} diff --git a/src/AzureIoTHub.Portal.Application/Mappers/AWS/ThingTypeSearchableAttProfile.cs b/src/AzureIoTHub.Portal.Application/Mappers/AWS/ThingTypeSearchableAttProfile.cs deleted file mode 100644 index c78f771c3..000000000 --- a/src/AzureIoTHub.Portal.Application/Mappers/AWS/ThingTypeSearchableAttProfile.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) CGI France. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -namespace AzureIoTHub.Portal.Application.Mappers.AWS -{ - using AutoMapper; - using AzureIoTHub.Portal.Domain.Entities.AWS; - using AzureIoTHub.Portal.Models.v10.AWS; - - public class ThingTypeSearchableAttProfile : Profile - { - public ThingTypeSearchableAttProfile() - { - _ = CreateMap() - .ForMember(dest => dest.Name, opts => opts.MapFrom(src => src.Name)) - .ReverseMap(); - } - } -} diff --git a/src/AzureIoTHub.Portal.Application/Mappers/AWS/ThingTypeTagProfile.cs b/src/AzureIoTHub.Portal.Application/Mappers/AWS/ThingTypeTagProfile.cs deleted file mode 100644 index 11f96c490..000000000 --- a/src/AzureIoTHub.Portal.Application/Mappers/AWS/ThingTypeTagProfile.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) CGI France. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -namespace AzureIoTHub.Portal.Application.Mappers.AWS -{ - using AutoMapper; - using AzureIoTHub.Portal.Domain.Entities.AWS; - using AzureIoTHub.Portal.Models.v10.AWS; - - public class ThingTypeTagProfile : Profile - { - public ThingTypeTagProfile() - { - _ = CreateMap() - .ForMember(dest => dest.Key, opts => opts.MapFrom(src => src.Key)) - .ForMember(dest => dest.Value, opts => opts.MapFrom(src => src.Value)) - .ReverseMap(); - - } - } -} diff --git a/src/AzureIoTHub.Portal.Application/Mappers/DeviceModelProfile.cs b/src/AzureIoTHub.Portal.Application/Mappers/DeviceModelProfile.cs index 15d29390b..ee49d2402 100644 --- a/src/AzureIoTHub.Portal.Application/Mappers/DeviceModelProfile.cs +++ b/src/AzureIoTHub.Portal.Application/Mappers/DeviceModelProfile.cs @@ -1,24 +1,42 @@ -// Copyright (c) CGI France. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -namespace AzureIoTHub.Portal.Application.Mappers -{ - using AutoMapper; - using AzureIoTHub.Portal.Domain.Entities; - using Models.v10; - using Models.v10.LoRaWAN; - - public class DeviceModelProfile : Profile - { - public DeviceModelProfile() - { - _ = CreateMap() - .ForMember(dest => dest.Id, opts => opts.MapFrom(src => src.ModelId)) - .ReverseMap(); - - _ = CreateMap() - .ForMember(dest => dest.Id, opts => opts.MapFrom(src => src.ModelId)) - .ReverseMap(); - } - } -} +// Copyright (c) CGI France. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace AzureIoTHub.Portal.Application.Mappers +{ + using Amazon.IoT.Model; + using AutoMapper; + using AzureIoTHub.Portal.Domain.Entities; + using AzureIoTHub.Portal.Domain.Shared; + using Models.v10; + using Models.v10.LoRaWAN; + + public class DeviceModelProfile : Profile + { + public DeviceModelProfile() + { + _ = CreateMap() + .ForMember(dest => dest.Id, opts => opts.MapFrom(src => src.ModelId)) + .ReverseMap(); + + _ = CreateMap() + .ForMember(dest => dest.Id, opts => opts.MapFrom(src => src.ModelId)) + .ReverseMap(); + + _ = CreateMap() + .ForMember(dest => dest.Name, opts => opts.MapFrom(src => src.Name)) + .ForMember(dest => dest.Description, opts => opts.MapFrom(src => src.Description)); + + _ = CreateMap() + .ForMember(dest => dest.ThingTypeName, opts => opts.MapFrom(src => src.Name)) + .ForMember(dest => dest.ThingTypeProperties, opts => opts.MapFrom(src => new ThingTypeProperties + { + ThingTypeDescription = src.Description + })); + + _ = CreateMap() + .ForMember(dest => dest.Id, opts => opts.MapFrom(src => src.ThingTypeId)) + .ForMember(dest => dest.Name, opts => opts.MapFrom(src => src.ThingTypeName)) + .ForMember(dest => dest.Description, opts => opts.MapFrom(src => src.ThingTypeProperties.ThingTypeDescription ?? string.Empty)); + } + } +} diff --git a/src/AzureIoTHub.Portal.Application/Services/AWS/IThingTypeService.cs b/src/AzureIoTHub.Portal.Application/Services/AWS/IThingTypeService.cs deleted file mode 100644 index da73696a2..000000000 --- a/src/AzureIoTHub.Portal.Application/Services/AWS/IThingTypeService.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) CGI France. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -namespace AzureIoTHub.Portal.Application.Services.AWS -{ - using AzureIoTHub.Portal.Models.v10.AWS; - using AzureIoTHub.Portal.Shared.Models.v1._0; - using AzureIoTHub.Portal.Shared.Models.v10.Filters; - using Microsoft.AspNetCore.Http; - - public interface IThingTypeService - { - //Get All Thing Types - Task> GetThingTypes(DeviceModelFilter deviceModelFilter); - - //Get a thing type - Task GetThingType(string thingTypeId); - //Create a thing type - Task CreateThingType(ThingTypeDto thingType); - //Deprecate a thing type - Task DeprecateThingType(string thingTypeId); - //Delete a thing type - Task DeleteThingType(string thingTypeId); - Task GetThingTypeAvatar(string thingTypeId); - - Task UpdateThingTypeAvatar(string thingTypeId, IFormFile file); - - Task DeleteThingTypeAvatar(string thingTypeId); - } -} diff --git a/src/AzureIoTHub.Portal.Application/Services/IDeviceModelService.cs b/src/AzureIoTHub.Portal.Application/Services/IDeviceModelService.cs index bb8366c03..0c78e2cc9 100644 --- a/src/AzureIoTHub.Portal.Application/Services/IDeviceModelService.cs +++ b/src/AzureIoTHub.Portal.Application/Services/IDeviceModelService.cs @@ -18,7 +18,7 @@ public interface IDeviceModelService Task GetDeviceModel(string deviceModelId); - Task CreateDeviceModel(TModel deviceModel); + Task CreateDeviceModel(TModel deviceModel); Task UpdateDeviceModel(TModel deviceModel); diff --git a/src/AzureIoTHub.Portal.Application/Services/IExternalDeviceService.cs b/src/AzureIoTHub.Portal.Application/Services/IExternalDeviceService.cs index 9d9a4d57a..6b72657f1 100644 --- a/src/AzureIoTHub.Portal.Application/Services/IExternalDeviceService.cs +++ b/src/AzureIoTHub.Portal.Application/Services/IExternalDeviceService.cs @@ -7,10 +7,15 @@ namespace AzureIoTHub.Portal.Application.Services using System.Threading.Tasks; using Models.v10; using Microsoft.Azure.Devices; - using Microsoft.Azure.Devices.Shared; - + using Microsoft.Azure.Devices.Shared; + using AzureIoTHub.Portal.Domain.Shared; + public interface IExternalDeviceService { + Task CreateDeviceModel(ExternalDeviceModelDto deviceModel); + + Task DeleteDeviceModel(ExternalDeviceModelDto deviceModel); + Task GetDevice(string deviceId); Task GetDeviceTwin(string deviceId); diff --git a/src/AzureIoTHub.Portal.Client/AzureIoTHub.Portal.Client.csproj b/src/AzureIoTHub.Portal.Client/AzureIoTHub.Portal.Client.csproj index f9bc094c9..f4858f74b 100644 --- a/src/AzureIoTHub.Portal.Client/AzureIoTHub.Portal.Client.csproj +++ b/src/AzureIoTHub.Portal.Client/AzureIoTHub.Portal.Client.csproj @@ -5,6 +5,14 @@ enable enable + + + + + + + + @@ -39,9 +47,5 @@ - - - - diff --git a/src/AzureIoTHub.Portal.Client/Components/DeviceModels/DeviceModelSearch.razor b/src/AzureIoTHub.Portal.Client/Components/DeviceModels/DeviceModelSearch.razor index 2875cb30f..8adcd1466 100644 --- a/src/AzureIoTHub.Portal.Client/Components/DeviceModels/DeviceModelSearch.razor +++ b/src/AzureIoTHub.Portal.Client/Components/DeviceModels/DeviceModelSearch.razor @@ -1,21 +1,8 @@ -@using AzureIoTHub.Portal.Models.v10; - -@inject PortalSettings Portal; - - + - @if (Portal.CloudProvider.Equals("Azure")) - { - - - } - else - { - - - } + @@ -29,24 +16,24 @@ @code { - [Parameter] - public EventCallback OnSearch { get; set; } - - private string? searchText = string.Empty; - - private async Task Search() - { + [Parameter] + public EventCallback OnSearch { get; set; } + + private string? searchText = string.Empty; + + private async Task Search() + { var searchInfo = new DeviceModelSearchInfo { SearchText = searchText - }; - await OnSearch.InvokeAsync(searchInfo); - } - - private async Task Reset() - { - searchText = string.Empty; - - await Search(); + }; + await OnSearch.InvokeAsync(searchInfo); + } + + private async Task Reset() + { + searchText = string.Empty; + + await Search(); } -} +} diff --git a/src/AzureIoTHub.Portal.Client/Pages/DeviceModels/CreateDeviceModelPage.razor b/src/AzureIoTHub.Portal.Client/Pages/DeviceModels/CreateDeviceModelPage.razor index 7fe306e0a..7453a35a1 100644 --- a/src/AzureIoTHub.Portal.Client/Pages/DeviceModels/CreateDeviceModelPage.razor +++ b/src/AzureIoTHub.Portal.Client/Pages/DeviceModels/CreateDeviceModelPage.razor @@ -1,15 +1,11 @@ @page "/device-models/new" - @using System.Net.Http.Headers @using AzureIoTHub.Portal.Client.Pages.DeviceModels @using AzureIoTHub.Portal.Client.Validators -@using AzureIoTHub.Portal.Shared.Models @using AzureIoTHub.Portal.Models @using AzureIoTHub.Portal.Models.v10 @using AzureIoTHub.Portal.Models.v10.LoRaWAN -@using AzureIoTHub.Portal.Models.v10.AWS -@using AzureIoTHub.Portal.Shared.Constants -@using AzureIoTHub.Portal.Client.Services.AWS +@using AzureIoTHub.Portal.Shared.Models @attribute [Authorize] @inject NavigationManager NavigationManager @@ -17,19 +13,8 @@ @inject ISnackbar Snackbar @inject IDeviceModelsClientService DeviceModelsClientService @inject ILoRaWanDeviceModelsClientService LoRaWanDeviceModelsClientService -@inject ILoRaWanDeviceModelsClientService LoRaWanDeviceModelsClientService -@inject IThingTypeClientService ThingTypeClientService - -@if (Portal.CloudProvider.Equals(CloudProviders.AWS)) -{ - Create Thing Type - -} -else -{ - Create Device Model -} +Create Device Model @@ -43,18 +28,17 @@ else - @if (imageDataUrl != null) + @if (imageDataUrl != null) { Delete Picture - } - else + } + else { - Save Changes @@ -70,213 +54,114 @@ else - @if (Portal.CloudProvider.Equals(CloudProviders.AWS)) - { - - - } - else - { - - - } + - @if (Portal.CloudProvider.Equals(CloudProviders.AWS)) - { - + + + @if (Portal.IsLoRaSupported) + { + + + LoRa Device + - } - else - { - - @if (Portal.IsLoRaSupported) + + @if (IsLoRa) { - - - LoRa Device - - - - @if (IsLoRa) - { - The device is a LoRa Device. + The device is a LoRa Device. - } - } - } - - + + } - @if (!IsLoRa) + @if (!IsLoRa) { - @if (Portal.CloudProvider.Equals(CloudProviders.AWS)) - { - - - - Tags - - - Add tag - - @foreach (var tag in this.ThingTypeTags) - { - - - - - - - - - - Remove - - - } - - - - - - - } - else - { - - - - Properties - - - - Add property - - - @foreach (var item in this.Properties.OrderBy(x => x.Order)) - { - - - + + + Properties + + + + Add property + + + @foreach (var item in this.Properties.OrderBy(x => x.Order)) + { + + + - - - + + - - - + + - - - + + - @foreach (DevicePropertyType item in Enum.GetValues(typeof(DevicePropertyType))) - { - @item - } - - - - - - - - Remove - - - } - - - - - - - } - } + @foreach (DevicePropertyType item in Enum.GetValues(typeof(DevicePropertyType))) + { + @item + } + + + + - @if (Portal.CloudProvider.Equals(CloudProviders.AWS)) - { - - - - Searchable Attribute - - - - Add Attribute - - - @foreach (var search in this.searchableAtt) - { - - - - - Remove + Remove } - - - - - - } - else - { - - - - Labels - - + } + + + + Labels + + + + + + + - @if (IsLoRa) + @if (IsLoRa) { @code { - [CascadingParameter] - public Error Error { get; set; } = default!; - - private MudForm form = default!; - - private DeviceModelValidator standardValidator = new DeviceModelValidator(); - private ThingTypeValidator thingTypeValidator = new ThingTypeValidator(); - private LoRaDeviceModelValidator loraValidator = new LoRaDeviceModelValidator(); - private DevicePropertyValidator propertiesValidator = new DevicePropertyValidator(); - private List Commands { get; set; } = new(); - private List Properties { get; set; } = new List(); - private List searchableAtt { get; set; } = new List(); - private List ThingTypeTags { get; set; } = new List(); - private LoRaDeviceModelCommandValidator CommandValidator = new LoRaDeviceModelCommandValidator(); - private ThingTypeDto ThingType { get; set; } = new ThingTypeDto(); - - private bool isProcessing; - - private bool IsLoRa - { - get - { - return this.Model is LoRaDeviceModelDto; - } - set - { - if (value) - { - this.SetLoRaDeviceModel(); - return; - } - - SetStandardDeviceModel(); - } - } - - private void SetLoRaDeviceModel() - { - Model = new LoRaDeviceModelDto(Model); - Commands = new List(); - } - - private void SetStandardDeviceModel() - { + [CascadingParameter] + public Error Error { get; set; } = default!; + + private MudForm form = default!; + + private DeviceModelValidator standardValidator = new DeviceModelValidator(); + private LoRaDeviceModelValidator loraValidator = new LoRaDeviceModelValidator(); + private DevicePropertyValidator propertiesValidator = new DevicePropertyValidator(); + private List Commands { get; set; } = new(); + private List Properties { get; set; } = new List(); + + private LoRaDeviceModelCommandValidator CommandValidator = new LoRaDeviceModelCommandValidator(); + + private bool isProcessing; + + private bool IsLoRa + { + get + { + return this.Model is LoRaDeviceModelDto; + } + set + { + if (value) + { + this.SetLoRaDeviceModel(); + return; + } + + SetStandardDeviceModel(); + } + } + + private void SetLoRaDeviceModel() + { + Model = new LoRaDeviceModelDto(Model); + Commands = new List(); + } + + private void SetStandardDeviceModel() + { Model = new DeviceModelDto { ModelId = Model.ModelId, Name = Model.Name, IsBuiltin = Model.IsBuiltin, Description = Model.Description - }; - } - + }; + } + internal IDeviceModel Model { get; set; } = new DeviceModelDto { ModelId = Guid.NewGuid().ToString() - }; - - // Used to manage the picture - private MultipartFormDataContent? content = default!; - private string? imageDataUrl = default!; - - private bool CheckLoRaValidation() - { - if (IsLoRa && this.Model is LoRaDeviceModelDto loRaDeviceModel) - { - return !this.loraValidator.Validate(loRaDeviceModel).IsValid; - } - - return true; - } - - private bool CheckGeneralValidation() - { - if (Portal.CloudProvider.Equals(CloudProviders.AWS)) - { - if(this.ThingType != null) - { - return !this.thingTypeValidator.Validate(this.ThingType).IsValid; - } - } - else - { - if (!IsLoRa && this.Model is DeviceModelDto deviceModel) - { - return !this.standardValidator.Validate(deviceModel).IsValid; - } - } - - - return CheckLoRaValidation(); - } - - private void DeleteAvatar() - { - content = null; - imageDataUrl = null; - } - - private async Task UploadFiles(InputFileChangeEventArgs e) - { - content = new MultipartFormDataContent(); - - var resizedImageFile = await e.File.RequestImageFileAsync(e.File.ContentType, 200, 200); - - var fileContent = new StreamContent(resizedImageFile.OpenReadStream()); - fileContent.Headers.ContentType = new MediaTypeHeaderValue(e.File.ContentType); - - content.Add(content: fileContent, - name: "\"file\"", - fileName: e.File.Name); - - var buffer = new byte[resizedImageFile.Size]; - await resizedImageFile.OpenReadStream().ReadAsync(buffer); - - imageDataUrl = $"data:{e.File.ContentType};base64,{Convert.ToBase64String(buffer)}"; - } - - private async Task Save() - { - - if(Portal.CloudProvider.Equals(CloudProviders.AWS)) - { - await this.SaveThingType(); - } - else - { - await this.SaveDeviceModel(); - } - } - - private async Task SaveDeviceModel() - { - isProcessing = true; - - // Displays validation error message for each field - await form.Validate(); - - // Used to check commands - bool duplicated = false; - bool cmdValidationError = false; - - if (IsLoRa) - { - // Check duplicates in command name - var query = Commands.GroupBy(x => x.Name) - .Where(x => x.Count() > 1) - .Select(x => x.Key) - .ToList(); - foreach (var item in query) - { - Snackbar.Add($"The command '{item}' appears more than once!", Severity.Warning); - duplicated = true; - } - - // Check validation error in commands - foreach (var cmd in Commands) - { - if (!CommandValidator.Validate(cmd).IsValid) - cmdValidationError = true; - } - } - - if (!IsLoRa ? !standardValidator.Validate((Model as DeviceModelDto)!).IsValid : - (!this.loraValidator.Validate((this.Model as LoRaDeviceModelDto)!).IsValid - || duplicated - || cmdValidationError)) - { - Snackbar.Add("One or more validation errors occurred", Severity.Error); - - isProcessing = false; - - return; - } - - try - { - if (IsLoRa) - { - await LoRaWanDeviceModelsClientService.CreateDeviceModel((Model as LoRaDeviceModelDto)!); - - await LoRaWanDeviceModelsClientService.SetDeviceModelCommands(Model.ModelId, Commands); - } - else - { - - await DeviceModelsClientService.CreateDeviceModel((Model as DeviceModelDto)!); - - await DeviceModelsClientService.SetDeviceModelModelProperties(Model.ModelId, Properties); - - - } - - if (content is not null) - { - if (IsLoRa) - { - await LoRaWanDeviceModelsClientService.ChangeAvatar(Model.ModelId, content); - } - else - { - await DeviceModelsClientService.ChangeAvatar(Model.ModelId, content); - } - } - - Snackbar.Add("Device model successfully created.", Severity.Success); - - - // Go back to the list of devices - NavigationManager.NavigateTo("device-models"); - } - - catch (ProblemDetailsException exception) - { - Error?.ProcessProblemDetails(exception); - } - finally - { - isProcessing = false; - } - } - - private async Task SaveThingType() - { - isProcessing = true; - - // Displays validation error message for each field - await form.Validate(); - - - if (!thingTypeValidator.Validate(ThingType).IsValid) - { - Snackbar.Add("One or more validation errors occurred", Severity.Error); - - isProcessing = false; - - return; - } - - try - { - - ThingType.Tags = this.ThingTypeTags; - ThingType.ThingTypeSearchableAttDtos = searchableAtt; - - var GetThingTypeId = await ThingTypeClientService.CreateThingType(ThingType); - - if (content is not null) - { - _ = this.ThingTypeClientService.ChangeAvatar(GetThingTypeId, content); - } - - Snackbar.Add("Thing Type successfully created.", Severity.Success); - - // Go back to the list of devices - NavigationManager.NavigateTo("device-models"); - } - - catch (ProblemDetailsException exception) - { - Error?.ProcessProblemDetails(exception); - } - finally - { - isProcessing = false; - } + }; + + // Used to manage the picture + private MultipartFormDataContent? content = default!; + private string? imageDataUrl = default!; + + private bool CheckLoRaValidation() + { + if (IsLoRa && this.Model is LoRaDeviceModelDto loRaDeviceModel) + { + return !this.loraValidator.Validate(loRaDeviceModel).IsValid; + } + + return true; + } + + private bool CheckGeneralValidation() + { + if (!IsLoRa && this.Model is DeviceModelDto deviceModel) + { + return !this.standardValidator.Validate(deviceModel).IsValid; + } + + return CheckLoRaValidation(); + } + + private void DeleteAvatar() + { + content = null; + imageDataUrl = null; + } + + private async Task UploadFiles(InputFileChangeEventArgs e) + { + content = new MultipartFormDataContent(); + + var resizedImageFile = await e.File.RequestImageFileAsync(e.File.ContentType, 200, 200); + + var fileContent = new StreamContent(resizedImageFile.OpenReadStream()); + fileContent.Headers.ContentType = new MediaTypeHeaderValue(e.File.ContentType); + + content.Add(content: fileContent, + name: "\"file\"", + fileName: e.File.Name); + + var buffer = new byte[resizedImageFile.Size]; + await resizedImageFile.OpenReadStream().ReadAsync(buffer); + + imageDataUrl = $"data:{e.File.ContentType};base64,{Convert.ToBase64String(buffer)}"; + } + + private async Task Save() + { + isProcessing = true; + + // Displays validation error message for each field + await form.Validate(); + + // Used to check commands + bool duplicated = false; + bool cmdValidationError = false; + + if (IsLoRa) + { + // Check duplicates in command name + var query = Commands.GroupBy(x => x.Name) + .Where(x => x.Count() > 1) + .Select(x => x.Key) + .ToList(); + foreach (var item in query) + { + Snackbar.Add($"The command '{item}' appears more than once!", Severity.Warning); + duplicated = true; + } + + // Check validation error in commands + foreach (var cmd in Commands) + { + if (!CommandValidator.Validate(cmd).IsValid) + cmdValidationError = true; + } + } + + if (!IsLoRa ? !standardValidator.Validate((Model as DeviceModelDto)!).IsValid : + (!this.loraValidator.Validate((this.Model as LoRaDeviceModelDto)!).IsValid + || duplicated + || cmdValidationError)) + { + Snackbar.Add("One or more validation errors occurred", Severity.Error); + + isProcessing = false; + + return; + } + + try + { + if (IsLoRa) + { + await LoRaWanDeviceModelsClientService.CreateDeviceModel((Model as LoRaDeviceModelDto)!); + + await LoRaWanDeviceModelsClientService.SetDeviceModelCommands(Model.ModelId, Commands); + } + else + { + Model = await DeviceModelsClientService.CreateDeviceModel((Model as DeviceModelDto)!); + + await DeviceModelsClientService.SetDeviceModelModelProperties(Model.ModelId, Properties); + } + + if (content is not null) + { + if (IsLoRa) + { + await LoRaWanDeviceModelsClientService.ChangeAvatar(Model.ModelId, content); + } + else + { + await DeviceModelsClientService.ChangeAvatar(Model.ModelId, content); + } + } + + Snackbar.Add("Device model successfully created.", Severity.Success); + + // Go back to the list of devices + NavigationManager.NavigateTo("device-models"); + } + + catch (ProblemDetailsException exception) + { + Error?.ProcessProblemDetails(exception); + } + finally + { + isProcessing = false; + } } -} +} diff --git a/src/AzureIoTHub.Portal.Client/Pages/DeviceModels/DeleteDeviceModelPage.razor b/src/AzureIoTHub.Portal.Client/Pages/DeviceModels/DeleteDeviceModelPage.razor index 7a1d77a6e..cf0327dbf 100644 --- a/src/AzureIoTHub.Portal.Client/Pages/DeviceModels/DeleteDeviceModelPage.razor +++ b/src/AzureIoTHub.Portal.Client/Pages/DeviceModels/DeleteDeviceModelPage.razor @@ -1,70 +1,44 @@ -@using AzureIoTHub.Portal.Models.v10 -@using AzureIoTHub.Portal.Client.Services.AWS - -@inject ISnackbar Snackbar -@inject IDeviceModelsClientService DeviceModelsClientService -@inject PortalSettings Portal -@inject IThingTypeClientService ThingTypeClientService - - - - @if (Portal.CloudProvider.Equals("AWS")) - { -

Delete @thingTypeName ?

- - } - else - { -

Delete @deviceModelName ?

- - } -
-

Warning : this cannot be undone.

-
- - Cancel - Delete - -
- -@code { - [CascadingParameter] - public Error Error { get; set; } = default!; - - [CascadingParameter] MudDialogInstance MudDialog { get; set; } = default!; - [Parameter] public string deviceModelID { get; set; } = default!; - [Parameter] public string deviceModelName { get; set; } = default!; - - [Parameter] public string thingTypeID { get; set; } = default!; - [Parameter] public string thingTypeName { get; set; } = default!; - - void Submit() => MudDialog.Close(DialogResult.Ok(true)); - void Cancel() => MudDialog.Cancel(); - - /// - /// Sends a POST request to the DevicesController, to delete the device from the Azure IoT Hub - /// - /// - private async Task DeleteDevice() - { - try - { - if (Portal.CloudProvider.Equals("AWS")) - { - await ThingTypeClientService.DeleteThingType(thingTypeID); - Snackbar.Add($"Thing Type {thingTypeName} has been successfully deleted!", Severity.Success); - } - else - { - await DeviceModelsClientService.DeleteDeviceModel(deviceModelID); - Snackbar.Add($"Device model {deviceModelName} has been successfully deleted!", Severity.Success); - } - - MudDialog.Close(DialogResult.Ok(true)); - } - catch (ProblemDetailsException exception) - { - Error?.ProcessProblemDetails(exception); - } - } -} +@inject ISnackbar Snackbar +@inject IDeviceModelsClientService DeviceModelsClientService + + + +

Delete @deviceModelName ?

+
+

Warning : this cannot be undone.

+
+ + Cancel + Delete + +
+ +@code { + [CascadingParameter] + public Error Error { get; set; } = default!; + + [CascadingParameter] MudDialogInstance MudDialog { get; set; } = default!; + [Parameter] public string deviceModelID { get; set; } = default!; + [Parameter] public string deviceModelName { get; set; } = default!; + + void Submit() => MudDialog.Close(DialogResult.Ok(true)); + void Cancel() => MudDialog.Cancel(); + + /// + /// Sends a POST request to the DevicesController, to delete the device from the Azure IoT Hub + /// + /// + private async Task DeleteDevice() + { + try + { + await DeviceModelsClientService.DeleteDeviceModel(deviceModelID); + Snackbar.Add($"Device model {deviceModelName} has been successfully deleted!", Severity.Success); + MudDialog.Close(DialogResult.Ok(true)); + } + catch (ProblemDetailsException exception) + { + Error?.ProcessProblemDetails(exception); + } + } +} diff --git a/src/AzureIoTHub.Portal.Client/Pages/DeviceModels/DeviceModelDetailPage.razor b/src/AzureIoTHub.Portal.Client/Pages/DeviceModels/DeviceModelDetailPage.razor index 959c50529..9fa56c81d 100644 --- a/src/AzureIoTHub.Portal.Client/Pages/DeviceModels/DeviceModelDetailPage.razor +++ b/src/AzureIoTHub.Portal.Client/Pages/DeviceModels/DeviceModelDetailPage.razor @@ -1,13 +1,11 @@ @page "/device-models/{ModelID}" @using System.Net.Http.Headers @using AzureIoTHub.Portal.Client.Pages.DeviceModels -@using AzureIoTHub.Portal.Client.Services.AWS @using AzureIoTHub.Portal.Client.Validators @using AzureIoTHub.Portal.Models @using AzureIoTHub.Portal.Models.v10 @using AzureIoTHub.Portal.Models.v10.LoRaWAN @using AzureIoTHub.Portal.Shared.Models -@using AzureIoTHub.Portal.Models.v10.AWS @attribute [Authorize] @inject NavigationManager NavigationManager @@ -15,303 +13,153 @@ @inject IDialogService DialogService @inject IDeviceModelsClientService DeviceModelsClientService @inject ILoRaWanDeviceModelsClientService LoRaWanDeviceModelsClientService -@inject IThingTypeClientService ThingClientService @inject PortalSettings Portal - @if (Portal.CloudProvider.Equals("Azure")) - { - Device Model - - } - else - { - Thing Type - - } + Device Model -@if (Portal.CloudProvider.Equals("Azure")) -{ - - - - - -
- -
-
- - @if (imageDataUrl != null) - { - Delete Picture - } - else - { - -
- - Delete model - Save Changes - + + + + + +
+ +
+
+ + @if (imageDataUrl != null) + { + Delete Picture + } + else + { + +
+ + Delete model + Save Changes - - - - + + + + + + + + + Details + + + + + + + + + + + + + + @if (!IsLoRa) + { - - Details + + Properties - - - - - - - - - - - - - @if (!IsLoRa) - { - - - - Properties - - - Add property - - - @foreach (var item in this.Properties.OrderBy(x => x.Order)) - { - - - + Add property + + + @foreach (var item in this.Properties.OrderBy(x => x.Order)) + { + + + - - - + + - - - + + - - - + + - @foreach (DevicePropertyType item in Enum.GetValues(typeof(DevicePropertyType))) - { - @item - } - - - - - Writable - - - Remove - - - } - - - - - - } - - - - - Labels - - - - - - - - - - @if (IsLoRa) - { - - - - } - - -
-
-} -else -{ - - - - - -
- -
-
- - @if (imageDataUrl != null) - { - Delete Picture - } - else - { - -
- - Save Changes - - - Delete - - @if (ThingType.Deprecated) - { - Deprecate - - } - else - { - Deprecate - - } - -
- - - - - - - - Details - - - - - - - - - - - - - - - - - - Tags - - - @if (ThingType.Tags != null) - { - @foreach (var tag in ThingType.Tags) - { - - - - - - - - - } + @foreach (DevicePropertyType item in Enum.GetValues(typeof(DevicePropertyType))) + { + @item + } + + + + + Writable + + + Remove + + } - - - - -
-
- - - - - Searchable Attribute - - - - @if (ThingType.ThingTypeSearchableAttDtos != null) - { - @foreach (var search in ThingType.ThingTypeSearchableAttDtos) - { - - - - - - } - } - + } - + + + + Labels + + + + + + + + +
+ @if (IsLoRa) + { + + - - - - -} + } + + + + @code { [CascadingParameter] @@ -372,8 +220,6 @@ else ModelId = Guid.NewGuid().ToString() }; - private ThingTypeDto ThingType { get; set; } = new ThingTypeDto(); - // Used to manage the picture private MultipartFormDataContent? content = default!; private string? imageDataUrl = default!; @@ -404,27 +250,18 @@ else { isProcessing = true; - if (Portal.CloudProvider.Equals("AWS")) + if (IsLoRa) { - ThingType = await ThingClientService.GetThingType(ModelID); - imageDataUrl = await ThingClientService.GetAvatarUrl(ThingType.ThingTypeID); + Model = await LoRaWanDeviceModelsClientService.GetDeviceModel(ModelID); + Commands.AddRange(await LoRaWanDeviceModelsClientService.GetDeviceModelCommands(ModelID)); + imageDataUrl = await LoRaWanDeviceModelsClientService.GetAvatarUrl(ModelID); } else { - if (IsLoRa) - { - Model = await LoRaWanDeviceModelsClientService.GetDeviceModel(ModelID); - Commands.AddRange(await LoRaWanDeviceModelsClientService.GetDeviceModelCommands(ModelID)); - imageDataUrl = await LoRaWanDeviceModelsClientService.GetAvatarUrl(ModelID); - } - else - { - Model = await DeviceModelsClientService.GetDeviceModel(ModelID); - Properties.AddRange(await DeviceModelsClientService.GetDeviceModelModelProperties(ModelID)); - imageDataUrl = await DeviceModelsClientService.GetAvatarUrl(ModelID); - } + Model = await DeviceModelsClientService.GetDeviceModel(ModelID); + Properties.AddRange(await DeviceModelsClientService.GetDeviceModelModelProperties(ModelID)); + imageDataUrl = await DeviceModelsClientService.GetAvatarUrl(ModelID); } - } catch (ProblemDetailsException exception) { @@ -549,31 +386,6 @@ else } - private async Task ChangeThingTypeImage() - { - try - { - - if (content is not null) - { - await this.ThingClientService.ChangeAvatar(ThingType.ThingTypeID, content); - } - - Snackbar.Add("Thing Type image successfully updated.", Severity.Success); - - // Go back to the list of devices models - NavigationManager.NavigateTo("device-models"); - } - catch (ProblemDetailsException exception) - { - Error?.ProcessProblemDetails(exception); - } - finally - { - isProcessing = false; - } - } - private async Task DeleteDeviceModel() { isProcessing = true; @@ -595,49 +407,4 @@ else // Go back to the list of devices after the deletion NavigationManager.NavigateTo("device-models"); } - - private async Task DeleteThingType() - { - isProcessing = true; - - var parameters = new DialogParameters - { - {"thingTypeID", ThingType.ThingTypeID}, - {"thingTypeName", ThingType.ThingTypeName} - }; - var result = await DialogService.Show("Confirm Deletion", parameters).Result; - - isProcessing = false; - - if (result.Canceled) - { - return; - } - - // Go back to the list of devices after the deletion - NavigationManager.NavigateTo("device-models"); - } - private async Task Deprecate() - { - isProcessing = true; - - try - { - await ThingClientService.DeprecateThingType(ThingType.ThingTypeID); - Snackbar.Add($"The {ThingType.ThingTypeName} object type has been been successfully deprecated. You have to wait about 5 minutes before you can delete the object type.", Severity.Success); - - // Go back to the list of devices models - NavigationManager.NavigateTo("device-models"); - } - catch (ProblemDetailsException exception) - { - Error?.ProcessProblemDetails(exception); - } - finally - { - isProcessing = false; - } - - } - - } +} diff --git a/src/AzureIoTHub.Portal.Client/Pages/DeviceModels/DeviceModelListPage.razor b/src/AzureIoTHub.Portal.Client/Pages/DeviceModels/DeviceModelListPage.razor index de0a32ac6..423756736 100644 --- a/src/AzureIoTHub.Portal.Client/Pages/DeviceModels/DeviceModelListPage.razor +++ b/src/AzureIoTHub.Portal.Client/Pages/DeviceModels/DeviceModelListPage.razor @@ -1,357 +1,194 @@ @page "/device-models" @using AzureIoTHub.Portal.Models.v10 @using AzureIoTHub.Portal.Shared.Models.v10.Filters; -@using AzureIoTHub.Portal.Client.Services.AWS -@using AzureIoTHub.Portal.Models.v10.AWS - @attribute [Authorize] @inject NavigationManager navigationManager @inject PortalSettings Portal @inject IDialogService dialogService @inject IDeviceModelsClientService DeviceModelsClientService -@inject IThingTypeClientService ThingTypeClientService await Search(args)) /> - @if (Portal.CloudProvider.Equals("Azure")) - { - - - - - - - - - - Device Models - - - - - - - - - - - Name - Description - Details - Delete - - - - - - - - - @context.Name - - - - - - - - @context.Description - - - - - - - - - - - - - - - - - No matching records found - - - Loading... - - - - } - else - { - - - - - - - - - - Thing Types - - - + + + + + + + + + + Device Models + + + + + + + + + + + Name + Description + Details + Delete + + + + + + + + + @context.Name + + + + + + + + @context.Description + + + + - - + + + + - - - - Name - Description - Details - Delete - - - - - - - - - @context.ThingTypeName - - - - - @context.ThingTypeDescription - - - - - - - - - - - - - - - - - No matching records found - - - Loading... - - - - } + + + + + + + No matching records found + + + Loading... + + @code { - [CascadingParameter] - public Error Error { get; set; } = default!; - - private MudTable table = default!; - private MudTable thingTypeTable = default!; - - private readonly Dictionary pages = new(); - - //private List DeviceModelList = new List(); - - private bool IsLoading = true; - - private DeviceModelSearchInfo deviceModelSearchInfo = new(); - - //protected override async Task OnInitializedAsync() - //{ - // table.ReloadServerData(); - //} - - private void AddDeviceModel() - { - - navigationManager.NavigateTo("/device-models/new"); - } - + [CascadingParameter] + public Error Error { get; set; } = default!; + + private MudTable table = default!; + + private readonly Dictionary pages = new(); + + //private List DeviceModelList = new List(); + + private bool IsLoading = true; + + private DeviceModelSearchInfo deviceModelSearchInfo = new(); + + //protected override async Task OnInitializedAsync() + //{ + // table.ReloadServerData(); + //} + + private void AddDeviceModel() + { + navigationManager.NavigateTo("/device-models/new"); + } + /// /// Sends a GET request to the DeviceModelsController, to retrieve all device models from the database /// /// - private async Task> LoadDeviceModels(TableState state) - { - try - { - IsLoading = true; - - string orderBy = default!; - - switch (state.SortDirection) - { - case SortDirection.Ascending: - orderBy = $"{state.SortLabel} asc"; - break; - case SortDirection.Descending: - orderBy = $"{state.SortLabel} desc"; - break; - } - + private async Task> LoadDeviceModels(TableState state) + { + try + { + IsLoading = true; + + string orderBy = default!; + + switch (state.SortDirection) + { + case SortDirection.Ascending: + orderBy = $"{state.SortLabel} asc"; + break; + case SortDirection.Descending: + orderBy = $"{state.SortLabel} desc"; + break; + } + var result = await DeviceModelsClientService.GetDeviceModels(new DeviceModelFilter { SearchText = this.deviceModelSearchInfo.SearchText!, PageNumber = state.Page, PageSize = state.PageSize, OrderBy = new string[] { orderBy } - }); - + }); + return new TableData { Items = result.Items, TotalItems = result.TotalItems - }; - - } - catch (ProblemDetailsException exception) - { - Error?.ProcessProblemDetails(exception); - return new TableData(); - } - finally - { - IsLoading = false; - } - } - - /// - /// Sends a GET request to the ThingTypeController, to retrieve all thing types from the database - /// - /// - private async Task> LoadThingTypes(TableState state) - { - try - { - IsLoading = true; - - string orderBy = default!; - - switch (state.SortDirection) - { - case SortDirection.Ascending: - orderBy = $"{state.SortLabel} asc"; - break; - case SortDirection.Descending: - orderBy = $"{state.SortLabel} desc"; - break; - } - - var result = await ThingTypeClientService.GetThingTypes(new DeviceModelFilter - { - SearchText = this.deviceModelSearchInfo.SearchText!, - PageNumber = state.Page, - PageSize = state.PageSize, - OrderBy = new string[] { orderBy } - }); - - return new TableData - { - Items = result.Items, - TotalItems = result.TotalItems - }; - - } - catch (ProblemDetailsException exception) - { - Error?.ProcessProblemDetails(exception); - return new TableData(); - } - finally - { - IsLoading = false; - } - } - - private async Task DeleteDeviceModel(DeviceModelDto deviceModel) - { - var parameters = new DialogParameters(); - parameters.Add("deviceModelID", deviceModel.ModelId); - parameters.Add("deviceModelName", deviceModel.Name); - var result = await dialogService.Show("Confirm Deletion", parameters).Result; - - if (result.Canceled) - { - return; - } - - // Update the list of devices after the deletion - // await LoadDeviceModels(); - await Search(); - } - - private async Task DeleteThingType(ThingTypeDto thingType) - { - var parameters = new DialogParameters(); - parameters.Add("thingTypeID", thingType.ThingTypeID); - parameters.Add("thingTypeName", thingType.ThingTypeName); - var result = await dialogService.Show("Confirm Deletion", parameters).Result; - - if (result.Canceled) - { - return; - } - - // Update the list of devices after the deletion - // await LoadDeviceModels(); - await Search(); - } - - - private void GoToDetails(DeviceModelDto item) - { - navigationManager.NavigateTo($"/device-models/{item.ModelId}{((item.SupportLoRaFeatures && Portal.IsLoRaSupported) ? "?isLora=true" : "")}"); - } - private void GoToThingTypeDetails(ThingTypeDto thingType) - { - navigationManager.NavigateTo($"/device-models/{thingType.ThingTypeID}"); - } - - private async Task Search(DeviceModelSearchInfo? deviceModelSearchInfo = null) - { - if (deviceModelSearchInfo == null) - { - this.deviceModelSearchInfo = new(); - } - else - { - this.deviceModelSearchInfo = deviceModelSearchInfo; - } - - if (Portal.CloudProvider.Equals("Azure")) - { - await table.ReloadServerData(); - - } - else - { - await thingTypeTable.ReloadServerData(); - - } - } - - private async void Refresh() - { - if (Portal.CloudProvider.Equals("Azure")) - { - await table.ReloadServerData(); - - } - else - { - await thingTypeTable.ReloadServerData(); - - } + }; + } + catch (ProblemDetailsException exception) + { + Error?.ProcessProblemDetails(exception); + return new TableData(); + } + finally + { + IsLoading = false; + } + } + + private async Task DeleteDeviceModel(DeviceModelDto deviceModel) + { + var parameters = new DialogParameters(); + parameters.Add("deviceModelID", deviceModel.ModelId); + parameters.Add("deviceModelName", deviceModel.Name); + var result = await dialogService.Show("Confirm Deletion", parameters).Result; + + if (result.Canceled) + { + return; + } + + // Update the list of devices after the deletion + // await LoadDeviceModels(); + await Search(); + } + + private void GoToDetails(DeviceModelDto item) + { + navigationManager.NavigateTo($"/device-models/{item.ModelId}{((item.SupportLoRaFeatures && Portal.IsLoRaSupported) ? "?isLora=true" : "")}"); + } + + private async Task Search(DeviceModelSearchInfo? deviceModelSearchInfo = null) + { + if (deviceModelSearchInfo == null) + { + this.deviceModelSearchInfo = new(); + } + else + { + this.deviceModelSearchInfo = deviceModelSearchInfo; + } + + await table.ReloadServerData(); + } + + private async void Refresh() + { + await table.ReloadServerData(); } -} +} diff --git a/src/AzureIoTHub.Portal.Client/Program.cs b/src/AzureIoTHub.Portal.Client/Program.cs index 6dd47c209..93fc0ee59 100644 --- a/src/AzureIoTHub.Portal.Client/Program.cs +++ b/src/AzureIoTHub.Portal.Client/Program.cs @@ -5,7 +5,6 @@ using AzureIoTHub.Portal.Client; using AzureIoTHub.Portal.Client.Handlers; using AzureIoTHub.Portal.Client.Services; -using AzureIoTHub.Portal.Client.Services.AWS; using AzureIoTHub.Portal.Models.v10; using AzureIoTHub.Portal.Settings; using Blazored.LocalStorage; @@ -88,9 +87,6 @@ _ = builder.Services.AddScoped(); _ = builder.Services.AddScoped(); -//AWS Dependency injection -_ = builder.Services.AddScoped(); - await ConfigurePortalSettings(builder); diff --git a/src/AzureIoTHub.Portal.Client/Services/AWS/IThingTypeClientService.cs b/src/AzureIoTHub.Portal.Client/Services/AWS/IThingTypeClientService.cs deleted file mode 100644 index 3f16b460d..000000000 --- a/src/AzureIoTHub.Portal.Client/Services/AWS/IThingTypeClientService.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) CGI France. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -namespace AzureIoTHub.Portal.Client.Services.AWS -{ - using AzureIoTHub.Portal.Models.v10.AWS; - using AzureIoTHub.Portal.Shared.Models.v10.Filters; - - public interface IThingTypeClientService - { - Task> GetThingTypes(DeviceModelFilter? deviceModelFilter = null); - Task GetThingType(string thingTypeId); - - Task CreateThingType(ThingTypeDto thingType); - Task DeprecateThingType(string thingTypeId); - Task DeleteThingType(string thingTypeId); - Task GetAvatarUrl(string thingTypeId); - - Task ChangeAvatar(string thingTypeId, MultipartFormDataContent avatar); - - } -} diff --git a/src/AzureIoTHub.Portal.Client/Services/AWS/ThingTypeClientService.cs b/src/AzureIoTHub.Portal.Client/Services/AWS/ThingTypeClientService.cs deleted file mode 100644 index db9934d16..000000000 --- a/src/AzureIoTHub.Portal.Client/Services/AWS/ThingTypeClientService.cs +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright (c) CGI France. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -namespace AzureIoTHub.Portal.Client.Services.AWS -{ - using System.Collections.Generic; - using System.Net.Http; - using System.Net.Http.Json; - using System.Threading.Tasks; - using AzureIoTHub.Portal.Models.v10.AWS; - using AzureIoTHub.Portal.Shared.Models.v10.Filters; - using Microsoft.AspNetCore.WebUtilities; - - public class ThingTypeClientService : IThingTypeClientService - { - private readonly HttpClient http; - private readonly string apiUrlBase = "api/aws/thingtypes"; - - - public ThingTypeClientService(HttpClient http) - { - this.http = http; - } - - public async Task> GetThingTypes(DeviceModelFilter? deviceModelFilter = null) - { - var query = new Dictionary - { - { nameof(DeviceModelFilter.SearchText), deviceModelFilter?.SearchText ?? string.Empty }, -#pragma warning disable CA1305 - { nameof(DeviceModelFilter.PageNumber), deviceModelFilter?.PageNumber.ToString() ?? string.Empty }, - { nameof(DeviceModelFilter.PageSize), deviceModelFilter?.PageSize.ToString() ?? string.Empty }, -#pragma warning restore CA1305 - { nameof(DeviceModelFilter.OrderBy), string.Join("", deviceModelFilter?.OrderBy!) ?? string.Empty } - }; - - var uri = QueryHelpers.AddQueryString(this.apiUrlBase, query); - return await this.http.GetFromJsonAsync>(uri) ?? new PaginationResult(); - } - - public async Task GetThingType(string thingTypeId) - { - return await this.http.GetFromJsonAsync($"api/aws/thingtypes/{thingTypeId}")!; - } - - - public async Task CreateThingType(ThingTypeDto thingType) - { - var response = await this.http.PostAsJsonAsync("api/aws/thingtypes", thingType); - - return await response.Content.ReadAsStringAsync(); - } - - public async Task DeprecateThingType(string thingTypeId) - { - var result = await this.http.PutAsync($"api/aws/thingtypes/{thingTypeId}", null); - _ = result.EnsureSuccessStatusCode(); - - } - - public async Task DeleteThingType(string thingTypeId) - { - var result = await this.http.DeleteAsync($"api/aws/thingtypes/{thingTypeId}"); - _ = result.EnsureSuccessStatusCode(); - - } - public Task GetAvatarUrl(string thingTypeId) - { - return this.http.GetStringAsync($"api/aws/thingtypes/{thingTypeId}/avatar"); - } - - public async Task ChangeAvatar(string thingTypeId, MultipartFormDataContent avatar) - { - var result = await this.http.PostAsync($"api/aws/thingtypes/{thingTypeId}/avatar", avatar); - - _ = result.EnsureSuccessStatusCode(); - } - } -} diff --git a/src/AzureIoTHub.Portal.Client/Services/DeviceModelsClientService.cs b/src/AzureIoTHub.Portal.Client/Services/DeviceModelsClientService.cs index 705ee5359..1954ad4ee 100644 --- a/src/AzureIoTHub.Portal.Client/Services/DeviceModelsClientService.cs +++ b/src/AzureIoTHub.Portal.Client/Services/DeviceModelsClientService.cs @@ -43,9 +43,11 @@ public Task GetDeviceModel(string deviceModelId) return this.http.GetFromJsonAsync($"api/models/{deviceModelId}")!; } - public Task CreateDeviceModel(DeviceModelDto deviceModel) + public async Task CreateDeviceModel(DeviceModelDto deviceModel) { - return this.http.PostAsJsonAsync("api/models", deviceModel); + var response = await this.http.PostAsJsonAsync("api/models", deviceModel); + + return await response.Content.ReadFromJsonAsync(); } public Task UpdateDeviceModel(DeviceModelDto deviceModel) diff --git a/src/AzureIoTHub.Portal.Client/Services/IDeviceModelsClientService.cs b/src/AzureIoTHub.Portal.Client/Services/IDeviceModelsClientService.cs index cf67142ae..00ee30857 100644 --- a/src/AzureIoTHub.Portal.Client/Services/IDeviceModelsClientService.cs +++ b/src/AzureIoTHub.Portal.Client/Services/IDeviceModelsClientService.cs @@ -15,7 +15,7 @@ public interface IDeviceModelsClientService Task GetDeviceModel(string deviceModelId); - Task CreateDeviceModel(DeviceModelDto deviceModel); + Task CreateDeviceModel(DeviceModelDto deviceModel); Task UpdateDeviceModel(DeviceModelDto deviceModel); diff --git a/src/AzureIoTHub.Portal.Client/Validators/AWS/ThingTypeValidator.cs b/src/AzureIoTHub.Portal.Client/Validators/AWS/ThingTypeValidator.cs deleted file mode 100644 index 42d1b7725..000000000 --- a/src/AzureIoTHub.Portal.Client/Validators/AWS/ThingTypeValidator.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) CGI France. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -namespace AzureIoTHub.Portal.Client.Validators -{ - using AzureIoTHub.Portal.Models.v10.AWS; - using FluentValidation; - - public class ThingTypeValidator : AbstractValidator - { - public ThingTypeValidator() - { - _ = RuleFor(x => x.ThingTypeName) - .NotEmpty() - .WithMessage("Model name is required."); - } - } -} diff --git a/src/AzureIoTHub.Portal.Domain/Entities/AWS/ThingType.cs b/src/AzureIoTHub.Portal.Domain/Entities/AWS/ThingType.cs deleted file mode 100644 index 15a9df276..000000000 --- a/src/AzureIoTHub.Portal.Domain/Entities/AWS/ThingType.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) CGI France. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -namespace AzureIoTHub.Portal.Domain.Entities.AWS -{ - using System.ComponentModel.DataAnnotations; - using AzureIoTHub.Portal.Domain.Base; - - public class ThingType : EntityBase - { - [Required] - public string Name { get; set; } = default!; - public string? Description { get; set; } = default!; - public bool Deprecated { get; set; } - public ICollection? Tags { get; set; } = default!; - public ICollection? ThingTypeSearchableAttributes { get; set; } = default!; - } -} diff --git a/src/AzureIoTHub.Portal.Domain/Entities/AWS/ThingTypeSearchableAtt.cs b/src/AzureIoTHub.Portal.Domain/Entities/AWS/ThingTypeSearchableAtt.cs deleted file mode 100644 index bdb36c649..000000000 --- a/src/AzureIoTHub.Portal.Domain/Entities/AWS/ThingTypeSearchableAtt.cs +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) CGI France. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -namespace AzureIoTHub.Portal.Domain.Entities.AWS -{ - using AzureIoTHub.Portal.Domain.Base; - - public class ThingTypeSearchableAtt : EntityBase - { - public string Name { get; set; } = default!; - - } -} diff --git a/src/AzureIoTHub.Portal.Domain/Entities/AWS/ThingTypeTag.cs b/src/AzureIoTHub.Portal.Domain/Entities/AWS/ThingTypeTag.cs deleted file mode 100644 index 568a4fa70..000000000 --- a/src/AzureIoTHub.Portal.Domain/Entities/AWS/ThingTypeTag.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) CGI France. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -namespace AzureIoTHub.Portal.Domain.Entities.AWS -{ - using AzureIoTHub.Portal.Domain.Base; - - public class ThingTypeTag : EntityBase - { - public string Key { get; set; } = default!; - public string Value { get; set; } = default!; - - } -} diff --git a/src/AzureIoTHub.Portal.Domain/IRepository.cs b/src/AzureIoTHub.Portal.Domain/IRepository.cs index 6d559624c..b80607aae 100644 --- a/src/AzureIoTHub.Portal.Domain/IRepository.cs +++ b/src/AzureIoTHub.Portal.Domain/IRepository.cs @@ -18,7 +18,6 @@ public interface IRepository where T : EntityBase Task GetByIdAsync(object id, params Expression>[] includes); Task InsertAsync(T obj); - Task InsertAndGetIdAsync(T obj); void Update(T obj); diff --git a/src/AzureIoTHub.Portal.Domain/IUnitOfWork.cs b/src/AzureIoTHub.Portal.Domain/IUnitOfWork.cs index 2dd831d2a..af2e89ad2 100644 --- a/src/AzureIoTHub.Portal.Domain/IUnitOfWork.cs +++ b/src/AzureIoTHub.Portal.Domain/IUnitOfWork.cs @@ -3,8 +3,8 @@ namespace AzureIoTHub.Portal.Domain { - using System.Threading.Tasks; - + using System.Threading.Tasks; + public interface IUnitOfWork { Task SaveAsync(); diff --git a/src/AzureIoTHub.Portal.Domain/Repositories/AWS/IThingTypeRepository.cs b/src/AzureIoTHub.Portal.Domain/Repositories/AWS/IThingTypeRepository.cs deleted file mode 100644 index b711e2698..000000000 --- a/src/AzureIoTHub.Portal.Domain/Repositories/AWS/IThingTypeRepository.cs +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) CGI France. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -namespace AzureIoTHub.Portal.Domain.Repositories -{ - using AzureIoTHub.Portal.Domain.Entities.AWS; - - public interface IThingTypeRepository : IRepository - { - } -} diff --git a/src/AzureIoTHub.Portal.Domain/Repositories/AWS/IThingTypeSearchableAttRepository.cs b/src/AzureIoTHub.Portal.Domain/Repositories/AWS/IThingTypeSearchableAttRepository.cs deleted file mode 100644 index 3bfe4f483..000000000 --- a/src/AzureIoTHub.Portal.Domain/Repositories/AWS/IThingTypeSearchableAttRepository.cs +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) CGI France. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -namespace AzureIoTHub.Portal.Domain.Repositories.AWS -{ - using AzureIoTHub.Portal.Domain.Entities.AWS; - - public interface IThingTypeSearchableAttRepository : IRepository - { - } -} diff --git a/src/AzureIoTHub.Portal.Domain/Repositories/AWS/IThingTypeTagRepository.cs b/src/AzureIoTHub.Portal.Domain/Repositories/AWS/IThingTypeTagRepository.cs deleted file mode 100644 index 808ef16c9..000000000 --- a/src/AzureIoTHub.Portal.Domain/Repositories/AWS/IThingTypeTagRepository.cs +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) CGI France. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -namespace AzureIoTHub.Portal.Domain.Repositories.AWS -{ - using AzureIoTHub.Portal.Domain.Entities.AWS; - - public interface IThingTypeTagRepository : IRepository - { - } -} diff --git a/src/AzureIoTHub.Portal.Domain/Shared/ExternalDeviceModelDto.cs b/src/AzureIoTHub.Portal.Domain/Shared/ExternalDeviceModelDto.cs new file mode 100644 index 000000000..56f3be1e2 --- /dev/null +++ b/src/AzureIoTHub.Portal.Domain/Shared/ExternalDeviceModelDto.cs @@ -0,0 +1,18 @@ +// Copyright (c) CGI France. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace AzureIoTHub.Portal.Domain.Shared +{ + public class ExternalDeviceModelDto + { + public string Id { get; set; } = default!; + + public string Name { get; set; } = default!; + + public string Description { get; set; } = default!; + + public bool IsBuiltin { get; set; } + + public bool SupportLoRaFeatures { get; set; } + } +} diff --git a/src/AzureIoTHub.Portal.Infrastructure/Jobs/AWS/SyncThingTypesJob.cs b/src/AzureIoTHub.Portal.Infrastructure/Jobs/AWS/SyncThingTypesJob.cs index 47e5ed333..aeb5aceeb 100644 --- a/src/AzureIoTHub.Portal.Infrastructure/Jobs/AWS/SyncThingTypesJob.cs +++ b/src/AzureIoTHub.Portal.Infrastructure/Jobs/AWS/SyncThingTypesJob.cs @@ -8,43 +8,35 @@ namespace AzureIoTHub.Portal.Infrastructure.Jobs.AWS using Amazon.IoT.Model; using AutoMapper; using AzureIoTHub.Portal.Application.Managers; - using AzureIoTHub.Portal.Domain; - using AzureIoTHub.Portal.Domain.Entities.AWS; - using AzureIoTHub.Portal.Domain.Exceptions; - using AzureIoTHub.Portal.Domain.Repositories; - using AzureIoTHub.Portal.Domain.Repositories.AWS; - using AzureIoTHub.Portal.Models.v10.AWS; + using AzureIoTHub.Portal.Domain; + using AzureIoTHub.Portal.Domain.Entities; + using AzureIoTHub.Portal.Domain.Repositories; using Microsoft.Extensions.Logging; using Quartz; [DisallowConcurrentExecution] public class SyncThingTypesJob : IJob - { - private readonly IThingTypeRepository thingTypeRepository; - private readonly IThingTypeSearchableAttRepository thingTypeSearchableAttrRepository; - private readonly IThingTypeTagRepository thingTypeTagRepository; - private readonly IDeviceModelImageManager awsImageManager; + { + + private readonly ILogger logger; private readonly IMapper mapper; private readonly IUnitOfWork unitOfWork; + private readonly IDeviceModelRepository deviceModelRepository; private readonly IAmazonIoT amazonIoTClient; - private readonly ILogger logger; + private readonly IDeviceModelImageManager deviceModelImageManager; public SyncThingTypesJob( - IThingTypeRepository thingTypeRepository, - IThingTypeSearchableAttRepository thingTypeSearchableAttrRepository, - IThingTypeTagRepository thingTypeTagRepository, - IDeviceModelImageManager awsImageManager, + ILogger logger, IMapper mapper, - IUnitOfWork unitOfWork, - IAmazonIoT amazonIoTClient, - ILogger logger) + IUnitOfWork unitOfWork, + IDeviceModelRepository deviceModelRepository, + IAmazonIoT amazonIoTClient, + IDeviceModelImageManager awsImageManager) { - this.thingTypeRepository = thingTypeRepository; - this.thingTypeSearchableAttrRepository = thingTypeSearchableAttrRepository; - this.thingTypeTagRepository = thingTypeTagRepository; - this.awsImageManager = awsImageManager; + this.deviceModelImageManager = awsImageManager; this.mapper = mapper; - this.unitOfWork = unitOfWork; + this.unitOfWork = unitOfWork; + this.deviceModelRepository = deviceModelRepository; this.amazonIoTClient = amazonIoTClient; this.logger = logger; } @@ -56,7 +48,7 @@ public async Task Execute(IJobExecutionContext context) { this.logger.LogInformation("Start of sync Thing Types job"); - await SyncThingTypes(); + await SyncThingTypesAsDeviceModels(); this.logger.LogInformation("End of sync Thing Types job"); } @@ -66,165 +58,89 @@ public async Task Execute(IJobExecutionContext context) } } - private async Task SyncThingTypes() + private async Task SyncThingTypesAsDeviceModels() { - var getAllAWSThingTypes = await GetAllAWSThingTypes(); - foreach (var thingTYpe in getAllAWSThingTypes) - { - await CreateOrUpdateThingType(thingTYpe); - } + var thingTypes = await GetAllThingTypes(); + + thingTypes.ForEach(async thingType => + { + await CreateOrUpdateDeviceModel(thingType); + }); //Delete in Database AWS deleted thing types - await DeleteThingTypes(getAllAWSThingTypes); + await DeleteThingTypes(thingTypes); } - private async Task> GetAllAWSThingTypes() - { - var thingTypes = new List(); - - var request = new ListThingTypesRequest(); - var response = await amazonIoTClient.ListThingTypesAsync(request); - - if (response.HttpStatusCode != System.Net.HttpStatusCode.OK) - { - throw new InternalServerErrorException("The request of getting all thing types failed due to an error in the Amazon IoT API."); - - } - else - { - foreach (var thingType in response.ThingTypes) - { + private async Task> GetAllThingTypes() + { + var thingTypes = new List(); + + var nextToken = string.Empty; + + do + { + var request = new ListThingTypesRequest + { + NextToken = nextToken + }; + + var response = await amazonIoTClient.ListThingTypesAsync(request); + + foreach (var thingType in response.ThingTypes) + { var requestDescribeThingType = new DescribeThingTypeRequest { ThingTypeName = thingType.ThingTypeName, - }; - var responseDescribeThingType = await amazonIoTClient.DescribeThingTypeAsync(requestDescribeThingType); - - if (responseDescribeThingType.HttpStatusCode != System.Net.HttpStatusCode.OK) - { - throw new InternalServerErrorException("The request of getting DescribeThingType failed due to an error in the Amazon IoT API."); - - } - else - { - var getAllSearchableAttribute = GetAllSearchableAttributes(responseDescribeThingType); - - //get All tags from ResourceArn - //Because we do not have possiblity to retreive the Tag from DescribeThingTypeResponse and ListThingTypesResponse too. - var tags = await GetAllThingTypeTags(responseDescribeThingType); - - var getThingType = new ThingTypeDto - { - ThingTypeID = responseDescribeThingType.ThingTypeId, - ThingTypeName = responseDescribeThingType.ThingTypeName, - ThingTypeDescription = responseDescribeThingType.ThingTypeProperties.ThingTypeDescription, - ThingTypeSearchableAttDtos = getAllSearchableAttribute, - Deprecated = responseDescribeThingType.ThingTypeMetadata.Deprecated, - Tags = tags - }; - - thingTypes.Add(getThingType); - } - } - } + }; + + thingTypes.Add(await this.amazonIoTClient.DescribeThingTypeAsync(requestDescribeThingType)); + } + + nextToken = response.NextToken; + } + while (!string.IsNullOrEmpty(nextToken)); return thingTypes; } - //To Get All Searchable attributes for a Thing Type - private static List GetAllSearchableAttributes(DescribeThingTypeResponse thingType) - { - var searchableAttrDtos = new List(); - - searchableAttrDtos.AddRange(thingType.ThingTypeProperties.SearchableAttributes.Select(searchAttr => new ThingTypeSearchableAttDto - { - Name = searchAttr - })); + private async Task CreateOrUpdateDeviceModel(DescribeThingTypeResponse thingType) + { + if (thingType.ThingTypeMetadata.Deprecated) + { + return; + } + var deviceModel = this.mapper.Map(thingType); + + var existingDeviceModel = await this.deviceModelRepository.GetByIdAsync(deviceModel.Id); - return searchableAttrDtos; - } - - //To Get All tags for a thing types - private async Task> GetAllThingTypeTags(DescribeThingTypeResponse thingType) - { - var listTagRequets = new ListTagsForResourceRequest + if (existingDeviceModel == null) { - ResourceArn = thingType.ThingTypeArn - }; - - var thingTypeTags = await this.amazonIoTClient.ListTagsForResourceAsync(listTagRequets); - - var tags = new List(); - - tags.AddRange(thingTypeTags.Tags.Select(tag => new ThingTypeTagDto - { - Key = tag.Key, - Value = tag.Value - })); - - return tags; - } - - private async Task CreateOrUpdateThingType(ThingTypeDto thingTypeDto) - { - var existingTHingType = await this.thingTypeRepository.GetByIdAsync(thingTypeDto.ThingTypeID, d => d.ThingTypeSearchableAttributes!, d => d.Tags!); - var thingType = this.mapper.Map(thingTypeDto); - - if (existingTHingType == null) - { - await this.thingTypeRepository.InsertAsync(thingType); - _ = await this.awsImageManager.SetDefaultImageToModel(thingTypeDto.ThingTypeID); + await this.deviceModelRepository.InsertAsync(deviceModel); + _ = await this.deviceModelImageManager.SetDefaultImageToModel(deviceModel.Id); } else { - if (existingTHingType.ThingTypeSearchableAttributes != null - && thingType.ThingTypeSearchableAttributes?.Count != 0) - { - foreach (var search in existingTHingType.ThingTypeSearchableAttributes!) - { - this.thingTypeSearchableAttrRepository.Delete(search.Id); - - } - } - - if (existingTHingType.Tags != null - && thingType.Tags?.Count != 0) - { - foreach (var tag in existingTHingType.Tags!) - { - this.thingTypeTagRepository.Delete(tag.Id); - } - } - - _ = this.mapper.Map(thingType, existingTHingType); - this.thingTypeRepository.Update(existingTHingType); + _ = this.mapper.Map(deviceModel, existingDeviceModel); + this.deviceModelRepository.Update(existingDeviceModel); } await this.unitOfWork.SaveAsync(); } - private async Task DeleteThingTypes(List allAWSThingTypes) - { - foreach (var thingType in (await this.thingTypeRepository.GetAllAsync()).Where(thingType => !allAWSThingTypes.Exists(x => x.ThingTypeID == thingType.Id))) - { - var getThingType = await this.thingTypeRepository.GetByIdAsync(thingType.Id, d => d.ThingTypeSearchableAttributes!, d => d.Tags!); - - if (getThingType != null) - { - foreach (var search in getThingType.ThingTypeSearchableAttributes!) - { - this.thingTypeSearchableAttrRepository.Delete(search.Id); - } - foreach (var tag in getThingType.Tags!) - { - this.thingTypeTagRepository.Delete(tag.Id); - } - this.thingTypeRepository.Delete(getThingType.Id); - _ = this.awsImageManager.DeleteDeviceModelImageAsync(getThingType.Id); - } - - } + private async Task DeleteThingTypes(List thingTypes) + { + // Get all device models that are not in AWS anymore or that are deprecated + var deviceModelsToDelete = (await this.deviceModelRepository.GetAllAsync()) + .Where(deviceModel => !thingTypes.Any(thingType => deviceModel.Id.Equals(thingType.ThingTypeId, StringComparison.Ordinal)) || + thingTypes.Any(thingType => deviceModel.Id.Equals(thingType.ThingTypeId, StringComparison.Ordinal) && thingType.ThingTypeMetadata.Deprecated)) + .ToList(); + + deviceModelsToDelete.ForEach(async deviceModel => + { + await this.deviceModelImageManager.DeleteDeviceModelImageAsync(deviceModel.Id); + this.deviceModelRepository.Delete(deviceModel.Id); + }); await this.unitOfWork.SaveAsync(); } diff --git a/src/AzureIoTHub.Portal.Infrastructure/PortalDbContext.cs b/src/AzureIoTHub.Portal.Infrastructure/PortalDbContext.cs index 12a2bcea8..2cd8457bb 100644 --- a/src/AzureIoTHub.Portal.Infrastructure/PortalDbContext.cs +++ b/src/AzureIoTHub.Portal.Infrastructure/PortalDbContext.cs @@ -3,7 +3,6 @@ namespace AzureIoTHub.Portal.Infrastructure { - using AzureIoTHub.Portal.Domain.Entities.AWS; using Domain.Entities; using Microsoft.EntityFrameworkCore; using Newtonsoft.Json; @@ -23,9 +22,6 @@ public class PortalDbContext : DbContext public DbSet Concentrators { get; set; } public DbSet LoRaDeviceTelemetry { get; set; } public DbSet