diff --git a/src/AzureIoTHub.Portal.Client/Components/Devices/EditDevice.razor b/src/AzureIoTHub.Portal.Client/Components/Devices/EditDevice.razor
new file mode 100644
index 000000000..1ba801cc9
--- /dev/null
+++ b/src/AzureIoTHub.Portal.Client/Components/Devices/EditDevice.razor
@@ -0,0 +1,815 @@
+@using AzureIoTHub.Portal.Client.Pages.Devices
+@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.Shared.Models.v10.Filters;
+@using Microsoft.AspNetCore.WebUtilities
+
+@implements IDisposable
+
+@inject ISnackbar Snackbar
+@inject IDialogService DialogService
+@inject IDeviceModelsClientService DeviceModelsClientService
+@inject ILoRaWanDeviceModelsClientService LoRaWanDeviceModelsClientService
+@inject IDeviceTagSettingsClientService DeviceTagSettingsClientService
+@inject IDeviceClientService DeviceClientService
+@inject ILoRaWanDeviceClientService LoRaWanDeviceClientService
+@inject IDeviceLayoutService DeviceLayoutService
+@inject NavigationManager NavigationManager
+
+
+
+
+
+
+
+ @if (context.Equals(CreateEditMode.Create))
+ {
+ @(string.IsNullOrEmpty(Device.DeviceName) ? Device.DeviceID : Device.DeviceName)
+ }
+ else if (context.Equals(CreateEditMode.Edit))
+ {
+ Model: @DeviceModel.Name
+ @(string.IsNullOrEmpty(Device.DeviceName) ? Device.DeviceID : Device.DeviceName)
+ }
+
+
+
+
+
+
+
+ @if (context.Equals(CreateEditMode.Edit))
+ {
+
+ @if (isLoaded && (!IsLoRa || !(Device is LoRaDeviceDetails)))
+ {
+ Connect
+ }
+
+ }
+
+ @if (context.Equals(CreateEditMode.Create))
+ {
+
+
+ @saveButtonText
+
+ Save
+ Save and add new
+ Save and duplicate
+
+
+
+ }
+ else if (context.Equals(CreateEditMode.Edit))
+ {
+
+ Delete device
+
+ @saveButtonText
+
+ Save
+ Duplicate
+
+
+
+ }
+
+
+
+
+
+
+
+
+ Details
+
+
+ @if (context.Equals(CreateEditMode.Create))
+ {
+
+ @if (duplicateDevice)
+ {
+
+ }
+ else
+ {
+ this.DeviceModel)
+ Variant="Variant.Outlined"
+ ToStringFunc="@(x => x?.Name)"
+ ResetValueOnEmptyText=true
+ Immediate=true
+ Clearable=true
+ CoerceText=true
+ CoerceValue=false>
+
+ @context.Name
+
+ @((!string.IsNullOrEmpty(@context.Description) && @context.Description.Length > 100) ? @context.Description.Substring(0, 100) + "..." : @context.Description)
+
+
+
+ @if (Device.ModelId == null && displayValidationErrorMessages)
+ {
+ The Model is required.
+ }
+ }
+
+ }
+
+ @if (context.Equals(CreateEditMode.Create))
+ {
+
+ @if (IsLoRa)
+ {
+
+ }
+ else
+ {
+
+ }
+
+ }
+ else if (context.Equals(CreateEditMode.Edit))
+ {
+
+
+
+ }
+
+ @if (context.Equals(CreateEditMode.Create))
+ {
+
+
+
+ }
+
+
+
+
+
+
+
+ Status
+
+
+
+ Enabled
+ The device can connect to the platform.
+
+
+ Disabled
+ The device cannot connect to the platform.
+
+
+
+
+
+
+
+
+
+
+
+ Tags
+
+
+ @foreach (DeviceTagDto tag in TagList)
+ {
+ if (context.Equals(CreateEditMode.Create))
+ {
+
+
+
+ }
+ else if (context.Equals(CreateEditMode.Edit))
+ {
+
+ @if (!Device.Tags.ContainsKey(tag.Name))
+ {
+ Device.Tags.Add(tag.Name, "");
+ }
+
+
+ }
+ }
+
+
+
+
+
+
+
+
+
+ Labels
+
+
+ @if (context.Equals(CreateEditMode.Create))
+ {
+
+ }
+ else if (context.Equals(CreateEditMode.Edit))
+ {
+
+ }
+
+
+
+
+
+
+ @if (!IsLoRa && Properties.Any())
+ {
+
+
+
+ Properties
+
+
+ @foreach (var item in Properties.OrderBy(c => c.Order))
+ {
+ switch (item.PropertyType)
+ {
+ case DevicePropertyType.Boolean:
+
+
+
+ break;
+ case DevicePropertyType.Double:
+
+ string.IsNullOrEmpty(c) || double.TryParse(c, out var result))
+ Clearable="true" />
+
+ break;
+ case DevicePropertyType.Float:
+
+ string.IsNullOrEmpty(c) || float.TryParse(c, out var result))
+ Clearable="true" />
+
+ break;
+ case DevicePropertyType.Integer:
+
+ string.IsNullOrEmpty(c) || int.TryParse(c, out var result))
+ Clearable="true" />
+
+ break;
+ case DevicePropertyType.Long:
+
+ string.IsNullOrEmpty(c) || long.TryParse(c, out var result))
+ Clearable="true" />
+
+ break;
+ case DevicePropertyType.String:
+
+
+
+ break;
+ }
+ }
+
+
+
+
+
+ }
+
+
+
+ @if (context.Equals(CreateEditMode.Create))
+ {
+ if (IsLoRa)
+ {
+
+
+
+ }
+ }
+ else if (context.Equals(CreateEditMode.Edit))
+ {
+ if (IsLoRa && Device != null && Commands != null)
+ {
+
+
+
+ }
+ }
+
+
+
+
+
+@code {
+ [Parameter]
+ public CreateEditMode context { get; set; }
+
+ [CascadingParameter]
+ public Error Error { get; set; } = default!;
+
+ private MudForm form = default!;
+
+ private DeviceDetailsValidator standardValidator = new DeviceDetailsValidator();
+ private LoRaDeviceDetailsValidator loraValidator = new LoRaDeviceDetailsValidator();
+
+ private LoRaDeviceModelDto LoRaDeviceModelDto { get; set; } = default!;
+
+ private IDeviceDetails Device { get; set; } = new DeviceDetails();
+
+ public PatternMask maskLoRaDeviceID = new PatternMask("XXXXXXXXXXXXXXXX")
+ {
+ MaskChars = new[] { new MaskChar('X', @"[0-9a-fA-F]") },
+ CleanDelimiters = false,
+ Transformation = AllUpperCase
+ };
+
+ private static char AllUpperCase(char c) => c.ToString().ToUpperInvariant()[0];
+
+ private IDeviceModel _deviceModel = default!;
+
+ private IDeviceModel DeviceModel
+ {
+ get
+ {
+ return _deviceModel;
+ }
+ set
+ {
+ if (context.Equals(CreateEditMode.Create))
+ {
+ Task.Run(async () => await ChangeModel(value));
+ }
+ else if (context.Equals(CreateEditMode.Edit))
+ {
+ _deviceModel = value;
+ }
+ }
+ }
+
+ private IEnumerable TagList { get; set; } = Array.Empty();
+ private List Properties = new List();
+
+ private bool displayValidationErrorMessages;
+
+ private bool duplicateDevice;
+
+ private DeviceSaveAction deviceSaveAction = DeviceSaveAction.Save;
+ private string saveButtonText = "Save";
+
+ [Parameter]
+ public string DeviceID { get; set; } = default!;
+
+ private bool isLoaded;
+
+ private bool isProcessing;
+
+ private IEnumerable Commands { get; set; } = Array.Empty();
+
+ private void setPropertiesWithContext()
+ {
+ if (context.Equals(CreateEditMode.Create))
+ {
+ TagList = new List();
+
+ displayValidationErrorMessages = false;
+ }
+ else if (context.Equals(CreateEditMode.Edit))
+ {
+ DeviceModel = new DeviceModelDto();
+
+ isLoaded = false;
+
+ TagList = Array.Empty();
+ }
+ }
+
+ [Parameter]
+ //[SupplyParameterFromQuery]
+ public bool IsLoRa { get; set; }
+
+ //private void setIsLoRaWithContext()
+ //{
+ // if (context.Equals(CreateEditMode.Create))
+ // {
+ // IsLoRa = Device is LoRaDeviceDetails;
+ // }
+ // else if (context.Equals(CreateEditMode.Edit))
+ // {
+ // var uri = NavigationManager.ToAbsoluteUri(NavigationManager.Uri);
+
+ // IsLoRa = QueryHelpers.ParseQuery(uri.Query).TryGetValue("isLora", out var isLora);
+ // }
+ //}
+
+ protected override async Task OnInitializedAsync()
+ {
+ setPropertiesWithContext();
+ //setIsLoRaWithContext();
+
+ try
+ {
+ if (context.Equals(CreateEditMode.Create))
+ {
+ DeviceLayoutService.RefreshDeviceOccurred += DeviceServiceOnRefreshDeviceOccurred!;
+ Device = DeviceLayoutService.GetSharedDevice() ?? this.Device;
+ DeviceModel = DeviceLayoutService.GetSharedDeviceModel() ?? this.DeviceModel;
+
+ // Enable device by default
+ Device.IsEnabled = true;
+
+ // Gets the custom tags that can be set when creating a device
+ TagList = await DeviceTagSettingsClientService.GetDeviceTags();
+
+ foreach (var tag in TagList)
+ {
+ Device.Tags.TryAdd(tag.Name, string.Empty);
+ }
+ }
+ else if (context.Equals(CreateEditMode.Edit))
+ {
+ isProcessing = true;
+
+ if (IsLoRa)
+ {
+ Device = await LoRaWanDeviceClientService.GetDevice(DeviceID);
+ Commands = await LoRaWanDeviceModelsClientService.GetDeviceModelCommands(Device.ModelId);
+ DeviceModel = await LoRaWanDeviceModelsClientService.GetDeviceModel(Device.ModelId);
+ }
+ else
+ {
+ Device = await DeviceClientService.GetDevice(DeviceID);
+ Properties = (await DeviceClientService.GetDeviceProperties(DeviceID)).ToList();
+ DeviceModel = await DeviceModelsClientService.GetDeviceModel(Device.ModelId);
+ }
+
+ TagList = await DeviceTagSettingsClientService.GetDeviceTags();
+
+ isLoaded = true;
+ isProcessing = false;
+ }
+ }
+ catch (ProblemDetailsException exception)
+ {
+ Error?.ProcessProblemDetails(exception);
+ }
+ }
+
+ private void ProcessActionOnDevice()
+ {
+ switch (deviceSaveAction)
+ {
+ case DeviceSaveAction.Save:
+ Save();
+ break;
+ case DeviceSaveAction.Duplicate:
+ DeviceLayoutService.DuplicateSharedDevice(Device);
+ DeviceLayoutService.DuplicateSharedDeviceModel(DeviceModel);
+ NavigationManager.NavigateTo("devices/new");
+ break;
+ }
+ }
+
+ public void Dispose()
+ {
+ DeviceLayoutService.ResetSharedDevice();
+ DeviceLayoutService.ResetSharedDeviceModel();
+ DeviceLayoutService.RefreshDeviceOccurred -= DeviceServiceOnRefreshDeviceOccurred!;
+ }
+
+ ///
+ /// Sends a POST request to the DevicesController, to add the new device to the Azure IoT Hub
+ ///
+ public async void Save()
+ {
+ try
+ {
+ isProcessing = true;
+
+ if (context.Equals(CreateEditMode.Create))
+ {
+ await form.Validate();
+
+ if (CheckTagsError() || CheckGeneralValidation() || CheckLoRaValidation())
+ {
+ Snackbar.Add("One or more validation errors occurred", Severity.Error);
+
+ // Allows to display ValidationError messages for the MudAutocomplete field.
+ displayValidationErrorMessages = true;
+
+ isProcessing = false;
+
+ return;
+ }
+
+ if (IsLoRa)
+ await LoRaWanDeviceClientService.CreateDevice((Device as LoRaDeviceDetails)!);
+ else
+ {
+ await DeviceClientService.CreateDevice((Device as DeviceDetails)!);
+ await DeviceClientService.SetDeviceProperties(Device.DeviceID, Properties);
+ }
+
+ // Prompts a snack bar to inform the action was successful
+ Snackbar.Add($"Device {Device.DeviceID} has been successfully created!\r\nPlease note that changes might take some minutes to be visible in the list...", Severity.Success);
+
+ ProcessPostDeviceCreation();
+ }
+ else if (context.Equals(CreateEditMode.Edit))
+ {
+ await form.Validate();
+
+ if (CheckTagsError() || CheckGeneralValidation() || CheckLoRaValidation())
+ {
+ Snackbar.Add("One or more validation errors occurred", Severity.Error);
+
+ isProcessing = false;
+
+ return;
+ }
+
+ if (IsLoRa)
+ {
+ await LoRaWanDeviceClientService.UpdateDevice((Device as LoRaDeviceDetails)!);
+ }
+ else
+ {
+ await DeviceClientService.UpdateDevice((Device as DeviceDetails)!);
+
+ await DeviceClientService.SetDeviceProperties(DeviceID, Properties.ToList());
+ }
+
+ // Prompts a snack bar to inform the action was successful
+ Snackbar.Add($"Device {Device.DeviceName} has been successfully updated!\r\nPlease note that changes might take some minutes to be visible in the list...", Severity.Success, null);
+
+ NavigationManager.NavigateTo("devices");
+ }
+ }
+ catch (ProblemDetailsException exception)
+ {
+ Error?.ProcessProblemDetails(exception);
+ }
+ finally
+ {
+ isProcessing = false;
+ if (context.Equals(CreateEditMode.Edit)) StateHasChanged();
+ }
+ }
+
+ public async Task ShowConnectionString()
+ {
+ var parameters = new DialogParameters();
+ parameters.Add(nameof(ConnectionStringDialog.deviceId), this.DeviceID);
+
+ _ = await DialogService.Show("Device Credentials", parameters).Result;
+ }
+
+ private void ProcessPostDeviceCreation()
+ {
+ switch (deviceSaveAction)
+ {
+ case DeviceSaveAction.Save:
+ NavigationManager.NavigateTo("devices");
+ break;
+ case DeviceSaveAction.SaveAndAddNew:
+ Device = DeviceLayoutService.ResetSharedDevice(TagList.ToList());
+ DeviceModel = DeviceLayoutService.ResetSharedDeviceModel();
+ break;
+ case DeviceSaveAction.SaveAndDuplicate:
+ Device = DeviceLayoutService.DuplicateSharedDevice((Device as DeviceDetails)!);
+ DeviceModel = DeviceLayoutService.DuplicateSharedDeviceModel(DeviceModel);
+ break;
+ }
+ }
+
+ private void DeviceServiceOnRefreshDeviceOccurred(object sender, EventArgs e)
+ {
+ Device = DeviceLayoutService.GetSharedDevice();
+ DeviceModel = DeviceLayoutService.GetSharedDeviceModel();
+ StateHasChanged();
+ }
+
+ private bool CheckTagsError()
+ {
+ bool tagValidationError = false;
+
+ foreach (DeviceTagDto tag in TagList)
+ {
+ if (tag.Required && string.IsNullOrEmpty(Device.Tags[tag.Name]))
+ {
+ tagValidationError = true;
+ }
+ }
+ return tagValidationError;
+ }
+
+ private bool CheckLoRaValidation()
+ {
+ if (!IsLoRa)
+ {
+ return false;
+ }
+
+ if (this.Device is LoRaDeviceDetails loRaDeviceDetails)
+ {
+ return !this.loraValidator.Validate(loRaDeviceDetails).IsValid;
+ }
+
+ return true;
+ }
+
+ private bool CheckGeneralValidation()
+ {
+ if (!IsLoRa && this.Device is DeviceDetails deviceDetails)
+ {
+ return !this.standardValidator.Validate(deviceDetails).IsValid;
+ }
+
+ return CheckLoRaValidation();
+ }
+
+ ///
+ /// Prompts a pop-up windows to confirm the device's deletion.
+ ///
+ /// Device to delete from the hub
+ ///
+ private async Task DeleteDevice()
+ {
+ isProcessing = true;
+
+ var parameters = new DialogParameters
+ {
+ {"deviceID", Device.DeviceID},
+ {"deviceName", Device.DeviceName},
+ {"IsLoRaWan", IsLoRa}
+ };
+ var result = await DialogService.Show("Confirm Deletion", parameters).Result;
+
+ isProcessing = false;
+
+ if (result.Canceled)
+ {
+ return;
+ }
+
+ // Go back to the list of devices
+ NavigationManager.NavigateTo("devices");
+ }
+
+ private async Task> Search(string value)
+ {
+ var filter = new DeviceModelFilter
+ {
+ SearchText = value,
+ PageNumber = 0,
+ PageSize = 100,
+ OrderBy = new string[]
+ {
+ string.Empty
+ }
+ };
+ return (await DeviceModelsClientService.GetDeviceModels(filter)).Items.ToList();
+ }
+
+ private void SetSaveButtonText(DeviceSaveAction saveAction)
+ {
+ if (context.Equals(CreateEditMode.Create))
+ {
+ deviceSaveAction = saveAction;
+ saveButtonText = deviceSaveAction switch
+ {
+ DeviceSaveAction.Save => "Save",
+ DeviceSaveAction.SaveAndAddNew => "Save and add new",
+ DeviceSaveAction.SaveAndDuplicate => "Save and duplicate",
+ _ => saveButtonText
+ };
+ }
+ else if (context.Equals(CreateEditMode.Edit))
+ {
+ deviceSaveAction = saveAction;
+ saveButtonText = deviceSaveAction switch
+ {
+ DeviceSaveAction.Save => "Save",
+ DeviceSaveAction.Duplicate => "Duplicate",
+ _ => saveButtonText
+ };
+ }
+ }
+
+ internal async Task ChangeModel(IDeviceModel model)
+ {
+ try
+ {
+ Properties.Clear();
+
+ _deviceModel = model;
+
+ Device = new DeviceDetails
+ {
+ DeviceID = Device.DeviceID,
+ ModelId = model?.ModelId,
+ ImageUrl = model?.ImageUrl,
+ DeviceName = Device.DeviceName,
+ IsEnabled = Device.IsEnabled,
+ Tags = Device.Tags
+ };
+
+ if (model == null || string.IsNullOrWhiteSpace(model.ModelId))
+ {
+ return;
+ }
+
+ if (model.SupportLoRaFeatures)
+ {
+ var loRaDeviceModelResult = await LoRaWanDeviceModelsClientService.GetDeviceModel(model.ModelId);
+
+ this.Device = new LoRaDeviceDetails
+ {
+ DeviceID = this.Device.DeviceID,
+ ModelId = model.ModelId,
+ ImageUrl = model.ImageUrl,
+ DeviceName = this.Device.DeviceName,
+ IsEnabled = this.Device.IsEnabled,
+ Tags = this.Device.Tags,
+ SensorDecoder = loRaDeviceModelResult.SensorDecoder,
+ UseOTAA = loRaDeviceModelResult.UseOTAA
+ };
+
+ LoRaDeviceModelDto = loRaDeviceModelResult;
+
+ IsLoRa = true;
+ }
+ else
+ {
+ var properties = await DeviceModelsClientService.GetDeviceModelModelProperties(model.ModelId);
+
+ Properties.AddRange(properties.Select(x => new DevicePropertyValue
+ {
+ DisplayName = x.DisplayName,
+ IsWritable = x.IsWritable,
+ Name = x.Name,
+ Order = x.Order,
+ PropertyType = x.PropertyType
+ }));
+
+ IsLoRa = false;
+ }
+ }
+ catch (ProblemDetailsException exception)
+ {
+ Error?.ProcessProblemDetails(exception);
+ }
+ finally
+ {
+ await InvokeAsync(StateHasChanged);
+ }
+ }
+}
diff --git a/src/AzureIoTHub.Portal.Client/Enums/CreateEditMode.cs b/src/AzureIoTHub.Portal.Client/Enums/CreateEditMode.cs
new file mode 100644
index 000000000..94873bfaf
--- /dev/null
+++ b/src/AzureIoTHub.Portal.Client/Enums/CreateEditMode.cs
@@ -0,0 +1,11 @@
+// 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.Enums
+{
+ public enum CreateEditMode
+ {
+ Create,
+ Edit
+ }
+}
diff --git a/src/AzureIoTHub.Portal.Client/Pages/Devices/CreateDevicePage.razor b/src/AzureIoTHub.Portal.Client/Pages/Devices/CreateDevicePage.razor
index 7674f9b22..81c466ec7 100644
--- a/src/AzureIoTHub.Portal.Client/Pages/Devices/CreateDevicePage.razor
+++ b/src/AzureIoTHub.Portal.Client/Pages/Devices/CreateDevicePage.razor
@@ -1,547 +1,7 @@
@page "/devices/new"
-@using AzureIoTHub.Portal.Client.Pages.Devices
-@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.Shared.Models.v10;
-@using AzureIoTHub.Portal.Shared.Models.v10.Filters;
-@using Microsoft.AspNetCore.Components
-
-@attribute [Authorize]
-@inject ISnackbar Snackbar
-@inject NavigationManager NavManager
-@inject IDeviceModelsClientService DeviceModelsClientService
-@inject ILoRaWanDeviceModelsClientService LoRaWanDeviceModelsClientService
-@inject IDeviceTagSettingsClientService DeviceTagSettingsClientService
-@inject IDeviceClientService DeviceClientService
-@inject ILoRaWanDeviceClientService LoRaWanDeviceClientService
-@inject IDeviceLayoutService DeviceLayoutService
-
-@implements IDisposable
-
Create Device
-
-
-
-
-
-
- @(string.IsNullOrEmpty(Device.DeviceName) ? Device.DeviceID : Device.DeviceName)
-
-
-
-
-
-
-
-
-
-
- @saveButtonText
-
- Save
- Save and add new
- Save and duplicate
-
-
-
-
-
-
-
-
-
-
-
- Details
-
-
-
- @if (duplicateDevice)
- {
-
- }
- else
- {
- this.DeviceModel)
- Variant="Variant.Outlined"
- ToStringFunc="@(x => x?.Name)"
- ResetValueOnEmptyText=true
- Immediate=true
- Clearable=true
- CoerceText=true
- CoerceValue=false>
-
- @context.Name
-
- @((!string.IsNullOrEmpty(@context.Description) && @context.Description.Length > 100) ? @context.Description.Substring(0, 100) + "..." : @context.Description)
-
-
-
- @if (Device.ModelId == null && displayValidationErrorMessages)
- {
- The Model is required.
- }
- }
-
-
- @if (IsLoRa)
- {
-
- }
- else
- {
-
- }
-
-
-
-
-
-
-
-
-
- Status
-
-
-
- Enabled
- The device can connect to the platform.
-
-
- Disabled
- The device cannot connect to the platform.
-
-
-
-
-
-
-
-
-
-
-
- Tags
-
-
- @foreach (DeviceTagDto tag in TagList)
- {
-
-
-
- }
-
-
-
-
-
-
-
-
-
- Labels
-
-
-
-
-
-
-
- @if (!IsLoRa && Properties.Any())
- {
-
-
-
- Properties
-
-
- @foreach (var item in Properties.OrderBy(c => c.Order))
- {
- switch (item.PropertyType)
- {
- case DevicePropertyType.Boolean:
-
-
-
- break;
- case DevicePropertyType.Double:
-
- string.IsNullOrEmpty(c) || double.TryParse(c, out var result))
- Clearable="true" />
-
- break;
- case DevicePropertyType.Float:
-
- string.IsNullOrEmpty(c) || float.TryParse(c, out var result))
- Clearable="true" />
-
- break;
- case DevicePropertyType.Integer:
-
- string.IsNullOrEmpty(c) || int.TryParse(c, out var result))
- Clearable="true" />
-
- break;
- case DevicePropertyType.Long:
-
- string.IsNullOrEmpty(c) || long.TryParse(c, out var result))
- Clearable="true" />
-
- break;
- case DevicePropertyType.String:
-
-
-
- break;
- }
- }
-
-
-
-
-
- }
-
-
- @if (IsLoRa)
- {
-
-
-
- }
-
-
-
-
+
@code {
- [CascadingParameter]
- public Error Error { get; set; } = default!;
-
- private IDeviceDetails Device = new DeviceDetails();
-
- private MudForm form = default!;
-
- private DeviceDetailsValidator standardValidator = new DeviceDetailsValidator();
- private LoRaDeviceDetailsValidator loraValidator = new LoRaDeviceDetailsValidator();
- private LoRaDeviceModelDto LoRaDeviceModelDto { get; set; } = default!;
-
- private bool IsLoRa
- {
- get
- {
- return Device is LoRaDeviceDetails;
- }
- }
-
- public PatternMask maskLoRaDeviceID = new PatternMask("XXXXXXXXXXXXXXXX")
- {
- MaskChars = new[] { new MaskChar('X', @"[0-9a-fA-F]") },
- CleanDelimiters = false,
- Transformation = AllUpperCase
- };
-
- private static char AllUpperCase(char c) => c.ToString().ToUpperInvariant()[0];
-
- private IDeviceModel _deviceModel = default!;
-
- private IDeviceModel DeviceModel
- {
- get => _deviceModel;
- set { Task.Run(async () => await ChangeModel(value)); }
- }
-
- private IEnumerable TagList { get; set; } = new List();
- private List Properties = new List();
-
- private bool displayValidationErrorMessages = false;
-
- private bool isProcessing;
- private bool duplicateDevice;
-
- private DeviceSaveAction deviceSaveAction = DeviceSaveAction.Save;
- private string saveButtonText = "Save";
-
- protected override async Task OnInitializedAsync()
- {
- try
- {
- DeviceLayoutService.RefreshDeviceOccurred += DeviceServiceOnRefreshDeviceOccurred!;
- Device = DeviceLayoutService.GetSharedDevice() ?? this.Device;
- DeviceModel = DeviceLayoutService.GetSharedDeviceModel() ?? this.DeviceModel;
-
- // Enable device by default
- Device.IsEnabled = true;
-
- // Gets the custom tags that can be set when creating a device
- TagList = await DeviceTagSettingsClientService.GetDeviceTags();
-
- foreach (var tag in TagList)
- {
- Device.Tags.TryAdd(tag.Name, string.Empty);
- }
- }
- catch (ProblemDetailsException exception)
- {
- Error?.ProcessProblemDetails(exception);
- }
- }
-
- public void Dispose()
- {
- DeviceLayoutService.ResetSharedDevice();
- DeviceLayoutService.ResetSharedDeviceModel();
- DeviceLayoutService.RefreshDeviceOccurred -= DeviceServiceOnRefreshDeviceOccurred!;
- }
-
- ///
- /// Sends a POST request to the DevicesController, to add the new device to the Azure IoT Hub
- ///
- public async void Save()
- {
- try
- {
- isProcessing = true;
-
- await form.Validate();
-
- if (CheckTagsError() || CheckGeneralValidation() || CheckLoRaValidation())
- {
- Snackbar.Add("One or more validation errors occurred", Severity.Error);
-
- // Allows to display ValidationError messages for the MudAutocomplete field.
- displayValidationErrorMessages = true;
-
- isProcessing = false;
-
- return;
- }
-
- if (IsLoRa)
- await LoRaWanDeviceClientService.CreateDevice((Device as LoRaDeviceDetails)!);
- else
- {
- await DeviceClientService.CreateDevice((Device as DeviceDetails)!);
- await DeviceClientService.SetDeviceProperties(Device.DeviceID, Properties);
- }
-
- // Prompts a snack bar to inform the action was successful
- Snackbar.Add($"Device {Device.DeviceID} has been successfully created!\r\nPlease note that changes might take some minutes to be visible in the list...", Severity.Success);
-
- ProcessPostDeviceCreation();
- }
- catch (ProblemDetailsException exception)
- {
- Error?.ProcessProblemDetails(exception);
- }
- finally
- {
- isProcessing = false;
- }
- }
-
- private void ProcessPostDeviceCreation()
- {
- switch (deviceSaveAction)
- {
- case DeviceSaveAction.Save:
- NavManager.NavigateTo("devices");
- break;
- case DeviceSaveAction.SaveAndAddNew:
- Device = DeviceLayoutService.ResetSharedDevice(TagList.ToList());
- DeviceModel = DeviceLayoutService.ResetSharedDeviceModel();
- break;
- case DeviceSaveAction.SaveAndDuplicate:
- Device = DeviceLayoutService.DuplicateSharedDevice((Device as DeviceDetails)!);
- DeviceModel = DeviceLayoutService.DuplicateSharedDeviceModel(DeviceModel);
- break;
- }
- }
-
- private void DeviceServiceOnRefreshDeviceOccurred(object sender, EventArgs e)
- {
- Device = DeviceLayoutService.GetSharedDevice();
- DeviceModel = DeviceLayoutService.GetSharedDeviceModel();
- StateHasChanged();
- }
-
- private bool CheckTagsError()
- {
- bool tagValidationError = false;
-
- foreach (DeviceTagDto tag in TagList)
- {
- if (tag.Required && string.IsNullOrEmpty(Device.Tags[tag.Name]))
- {
- tagValidationError = true;
- }
- }
- return tagValidationError;
- }
-
- private bool CheckLoRaValidation()
- {
- if (!IsLoRa)
- {
- return false;
- }
-
- if (this.Device is LoRaDeviceDetails loRaDeviceDetails)
- {
- return !this.loraValidator.Validate(loRaDeviceDetails).IsValid;
- }
-
- return true;
- }
-
- private bool CheckGeneralValidation()
- {
- if (!IsLoRa && this.Device is DeviceDetails deviceDetails)
- {
- return !this.standardValidator.Validate(deviceDetails).IsValid;
- }
-
- return CheckLoRaValidation();
- }
-
- ///
- /// Allows to autocomplete the Device Model field in the form.
- ///
- /// Text entered in the field
- /// Item of the device model list that matches the user's value
- private async Task> Search(string value)
- {
- var filter = new DeviceModelFilter
- {
- SearchText = value,
- PageNumber = 0,
- PageSize = 100,
- OrderBy = new string[]
- {
- string.Empty
- }
- };
- return (await DeviceModelsClientService.GetDeviceModels(filter)).Items.ToList();
- }
-
- private void SetSaveButtonText(DeviceSaveAction saveAction)
- {
- deviceSaveAction = saveAction;
- saveButtonText = deviceSaveAction switch
- {
- DeviceSaveAction.Save => "Save",
- DeviceSaveAction.SaveAndAddNew => "Save and add new",
- DeviceSaveAction.SaveAndDuplicate => "Save and duplicate",
- _ => saveButtonText
- };
- }
-
- internal async Task ChangeModel(IDeviceModel model)
- {
- try
- {
- Properties.Clear();
-
- _deviceModel = model;
-
- Device = new DeviceDetails
- {
- DeviceID = Device.DeviceID,
- ModelId = model?.ModelId!,
- ImageUrl = model?.ImageUrl!,
- DeviceName = Device.DeviceName,
- IsEnabled = Device.IsEnabled,
- Tags = Device.Tags
- };
-
- if (model == null || string.IsNullOrWhiteSpace(model.ModelId))
- {
- return;
- }
-
- if (model.SupportLoRaFeatures)
- {
- var loRaDeviceModelResult = await LoRaWanDeviceModelsClientService.GetDeviceModel(model.ModelId);
-
- this.Device = new LoRaDeviceDetails
- {
- DeviceID = this.Device.DeviceID,
- ModelId = model.ModelId,
- ImageUrl = model.ImageUrl,
- DeviceName = this.Device.DeviceName,
- IsEnabled = this.Device.IsEnabled,
- Tags = this.Device.Tags,
- SensorDecoder = loRaDeviceModelResult.SensorDecoder,
- UseOTAA = loRaDeviceModelResult.UseOTAA
- };
-
- LoRaDeviceModelDto = loRaDeviceModelResult;
- }
- else
- {
- var properties = await DeviceModelsClientService.GetDeviceModelModelProperties(model.ModelId);
-
- Properties.AddRange(properties.Select(x => new DevicePropertyValue
- {
- DisplayName = x.DisplayName,
- IsWritable = x.IsWritable,
- Name = x.Name,
- Order = x.Order,
- PropertyType = x.PropertyType
- }));
- }
- }
- catch (ProblemDetailsException exception)
- {
- Error?.ProcessProblemDetails(exception);
- }
- finally
- {
- await InvokeAsync(StateHasChanged);
- }
- }
}
-
diff --git a/src/AzureIoTHub.Portal.Client/Pages/Devices/DeviceDetailPage.razor b/src/AzureIoTHub.Portal.Client/Pages/Devices/DeviceDetailPage.razor
index 07cc10339..fae06f5e7 100644
--- a/src/AzureIoTHub.Portal.Client/Pages/Devices/DeviceDetailPage.razor
+++ b/src/AzureIoTHub.Portal.Client/Pages/Devices/DeviceDetailPage.razor
@@ -1,232 +1,16 @@
@page "/devices/{DeviceID}"
-@using AzureIoTHub.Portal.Client.Pages.Devices
-@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
-@attribute [Authorize]
@inject NavigationManager NavManager
-@inject ISnackbar Snackbar
-@inject IDialogService DialogService
-@inject IDeviceModelsClientService DeviceModelsClientService
-@inject ILoRaWanDeviceModelsClientService LoRaWanDeviceModelsClientService
-@inject IDeviceTagSettingsClientService DeviceTagSettingsClientService
-@inject IDeviceClientService DeviceClientService
-@inject ILoRaWanDeviceClientService LoRaWanDeviceClientService
-@inject IDeviceLayoutService DeviceLayoutService
-
+
- Device Details
+ Device Details
-
-
-
-
-
-
-
-
-
+
-
-
- @if (isLoaded && (!IsLoRa || !(Device is LoRaDeviceDetails)))
- {
- Connect
- }
-
-
-
-
- Delete device
-
- @saveButtonText
-
- Save
- Duplicate
-
-
-
-
-
-
-
-
-
-
-
-
- Details
-
-
-
-
-
-
-
-
-
-
- Status
-
-
-
- Enabled
- The device can connect to the platform.
-
-
- Disabled
- The device cannot connect to the platform.
-
-
-
-
-
-
-
-
-
-
-
- Tags
-
-
- @foreach (DeviceTagDto tag in TagList)
- {
-
- @if (!Device.Tags.ContainsKey(tag.Name))
- {
- Device.Tags.Add(tag.Name, "");
- }
-
-
- }
-
-
-
-
-
-
-
-
-
- Labels
-
-
-
-
-
-
-
- @if (!IsLoRa && Properties.Any())
- {
-
-
-
- Properties
-
-
- @foreach (var item in Properties.OrderBy(c => c.Order))
- {
- switch (item.PropertyType)
- {
- case DevicePropertyType.Boolean:
-
-
-
- break;
- case DevicePropertyType.Double:
-
- string.IsNullOrEmpty(c) || double.TryParse(c, out var result))
- Clearable="true" />
-
- break;
- case DevicePropertyType.Float:
-
- string.IsNullOrEmpty(c) || float.TryParse(c, out var result))
- Clearable="true" />
-
- break;
- case DevicePropertyType.Integer:
-
- string.IsNullOrEmpty(c) || int.TryParse(c, out var result))
- Clearable="true" />
-
- break;
- case DevicePropertyType.Long:
-
- string.IsNullOrEmpty(c) || long.TryParse(c, out var result))
- Clearable="true" />
-
- break;
- case DevicePropertyType.String:
-
-
-
- break;
- }
-
- }
-
-
-
-
-
- }
-
-
- @if (IsLoRa && Device != null && Commands != null)
- {
-
-
-
- }
-
-
-
-
@code {
- [CascadingParameter]
- public Error Error { get; set; } = default!;
-
- private MudForm form = default!;
-
[Parameter]
[SupplyParameterFromQuery]
public bool IsLoRa
@@ -235,206 +19,8 @@
set;
}
- private DeviceDetailsValidator standardValidator = new DeviceDetailsValidator();
- private LoRaDeviceDetailsValidator loraValidator = new LoRaDeviceDetailsValidator();
-
[Parameter]
public string DeviceID { get; set; } = default!;
- private IDeviceDetails Device { get; set; } = new DeviceDetails();
-
- private IDeviceModel DeviceModel { get; set; } = new DeviceModelDto();
-
- private bool isLoaded = false;
-
- private bool isProcessing;
-
- private DeviceSaveAction deviceSaveAction = DeviceSaveAction.Save;
- private string saveButtonText = "Save";
-
private void Return() => NavManager.NavigateTo("devices");
-
- private IEnumerable Commands { get; set; } = Array.Empty();
-
- private IEnumerable TagList { get; set; } = Array.Empty();
-
- private IEnumerable Properties = Array.Empty();
-
- protected override async Task OnInitializedAsync()
- {
- try
- {
- isProcessing = true;
-
- if (IsLoRa)
- {
- Device = await LoRaWanDeviceClientService.GetDevice(DeviceID);
- Commands = await LoRaWanDeviceModelsClientService.GetDeviceModelCommands(Device.ModelId);
- DeviceModel = await LoRaWanDeviceModelsClientService.GetDeviceModel(Device.ModelId);
- }
- else
- {
- Device = await DeviceClientService.GetDevice(DeviceID);
- Properties = await DeviceClientService.GetDeviceProperties(DeviceID);
- DeviceModel = await DeviceModelsClientService.GetDeviceModel(Device.ModelId);
- }
-
- TagList = await DeviceTagSettingsClientService.GetDeviceTags();
-
- isLoaded = true;
- isProcessing = false;
- }
- catch (ProblemDetailsException exception)
- {
- Error?.ProcessProblemDetails(exception);
- }
- }
-
- private void ProcessActionOnDevice()
- {
- switch (deviceSaveAction)
- {
- case DeviceSaveAction.Save:
- Save();
- break;
- case DeviceSaveAction.Duplicate:
- DeviceLayoutService.DuplicateSharedDevice(Device);
- DeviceLayoutService.DuplicateSharedDeviceModel(DeviceModel);
- NavManager.NavigateTo("devices/new");
- break;
- }
- }
-
- ///
- /// Sends a POST request to the DevicesController, to add the new device to the Azure IoT Hub
- ///
- public async void Save()
- {
- try
- {
- isProcessing = true;
-
- await form.Validate();
-
- if (CheckTagsError() || CheckGeneralValidation() || CheckLoRaValidation())
- {
- Snackbar.Add("One or more validation errors occurred", Severity.Error);
-
- isProcessing = false;
-
- return;
- }
-
- if (IsLoRa)
- {
- await LoRaWanDeviceClientService.UpdateDevice((Device as LoRaDeviceDetails)!);
- }
- else
- {
- await DeviceClientService.UpdateDevice((Device as DeviceDetails)!);
-
- await DeviceClientService.SetDeviceProperties(DeviceID, Properties.ToList());
- }
-
- // Prompts a snack bar to inform the action was successful
- Snackbar.Add($"Device {Device.DeviceName} has been successfully updated!\r\nPlease note that changes might take some minutes to be visible in the list...", Severity.Success, null);
-
- NavManager.NavigateTo("devices");
- }
- catch (ProblemDetailsException exception)
- {
- Error?.ProcessProblemDetails(exception);
- }
- finally
- {
- isProcessing = false;
- StateHasChanged();
- }
- }
-
- public async Task ShowConnectionString()
- {
- var parameters = new DialogParameters();
- parameters.Add(nameof(ConnectionStringDialog.deviceId), this.DeviceID);
-
- _ = await DialogService.Show("Device Credentials", parameters).Result;
- }
-
- private bool CheckTagsError()
- {
- bool tagValidationError = false;
-
- foreach (DeviceTagDto tag in TagList)
- {
- if (tag.Required && string.IsNullOrEmpty(Device.Tags[tag.Name]))
- {
- tagValidationError = true;
- }
- }
- return tagValidationError;
- }
-
- private bool CheckLoRaValidation()
- {
- if(!IsLoRa)
- {
- return false;
- }
-
- if (this.Device is LoRaDeviceDetails loRaDeviceDetails)
- {
- return !this.loraValidator.Validate(loRaDeviceDetails).IsValid;
- }
-
- return true;
- }
-
- private bool CheckGeneralValidation()
- {
- if (!IsLoRa && this.Device is DeviceDetails deviceDetails)
- {
- return !this.standardValidator.Validate(deviceDetails).IsValid;
- }
-
- return CheckLoRaValidation();
- }
-
- ///
- /// Prompts a pop-up windows to confirm the device's deletion.
- ///
- /// Device to delete from the hub
- ///
- private async Task DeleteDevice()
- {
- isProcessing = true;
-
- var parameters = new DialogParameters
- {
- {"deviceID", Device.DeviceID},
- {"deviceName", Device.DeviceName},
- {"IsLoRaWan", IsLoRa}
- };
- var result = await DialogService.Show("Confirm Deletion", parameters).Result;
-
- isProcessing = false;
-
- if (result.Canceled)
- {
- return;
- }
-
- // Go back to the list of devices
- NavManager.NavigateTo("devices");
- }
-
- private void SetSaveButtonText(DeviceSaveAction saveAction)
- {
- deviceSaveAction = saveAction;
- saveButtonText = deviceSaveAction switch
- {
- DeviceSaveAction.Save => "Save",
- DeviceSaveAction.Duplicate => "Duplicate",
- _ => saveButtonText
- };
- }
}
diff --git a/src/AzureIoTHub.Portal.Client/Validators/LoRaDeviceDetailsValidator.cs b/src/AzureIoTHub.Portal.Client/Validators/LoRaDeviceDetailsValidator.cs
index d67d6ecd0..5c5361728 100644
--- a/src/AzureIoTHub.Portal.Client/Validators/LoRaDeviceDetailsValidator.cs
+++ b/src/AzureIoTHub.Portal.Client/Validators/LoRaDeviceDetailsValidator.cs
@@ -46,6 +46,12 @@ public LoRaDeviceDetailsValidator()
.NotEmpty()
.When(x => !x.UseOTAA)
.WithMessage("DevAddr is required.");
+
+ _ = RuleFor(x => x.DeviceID)
+ .NotEmpty()
+ .Length(1, 16)
+ .Matches("[A-F0-9]{16}")
+ .WithMessage("DeviceID is required. It should be a 16 bit hex string.");
}
public Func