Skip to content

Commit

Permalink
Scheduler and plannings management (#3255)
Browse files Browse the repository at this point in the history
* Add of layerId in device twin

* #2998 Quartz migration for SendPlanningCommand

* #2856 Disable built-in device model deletion

* #3238 Update view when a device is unchecked

* 3239 Allow to delete a planning from client

* 3239 Allow to delete a planning

* 2998 Schedule commands

* #3239 Change checkboxes for layers displayed

* Merge from main

* 2516 add supportLoRaFeatures tag in template file

* #3250 Import device list using the template given

* #2985 Batch import creates ABP tags in Device Twin for OTAA-based device models

* #3251 Import device - data overwritten

* Unit tests

* Update src/IoTHub.Portal.Infrastructure/Jobs/SendPlanningCommandJob.cs

Co-authored-by: Kevin BEAUGRAND <[email protected]>

* #2958 Remove 'Connection State' and 'Last status update' columns

* #3023 startupOrder not supported in Edge Device Model schema

---------

Co-authored-by: E068097 <[email protected]>
Co-authored-by: judramos <[email protected]>
  • Loading branch information
3 people committed Dec 5, 2024
1 parent 32be2da commit d6f81cc
Show file tree
Hide file tree
Showing 78 changed files with 4,397 additions and 475 deletions.
14 changes: 13 additions & 1 deletion src/IoTHub.Portal.Application/Helpers/ConfigHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ public static IoTEdgeModule CreateGatewayModule(Configuration config, JProperty
ModuleName = module.Name,
Image = module.Value["settings"]?["image"]?.Value<string>(),
ContainerCreateOptions = module.Value["settings"]?["createOptions"]?.Value<string>(),
StartupOrder = module.Value["settings"]?["startupOrder"]?.Value<int>() ?? 0,
Status = module.Value["status"]?.Value<string>(),
};

Expand Down Expand Up @@ -229,6 +230,11 @@ public static Dictionary<string, IDictionary<string, object>> GenerateModulesCon
edgeAgentPropertiesDesired.SystemModules.EdgeAgent.Settings.CreateOptions = edgeModel.SystemModules.Single(x => x.Name == "edgeAgent").ContainerCreateOptions;
}

if (edgeModel.SystemModules.Single(x => x.Name == "edgeAgent").StartupOrder > 0)
{
edgeAgentPropertiesDesired.SystemModules.EdgeAgent.Settings.StartupOrder = edgeModel.SystemModules.Single(x => x.Name == "edgeAgent").StartupOrder;
}

if (!string.IsNullOrEmpty(edgeModel.SystemModules.Single(x => x.Name == "edgeHub").Image))
{
edgeAgentPropertiesDesired.SystemModules.EdgeHub.Settings.Image = edgeModel.SystemModules.Single(x => x.Name == "edgeHub").Image;
Expand All @@ -241,6 +247,11 @@ public static Dictionary<string, IDictionary<string, object>> GenerateModulesCon
edgeAgentPropertiesDesired.SystemModules.EdgeHub.Settings.CreateOptions = edgeModel.SystemModules.Single(x => x.Name == "edgeHub").ContainerCreateOptions;
}

if (edgeModel.SystemModules.Single(x => x.Name == "edgeHub").StartupOrder > 0)
{
edgeAgentPropertiesDesired.SystemModules.EdgeHub.Settings.StartupOrder = edgeModel.SystemModules.Single(x => x.Name == "edgeHub").StartupOrder;
}

Check warning on line 253 in src/IoTHub.Portal.Application/Helpers/ConfigHelper.cs

View check run for this annotation

Codecov / codecov/patch

src/IoTHub.Portal.Application/Helpers/ConfigHelper.cs#L251-L253

Added lines #L251 - L253 were not covered by tests

foreach (var item in edgeModel.SystemModules.Single(x => x.Name == "edgeAgent").EnvironmentVariables)
{
edgeAgentPropertiesDesired.SystemModules.EdgeAgent.EnvironmentVariables?.Add(item.Name, new EnvironmentVariable() { EnvValue = item.Value });
Expand All @@ -262,7 +273,8 @@ public static Dictionary<string, IDictionary<string, object>> GenerateModulesCon
Settings = new ModuleSettings()
{
Image = module.Image,
CreateOptions = module.ContainerCreateOptions
CreateOptions = module.ContainerCreateOptions,
StartupOrder = module.StartupOrder,
},
RestartPolicy = "always",
EnvironmentVariables = new Dictionary<string, EnvironmentVariable>()
Expand Down
6 changes: 6 additions & 0 deletions src/IoTHub.Portal.Application/Mappers/DeviceProfile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ public DeviceProfile()
.ForMember(dest => dest.Id, opts => opts.MapFrom(src => src.DeviceID))
.ForMember(dest => dest.Name, opts => opts.MapFrom(src => src.DeviceName))
.ForMember(dest => dest.DeviceModelId, opts => opts.MapFrom(src => src.ModelId))
.ForMember(dest => dest.LayerId, opts => opts.MapFrom(src => src.LayerId))
.ForMember(dest => dest.Tags, opts => opts.MapFrom(src => src.Tags.Select(pair => new DeviceTagValue
{
Name = pair.Key,
Expand All @@ -25,12 +26,14 @@ public DeviceProfile()
.ForMember(dest => dest.DeviceID, opts => opts.MapFrom(src => src.Id))
.ForMember(dest => dest.DeviceName, opts => opts.MapFrom(src => src.Name))
.ForMember(dest => dest.ModelId, opts => opts.MapFrom(src => src.DeviceModelId))
.ForMember(dest => dest.LayerId, opts => opts.MapFrom(src => src.LayerId))
.ForMember(dest => dest.Tags, opts => opts.MapFrom(src => src.Tags.ToDictionary(tag => tag.Name, tag => tag.Value)));

_ = CreateMap<Twin, Device>()
.ForMember(dest => dest.Id, opts => opts.MapFrom(src => src.DeviceId))
.ForMember(dest => dest.Name, opts => opts.MapFrom(src => src.Tags["deviceName"]))
.ForMember(dest => dest.DeviceModelId, opts => opts.MapFrom(src => src.Tags["modelId"]))
.ForMember(dest => dest.LayerId, opts => opts.MapFrom(src => src.Tags.Contains("layerId") ? src.Tags["layerId"] : null))
.ForMember(dest => dest.Version, opts => opts.MapFrom(src => src.Version))
.ForMember(dest => dest.IsConnected, opts => opts.MapFrom(src => src.ConnectionState == Microsoft.Azure.Devices.DeviceConnectionState.Connected))
.ForMember(dest => dest.IsEnabled, opts => opts.MapFrom(src => src.Status == Microsoft.Azure.Devices.DeviceStatus.Enabled))
Expand All @@ -42,6 +45,7 @@ public DeviceProfile()
.ForMember(dest => dest.Id, opts => opts.MapFrom(src => src.DeviceID))
.ForMember(dest => dest.Name, opts => opts.MapFrom(src => src.DeviceName))
.ForMember(dest => dest.DeviceModelId, opts => opts.MapFrom(src => src.ModelId))
.ForMember(dest => dest.LayerId, opts => opts.MapFrom(src => src.LayerId))
.ForMember(dest => dest.Tags, opts => opts.MapFrom(src => src.Tags.Select(pair => new DeviceTagValue
{
Name = pair.Key,
Expand All @@ -52,12 +56,14 @@ public DeviceProfile()
.ForMember(dest => dest.DeviceID, opts => opts.MapFrom(src => src.Id))
.ForMember(dest => dest.DeviceName, opts => opts.MapFrom(src => src.Name))
.ForMember(dest => dest.ModelId, opts => opts.MapFrom(src => src.DeviceModelId))
.ForMember(dest => dest.LayerId, opts => opts.MapFrom(src => src.LayerId))
.ForMember(dest => dest.Tags, opts => opts.MapFrom(src => src.Tags.ToDictionary(tag => tag.Name, tag => tag.Value)));

_ = CreateMap<Twin, LorawanDevice>()
.ForMember(dest => dest.Id, opts => opts.MapFrom(src => src.DeviceId))
.ForMember(dest => dest.Name, opts => opts.MapFrom(src => src.Tags["deviceName"]))
.ForMember(dest => dest.DeviceModelId, opts => opts.MapFrom(src => src.Tags["modelId"]))
.ForMember(dest => dest.LayerId, opts => opts.MapFrom(src => src.Tags.Contains("layerId") ? src.Tags["layerId"] : null))
.ForMember(dest => dest.Version, opts => opts.MapFrom(src => src.Version))
.ForMember(dest => dest.IsConnected, opts => opts.MapFrom(src => src.ConnectionState == Microsoft.Azure.Devices.DeviceConnectionState.Connected))
.ForMember(dest => dest.IsEnabled, opts => opts.MapFrom(src => src.Status == Microsoft.Azure.Devices.DeviceStatus.Enabled))
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,6 @@
</MudRadioGroup>

</MudItem>
<MudItem xs="12" md="6">
<MudText>Connection state</MudText>
<MudRadioGroup @bind-SelectedOption="@searchState" Style="display:flex;align-items:baseline">
<MudItem md="4" sm="12">
<MudRadio Option=@("true") Color="Color.Primary" id="searchStateConnected">Connected</MudRadio>
</MudItem>
<MudItem md="4" sm="12">
<MudRadio Option=@("false") Color="Color.Primary" id="searchStateDisconnected">Disconnected</MudRadio>
</MudItem>
<MudItem md="4" sm="12">
<MudRadio Option=@("") Color="Color.Secondary" id="searchStateAll">All</MudRadio>
</MudItem>
</MudRadioGroup>
</MudItem>
</MudGrid>

<MudItem xs="12">
Expand Down
69 changes: 53 additions & 16 deletions src/IoTHub.Portal.Client/Components/Planning/EditPlanning.razor
Original file line number Diff line number Diff line change
@@ -1,16 +1,4 @@
@using IoTHub.Portal.Models
@using IoTHub.Portal.Models.v10
@using IoTHub.Portal.Shared.Models.v10
@using IoTHub.Portal.Client.Validators
@using System.Net.Http.Headers
@using IoTHub.Portal.Shared.Constants
@using IoTHub.Portal.Client.Models
@using IoTHub.Portal.Shared.Models
@using IoTHub.Portal.Shared.Models.v10.Filters
@using IoTHub.Portal.Models.v10.LoRaWAN
@using IoTHub.Portal.Client.Helpers

@attribute [Authorize]
@attribute [Authorize]
@inject NavigationManager NavigationManager
@inject PortalSettings Portal

Expand All @@ -25,6 +13,10 @@
<MudText Typo="Typo.h5" Color="Color.Primary" Class="mb-4">
@mode Planning
<MudButton Variant="Variant.Filled" Class="mx-1" Color="Color.Primary" OnClick="Save" id="saveButton" Disabled="isProcessing">Save</MudButton>
@if (mode == "Edit")
{
<MudButton Variant="Variant.Filled" Class="mx-1" Color="Color.Error" OnClick="DeletePlanning" id="deleteButton" Disabled="isProcessing">Delete planning</MudButton>
}
</MudText>
@if (!isProcessing)
{
Expand Down Expand Up @@ -85,7 +77,7 @@
</MudSelect>
</MudTd>
<MudTd DataLabel="Delete" Style="text-align: center">
<MudIconButton Color="Color.Default" Class="deleteRouteButton" OnClick="( () => DeleteSchedule(ContextSchedule))" Icon="@Icons.Material.Filled.Delete" Size="Size.Medium"></MudIconButton>
<MudIconButton Color="Color.Default" Class="deleteRouteButton" id="deleteScheduleButton" OnClick="( () => DeleteSchedule(ContextSchedule))" Icon="@Icons.Material.Filled.Delete" Size="Size.Medium"></MudIconButton>
</MudTd>
</RowTemplate>
<FooterContent>
Expand Down Expand Up @@ -184,10 +176,29 @@
<MudPaper Class="overflow-y-auto" Elevation="0">
<MudTreeView Items="@Layers">
<ItemTemplate>
<MudTreeViewItem @bind-Expanded="@context.IsExpanded" Items="@context.Children">
<MudTreeViewItem id="selectLayer" @bind-Expanded="@context.IsExpanded" Items="@context.Children">
<Content>
<MudTreeViewItemToggleButton @bind-Expanded="@context.IsExpanded" Visible="@(context.Children.Count() != 0)" />
<MudCheckBox T="bool?" Checked="@(context.LayerData.Planning == planning.Id)" ValueChanged="@(() => CheckedChanged(context))"></MudCheckBox>

@if ((context.LayerData.Planning != null && context.LayerData.Planning != "None" && context.LayerData.Planning == planning.Id))
{
<MudTooltip Text="Already registered">
<MudIconButton Color="Color.Success" Icon="@Icons.Material.Filled.CheckBox" Size="Size.Medium" @onclick="() => CheckedChanged(context)"></MudIconButton>

Check warning on line 186 in src/IoTHub.Portal.Client/Components/Planning/EditPlanning.razor

View check run for this annotation

Codecov / codecov/patch

src/IoTHub.Portal.Client/Components/Planning/EditPlanning.razor#L186

Added line #L186 was not covered by tests
</MudTooltip>
}
else if (context.LayerData.Planning != null && context.LayerData.Planning != "None" && context.LayerData.Planning != planning.Id)
{
<MudTooltip Text="Registered on other planning">
<MudIconButton Color="Color.Error" Icon="@Icons.Material.Filled.IndeterminateCheckBox" Size="Size.Medium" @onclick="() => CheckedChanged(context)"></MudIconButton>

Check warning on line 192 in src/IoTHub.Portal.Client/Components/Planning/EditPlanning.razor

View check run for this annotation

Codecov / codecov/patch

src/IoTHub.Portal.Client/Components/Planning/EditPlanning.razor#L192

Added line #L192 was not covered by tests
</MudTooltip>
}
else
{
<MudTooltip Text="Add layer">
<MudIconButton Color="Color.Default" Icon="@Icons.Material.Filled.CheckBoxOutlineBlank" Size="Size.Medium" @onclick="() => CheckedChanged(context)"></MudIconButton>

Check warning on line 198 in src/IoTHub.Portal.Client/Components/Planning/EditPlanning.razor

View check run for this annotation

Codecov / codecov/patch

src/IoTHub.Portal.Client/Components/Planning/EditPlanning.razor#L198

Added line #L198 was not covered by tests
</MudTooltip>
}

<MudText>@context.LayerData.Name</MudText>
</Content>
</MudTreeViewItem>
Expand Down Expand Up @@ -418,4 +429,30 @@
return "";
}

/// <summary>
/// Prompts a pop-up windows to confirm the planning's deletion.
/// </summary>
/// <returns></returns>
private async Task DeletePlanning()
{
isProcessing = true;

var parameters = new DialogParameters
{
{"planningID", planning.Id},
{"planningName", planning.Name}
};
var result = await DialogService.Show<DeletePlanningDialog>("Confirm Deletion", parameters).Result;

isProcessing = false;

if (result.Canceled)
{
return;
}

// Go back to the list of plannings
NavigationManager.NavigateTo($"/planning");
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,22 @@
<DialogContent>
<MudContainer Style="max-height: 600px; overflow-y: scroll">
<MudGrid>
<MudItem xs="12" md="6">
<MudItem xs="12" md="4">
<MudTextField @bind-Value="@currentModuleName"
id=@nameof(IoTEdgeModule.ModuleName)
Label="Module name"
Variant="Variant.Outlined"
For="@(() => Module.ModuleName)"
Required="true"/>
</MudItem>
<MudItem xs="12" md="2">
<MudTextField @bind-Value="@currentStartupOrder"
id=@nameof(IoTEdgeModule.StartupOrder)
Label="Startup Order"
Variant="Variant.Outlined"
For="@(() => Module.StartupOrder)"
Required="false" />
</MudItem>
<MudItem xs="12" md="6">
<MudTextField @bind-Value="@currentImage"
id=@nameof(IoTEdgeModule.Image)
Expand Down Expand Up @@ -87,6 +95,7 @@
private string currentImage = default!;
private string currentContainerCreateOptions = default!;
private string currentNumVersion = "1.0.0";
private int currentStartupOrder;

private List<IoTEdgeModuleEnvironmentVariable> currentEnvironmentVariables = new();
private List<IoTEdgeModuleTwinSetting> currentModuleIdentityTwinSettings = new();
Expand All @@ -103,6 +112,7 @@
currentImage = Module.Image;
currentContainerCreateOptions = Module.ContainerCreateOptions;
currentNumVersion = Module.Version;
currentStartupOrder = Module.StartupOrder;
currentEnvironmentVariables = new List<IoTEdgeModuleEnvironmentVariable>(Module.EnvironmentVariables.ToArray());
currentModuleIdentityTwinSettings = new List<IoTEdgeModuleTwinSetting>(Module.ModuleIdentityTwinSettings.ToArray());
currentCommands = new List<IoTEdgeModuleCommand>(Module.Commands.ToArray());
Expand All @@ -115,6 +125,7 @@
Module.ModuleName = currentModuleName;
Module.Image = currentImage;
Module.ContainerCreateOptions = currentContainerCreateOptions;
Module.StartupOrder = currentStartupOrder;

if (Portal.CloudProvider.Equals(CloudProviders.Azure)) { Module.Version = " "; }
else { Module.Version = currentNumVersion; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,22 @@
<DialogContent>
<MudContainer Style="max-height: 600px; overflow-y: scroll">
<MudGrid>
<MudItem xs="12" md="6">
<MudItem xs="12" md="4">
<MudTextField @bind-Value="@currentModuleName"
id=@nameof(EdgeModelSystemModule.Name)
Label="Module name"
Variant="Variant.Outlined"
For="@(()=> Module.Name)"
Required="true" Disabled/>
</MudItem>
<MudItem xs="12" md="2">
<MudTextField @bind-Value="@currentStartupOrder"
id=@nameof(EdgeModelSystemModule.StartupOrder)
Label="Startup Order"
Variant="Variant.Outlined"
For="@(() => Module.StartupOrder)"
Required="false" />
</MudItem>
<MudItem xs="12" md="6">
<MudTextField @bind-Value="@currentImage"
id=@nameof(EdgeModelSystemModule.Image)
Expand Down Expand Up @@ -45,7 +53,7 @@
</DialogActions>
</MudDialog>

@code {
@code {
[CascadingParameter]
MudDialogInstance MudDialog { get; set; } = default!;

Expand All @@ -55,6 +63,7 @@
private string currentModuleName = default!;
private string currentImage = default!;
private string currentContainerCreateOptions = default!;
private int currentStartupOrder;

private List<IoTEdgeModuleEnvironmentVariable> currentEnvironmentVariables = new();

Expand All @@ -67,6 +76,7 @@
currentModuleName = Module.Name;
currentImage = Module.Image;
currentContainerCreateOptions = Module.ContainerCreateOptions;
currentStartupOrder = Module.StartupOrder;
currentEnvironmentVariables = new List<IoTEdgeModuleEnvironmentVariable>(Module.EnvironmentVariables.ToArray());
await Task.Delay(0);
IsLoading = false;
Expand All @@ -77,6 +87,7 @@
Module.Name = currentModuleName;
Module.Image = currentImage;
Module.ContainerCreateOptions = currentContainerCreateOptions;
Module.StartupOrder = currentStartupOrder;
Module.EnvironmentVariables = new List<IoTEdgeModuleEnvironmentVariable>(currentEnvironmentVariables.ToArray());
MudDialog.Close(DialogResult.Ok(true));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,12 @@
if (device.LayerId != null && device.LayerId == InitLayer.Id)
{
if (DeviceRemoveList.Contains(device.DeviceID)) DeviceRemoveList.Remove(device.DeviceID);
else DeviceRemoveList.Add(device.DeviceID);
else
{
DeviceList.Remove(device.DeviceID);
DeviceRemoveList.Add(device.DeviceID);
device.LayerId = null;
}

Check warning on line 178 in src/IoTHub.Portal.Client/Dialogs/Layer/LinkDeviceLayerDialog.razor

View check run for this annotation

Codecov / codecov/patch

src/IoTHub.Portal.Client/Dialogs/Layer/LinkDeviceLayerDialog.razor#L174-L178

Added lines #L174 - L178 were not covered by tests
}
else
{
Expand Down Expand Up @@ -223,6 +228,7 @@
deviceDetails.IsConnected = device.IsConnected;
deviceDetails.IsEnabled = device.IsEnabled;
deviceDetails.StatusUpdatedTime = device.StatusUpdatedTime;
deviceDetails.LastActivityTime = device.LastActivityTime;

Check warning on line 231 in src/IoTHub.Portal.Client/Dialogs/Layer/LinkDeviceLayerDialog.razor

View check run for this annotation

Codecov / codecov/patch

src/IoTHub.Portal.Client/Dialogs/Layer/LinkDeviceLayerDialog.razor#L231

Added line #L231 was not covered by tests
deviceDetails.Labels = device.Labels.ToList();
deviceDetails.LayerId = device.LayerId;

Expand Down
Loading

0 comments on commit d6f81cc

Please sign in to comment.