Skip to content

Commit d6f81cc

Browse files
kbeaugrandE068097judramos
committed
Scheduler and plannings management (#3255)
* 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 <9513635+kbeaugrand@users.noreply.github.com> * #2958 Remove 'Connection State' and 'Last status update' columns * #3023 startupOrder not supported in Edge Device Model schema --------- Co-authored-by: E068097 <julie.ramos_ext@michelin.com> Co-authored-by: judramos <ramos.julie.63@gmail.com>
1 parent 32be2da commit d6f81cc

File tree

78 files changed

+4397
-475
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

78 files changed

+4397
-475
lines changed

src/IoTHub.Portal.Application/Helpers/ConfigHelper.cs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ public static IoTEdgeModule CreateGatewayModule(Configuration config, JProperty
142142
ModuleName = module.Name,
143143
Image = module.Value["settings"]?["image"]?.Value<string>(),
144144
ContainerCreateOptions = module.Value["settings"]?["createOptions"]?.Value<string>(),
145+
StartupOrder = module.Value["settings"]?["startupOrder"]?.Value<int>() ?? 0,
145146
Status = module.Value["status"]?.Value<string>(),
146147
};
147148

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

233+
if (edgeModel.SystemModules.Single(x => x.Name == "edgeAgent").StartupOrder > 0)
234+
{
235+
edgeAgentPropertiesDesired.SystemModules.EdgeAgent.Settings.StartupOrder = edgeModel.SystemModules.Single(x => x.Name == "edgeAgent").StartupOrder;
236+
}
237+
232238
if (!string.IsNullOrEmpty(edgeModel.SystemModules.Single(x => x.Name == "edgeHub").Image))
233239
{
234240
edgeAgentPropertiesDesired.SystemModules.EdgeHub.Settings.Image = edgeModel.SystemModules.Single(x => x.Name == "edgeHub").Image;
@@ -241,6 +247,11 @@ public static Dictionary<string, IDictionary<string, object>> GenerateModulesCon
241247
edgeAgentPropertiesDesired.SystemModules.EdgeHub.Settings.CreateOptions = edgeModel.SystemModules.Single(x => x.Name == "edgeHub").ContainerCreateOptions;
242248
}
243249

250+
if (edgeModel.SystemModules.Single(x => x.Name == "edgeHub").StartupOrder > 0)
251+
{
252+
edgeAgentPropertiesDesired.SystemModules.EdgeHub.Settings.StartupOrder = edgeModel.SystemModules.Single(x => x.Name == "edgeHub").StartupOrder;
253+
}
254+
244255
foreach (var item in edgeModel.SystemModules.Single(x => x.Name == "edgeAgent").EnvironmentVariables)
245256
{
246257
edgeAgentPropertiesDesired.SystemModules.EdgeAgent.EnvironmentVariables?.Add(item.Name, new EnvironmentVariable() { EnvValue = item.Value });
@@ -262,7 +273,8 @@ public static Dictionary<string, IDictionary<string, object>> GenerateModulesCon
262273
Settings = new ModuleSettings()
263274
{
264275
Image = module.Image,
265-
CreateOptions = module.ContainerCreateOptions
276+
CreateOptions = module.ContainerCreateOptions,
277+
StartupOrder = module.StartupOrder,
266278
},
267279
RestartPolicy = "always",
268280
EnvironmentVariables = new Dictionary<string, EnvironmentVariable>()

src/IoTHub.Portal.Application/Mappers/DeviceProfile.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ public DeviceProfile()
1515
.ForMember(dest => dest.Id, opts => opts.MapFrom(src => src.DeviceID))
1616
.ForMember(dest => dest.Name, opts => opts.MapFrom(src => src.DeviceName))
1717
.ForMember(dest => dest.DeviceModelId, opts => opts.MapFrom(src => src.ModelId))
18+
.ForMember(dest => dest.LayerId, opts => opts.MapFrom(src => src.LayerId))
1819
.ForMember(dest => dest.Tags, opts => opts.MapFrom(src => src.Tags.Select(pair => new DeviceTagValue
1920
{
2021
Name = pair.Key,
@@ -25,12 +26,14 @@ public DeviceProfile()
2526
.ForMember(dest => dest.DeviceID, opts => opts.MapFrom(src => src.Id))
2627
.ForMember(dest => dest.DeviceName, opts => opts.MapFrom(src => src.Name))
2728
.ForMember(dest => dest.ModelId, opts => opts.MapFrom(src => src.DeviceModelId))
29+
.ForMember(dest => dest.LayerId, opts => opts.MapFrom(src => src.LayerId))
2830
.ForMember(dest => dest.Tags, opts => opts.MapFrom(src => src.Tags.ToDictionary(tag => tag.Name, tag => tag.Value)));
2931

3032
_ = CreateMap<Twin, Device>()
3133
.ForMember(dest => dest.Id, opts => opts.MapFrom(src => src.DeviceId))
3234
.ForMember(dest => dest.Name, opts => opts.MapFrom(src => src.Tags["deviceName"]))
3335
.ForMember(dest => dest.DeviceModelId, opts => opts.MapFrom(src => src.Tags["modelId"]))
36+
.ForMember(dest => dest.LayerId, opts => opts.MapFrom(src => src.Tags.Contains("layerId") ? src.Tags["layerId"] : null))
3437
.ForMember(dest => dest.Version, opts => opts.MapFrom(src => src.Version))
3538
.ForMember(dest => dest.IsConnected, opts => opts.MapFrom(src => src.ConnectionState == Microsoft.Azure.Devices.DeviceConnectionState.Connected))
3639
.ForMember(dest => dest.IsEnabled, opts => opts.MapFrom(src => src.Status == Microsoft.Azure.Devices.DeviceStatus.Enabled))
@@ -42,6 +45,7 @@ public DeviceProfile()
4245
.ForMember(dest => dest.Id, opts => opts.MapFrom(src => src.DeviceID))
4346
.ForMember(dest => dest.Name, opts => opts.MapFrom(src => src.DeviceName))
4447
.ForMember(dest => dest.DeviceModelId, opts => opts.MapFrom(src => src.ModelId))
48+
.ForMember(dest => dest.LayerId, opts => opts.MapFrom(src => src.LayerId))
4549
.ForMember(dest => dest.Tags, opts => opts.MapFrom(src => src.Tags.Select(pair => new DeviceTagValue
4650
{
4751
Name = pair.Key,
@@ -52,12 +56,14 @@ public DeviceProfile()
5256
.ForMember(dest => dest.DeviceID, opts => opts.MapFrom(src => src.Id))
5357
.ForMember(dest => dest.DeviceName, opts => opts.MapFrom(src => src.Name))
5458
.ForMember(dest => dest.ModelId, opts => opts.MapFrom(src => src.DeviceModelId))
59+
.ForMember(dest => dest.LayerId, opts => opts.MapFrom(src => src.LayerId))
5560
.ForMember(dest => dest.Tags, opts => opts.MapFrom(src => src.Tags.ToDictionary(tag => tag.Name, tag => tag.Value)));
5661

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

src/IoTHub.Portal.Application/Services/ISendPlanningCommandService.cs

Lines changed: 0 additions & 9 deletions
This file was deleted.

src/IoTHub.Portal.Client/Components/Concentrators/ConcentratorSearch.razor

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -20,20 +20,6 @@
2020
</MudRadioGroup>
2121

2222
</MudItem>
23-
<MudItem xs="12" md="6">
24-
<MudText>Connection state</MudText>
25-
<MudRadioGroup @bind-SelectedOption="@searchState" Style="display:flex;align-items:baseline">
26-
<MudItem md="4" sm="12">
27-
<MudRadio Option=@("true") Color="Color.Primary" id="searchStateConnected">Connected</MudRadio>
28-
</MudItem>
29-
<MudItem md="4" sm="12">
30-
<MudRadio Option=@("false") Color="Color.Primary" id="searchStateDisconnected">Disconnected</MudRadio>
31-
</MudItem>
32-
<MudItem md="4" sm="12">
33-
<MudRadio Option=@("") Color="Color.Secondary" id="searchStateAll">All</MudRadio>
34-
</MudItem>
35-
</MudRadioGroup>
36-
</MudItem>
3723
</MudGrid>
3824

3925
<MudItem xs="12">

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

Lines changed: 53 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,4 @@
1-
@using IoTHub.Portal.Models
2-
@using IoTHub.Portal.Models.v10
3-
@using IoTHub.Portal.Shared.Models.v10
4-
@using IoTHub.Portal.Client.Validators
5-
@using System.Net.Http.Headers
6-
@using IoTHub.Portal.Shared.Constants
7-
@using IoTHub.Portal.Client.Models
8-
@using IoTHub.Portal.Shared.Models
9-
@using IoTHub.Portal.Shared.Models.v10.Filters
10-
@using IoTHub.Portal.Models.v10.LoRaWAN
11-
@using IoTHub.Portal.Client.Helpers
12-
13-
@attribute [Authorize]
1+
@attribute [Authorize]
142
@inject NavigationManager NavigationManager
153
@inject PortalSettings Portal
164

@@ -25,6 +13,10 @@
2513
<MudText Typo="Typo.h5" Color="Color.Primary" Class="mb-4">
2614
@mode Planning
2715
<MudButton Variant="Variant.Filled" Class="mx-1" Color="Color.Primary" OnClick="Save" id="saveButton" Disabled="isProcessing">Save</MudButton>
16+
@if (mode == "Edit")
17+
{
18+
<MudButton Variant="Variant.Filled" Class="mx-1" Color="Color.Error" OnClick="DeletePlanning" id="deleteButton" Disabled="isProcessing">Delete planning</MudButton>
19+
}
2820
</MudText>
2921
@if (!isProcessing)
3022
{
@@ -85,7 +77,7 @@
8577
</MudSelect>
8678
</MudTd>
8779
<MudTd DataLabel="Delete" Style="text-align: center">
88-
<MudIconButton Color="Color.Default" Class="deleteRouteButton" OnClick="( () => DeleteSchedule(ContextSchedule))" Icon="@Icons.Material.Filled.Delete" Size="Size.Medium"></MudIconButton>
80+
<MudIconButton Color="Color.Default" Class="deleteRouteButton" id="deleteScheduleButton" OnClick="( () => DeleteSchedule(ContextSchedule))" Icon="@Icons.Material.Filled.Delete" Size="Size.Medium"></MudIconButton>
8981
</MudTd>
9082
</RowTemplate>
9183
<FooterContent>
@@ -184,10 +176,29 @@
184176
<MudPaper Class="overflow-y-auto" Elevation="0">
185177
<MudTreeView Items="@Layers">
186178
<ItemTemplate>
187-
<MudTreeViewItem @bind-Expanded="@context.IsExpanded" Items="@context.Children">
179+
<MudTreeViewItem id="selectLayer" @bind-Expanded="@context.IsExpanded" Items="@context.Children">
188180
<Content>
189181
<MudTreeViewItemToggleButton @bind-Expanded="@context.IsExpanded" Visible="@(context.Children.Count() != 0)" />
190-
<MudCheckBox T="bool?" Checked="@(context.LayerData.Planning == planning.Id)" ValueChanged="@(() => CheckedChanged(context))"></MudCheckBox>
182+
183+
@if ((context.LayerData.Planning != null && context.LayerData.Planning != "None" && context.LayerData.Planning == planning.Id))
184+
{
185+
<MudTooltip Text="Already registered">
186+
<MudIconButton Color="Color.Success" Icon="@Icons.Material.Filled.CheckBox" Size="Size.Medium" @onclick="() => CheckedChanged(context)"></MudIconButton>
187+
</MudTooltip>
188+
}
189+
else if (context.LayerData.Planning != null && context.LayerData.Planning != "None" && context.LayerData.Planning != planning.Id)
190+
{
191+
<MudTooltip Text="Registered on other planning">
192+
<MudIconButton Color="Color.Error" Icon="@Icons.Material.Filled.IndeterminateCheckBox" Size="Size.Medium" @onclick="() => CheckedChanged(context)"></MudIconButton>
193+
</MudTooltip>
194+
}
195+
else
196+
{
197+
<MudTooltip Text="Add layer">
198+
<MudIconButton Color="Color.Default" Icon="@Icons.Material.Filled.CheckBoxOutlineBlank" Size="Size.Medium" @onclick="() => CheckedChanged(context)"></MudIconButton>
199+
</MudTooltip>
200+
}
201+
191202
<MudText>@context.LayerData.Name</MudText>
192203
</Content>
193204
</MudTreeViewItem>
@@ -418,4 +429,30 @@
418429
return "";
419430
}
420431

432+
/// <summary>
433+
/// Prompts a pop-up windows to confirm the planning's deletion.
434+
/// </summary>
435+
/// <returns></returns>
436+
private async Task DeletePlanning()
437+
{
438+
isProcessing = true;
439+
440+
var parameters = new DialogParameters
441+
{
442+
{"planningID", planning.Id},
443+
{"planningName", planning.Name}
444+
};
445+
var result = await DialogService.Show<DeletePlanningDialog>("Confirm Deletion", parameters).Result;
446+
447+
isProcessing = false;
448+
449+
if (result.Canceled)
450+
{
451+
return;
452+
}
453+
454+
// Go back to the list of plannings
455+
NavigationManager.NavigateTo($"/planning");
456+
}
457+
421458
}

src/IoTHub.Portal.Client/Dialogs/EdgeModels/EdgeModule/ModuleDialog.razor

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,22 @@
44
<DialogContent>
55
<MudContainer Style="max-height: 600px; overflow-y: scroll">
66
<MudGrid>
7-
<MudItem xs="12" md="6">
7+
<MudItem xs="12" md="4">
88
<MudTextField @bind-Value="@currentModuleName"
99
id=@nameof(IoTEdgeModule.ModuleName)
1010
Label="Module name"
1111
Variant="Variant.Outlined"
1212
For="@(() => Module.ModuleName)"
1313
Required="true"/>
1414
</MudItem>
15+
<MudItem xs="12" md="2">
16+
<MudTextField @bind-Value="@currentStartupOrder"
17+
id=@nameof(IoTEdgeModule.StartupOrder)
18+
Label="Startup Order"
19+
Variant="Variant.Outlined"
20+
For="@(() => Module.StartupOrder)"
21+
Required="false" />
22+
</MudItem>
1523
<MudItem xs="12" md="6">
1624
<MudTextField @bind-Value="@currentImage"
1725
id=@nameof(IoTEdgeModule.Image)
@@ -87,6 +95,7 @@
8795
private string currentImage = default!;
8896
private string currentContainerCreateOptions = default!;
8997
private string currentNumVersion = "1.0.0";
98+
private int currentStartupOrder;
9099

91100
private List<IoTEdgeModuleEnvironmentVariable> currentEnvironmentVariables = new();
92101
private List<IoTEdgeModuleTwinSetting> currentModuleIdentityTwinSettings = new();
@@ -103,6 +112,7 @@
103112
currentImage = Module.Image;
104113
currentContainerCreateOptions = Module.ContainerCreateOptions;
105114
currentNumVersion = Module.Version;
115+
currentStartupOrder = Module.StartupOrder;
106116
currentEnvironmentVariables = new List<IoTEdgeModuleEnvironmentVariable>(Module.EnvironmentVariables.ToArray());
107117
currentModuleIdentityTwinSettings = new List<IoTEdgeModuleTwinSetting>(Module.ModuleIdentityTwinSettings.ToArray());
108118
currentCommands = new List<IoTEdgeModuleCommand>(Module.Commands.ToArray());
@@ -115,6 +125,7 @@
115125
Module.ModuleName = currentModuleName;
116126
Module.Image = currentImage;
117127
Module.ContainerCreateOptions = currentContainerCreateOptions;
128+
Module.StartupOrder = currentStartupOrder;
118129

119130
if (Portal.CloudProvider.Equals(CloudProviders.Azure)) { Module.Version = " "; }
120131
else { Module.Version = currentNumVersion; }

src/IoTHub.Portal.Client/Dialogs/EdgeModels/EdgeModule/SystemModuleDialog.razor

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,22 @@
33
<DialogContent>
44
<MudContainer Style="max-height: 600px; overflow-y: scroll">
55
<MudGrid>
6-
<MudItem xs="12" md="6">
6+
<MudItem xs="12" md="4">
77
<MudTextField @bind-Value="@currentModuleName"
88
id=@nameof(EdgeModelSystemModule.Name)
99
Label="Module name"
1010
Variant="Variant.Outlined"
1111
For="@(()=> Module.Name)"
1212
Required="true" Disabled/>
1313
</MudItem>
14+
<MudItem xs="12" md="2">
15+
<MudTextField @bind-Value="@currentStartupOrder"
16+
id=@nameof(EdgeModelSystemModule.StartupOrder)
17+
Label="Startup Order"
18+
Variant="Variant.Outlined"
19+
For="@(() => Module.StartupOrder)"
20+
Required="false" />
21+
</MudItem>
1422
<MudItem xs="12" md="6">
1523
<MudTextField @bind-Value="@currentImage"
1624
id=@nameof(EdgeModelSystemModule.Image)
@@ -45,7 +53,7 @@
4553
</DialogActions>
4654
</MudDialog>
4755

48-
@code {
56+
@code {
4957
[CascadingParameter]
5058
MudDialogInstance MudDialog { get; set; } = default!;
5159

@@ -55,6 +63,7 @@
5563
private string currentModuleName = default!;
5664
private string currentImage = default!;
5765
private string currentContainerCreateOptions = default!;
66+
private int currentStartupOrder;
5867

5968
private List<IoTEdgeModuleEnvironmentVariable> currentEnvironmentVariables = new();
6069

@@ -67,6 +76,7 @@
6776
currentModuleName = Module.Name;
6877
currentImage = Module.Image;
6978
currentContainerCreateOptions = Module.ContainerCreateOptions;
79+
currentStartupOrder = Module.StartupOrder;
7080
currentEnvironmentVariables = new List<IoTEdgeModuleEnvironmentVariable>(Module.EnvironmentVariables.ToArray());
7181
await Task.Delay(0);
7282
IsLoading = false;
@@ -77,6 +87,7 @@
7787
Module.Name = currentModuleName;
7888
Module.Image = currentImage;
7989
Module.ContainerCreateOptions = currentContainerCreateOptions;
90+
Module.StartupOrder = currentStartupOrder;
8091
Module.EnvironmentVariables = new List<IoTEdgeModuleEnvironmentVariable>(currentEnvironmentVariables.ToArray());
8192
MudDialog.Close(DialogResult.Ok(true));
8293
}

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,12 @@
170170
if (device.LayerId != null && device.LayerId == InitLayer.Id)
171171
{
172172
if (DeviceRemoveList.Contains(device.DeviceID)) DeviceRemoveList.Remove(device.DeviceID);
173-
else DeviceRemoveList.Add(device.DeviceID);
173+
else
174+
{
175+
DeviceList.Remove(device.DeviceID);
176+
DeviceRemoveList.Add(device.DeviceID);
177+
device.LayerId = null;
178+
}
174179
}
175180
else
176181
{
@@ -223,6 +228,7 @@
223228
deviceDetails.IsConnected = device.IsConnected;
224229
deviceDetails.IsEnabled = device.IsEnabled;
225230
deviceDetails.StatusUpdatedTime = device.StatusUpdatedTime;
231+
deviceDetails.LastActivityTime = device.LastActivityTime;
226232
deviceDetails.Labels = device.Labels.ToList();
227233
deviceDetails.LayerId = device.LayerId;
228234

0 commit comments

Comments
 (0)