Skip to content

Commit 4d5a356

Browse files
author
Binon
committed
Merge branch 'Develop/Feature/TD-6601-search-admin' into Develop/Features/TD-6212-azure-ai-search-implementation
2 parents 47f7149 + 84b2bf6 commit 4d5a356

File tree

41 files changed

+882
-46
lines changed

Some content is hidden

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

41 files changed

+882
-46
lines changed
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
namespace LearningHub.Nhs.AdminUI.Configuration
2+
{
3+
/// <summary>
4+
/// Configuration settings for Azure AI Search.
5+
/// </summary>
6+
public class AzureSearchConfig
7+
{
8+
/// <summary>
9+
/// Gets or sets the Azure Search service endpoint URL.
10+
/// </summary>
11+
public string ServiceEndpoint { get; set; }
12+
13+
/// <summary>
14+
/// Gets or sets the admin API key for managing indexes and indexers.
15+
/// </summary>
16+
public string AdminApiKey { get; set; }
17+
18+
/// <summary>
19+
/// Gets or sets the query API key for search operations.
20+
/// </summary>
21+
public string QueryApiKey { get; set; }
22+
23+
/// <summary>
24+
/// Gets or sets the name of the search index.
25+
/// </summary>
26+
public string IndexName { get; set; }
27+
28+
/// <summary>
29+
/// Gets or sets the name of the indexer.
30+
/// </summary>
31+
public string IndexerName { get; set; }
32+
}
33+
}
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
namespace LearningHub.Nhs.AdminUI.Controllers
2+
{
3+
using System.Threading.Tasks;
4+
using LearningHub.Nhs.AdminUI.Helpers;
5+
using LearningHub.Nhs.AdminUI.Interfaces;
6+
using LearningHub.Nhs.AdminUI.Models;
7+
using Microsoft.AspNetCore.Hosting;
8+
using Microsoft.AspNetCore.Mvc;
9+
using Microsoft.Extensions.Logging;
10+
using Microsoft.FeatureManagement;
11+
12+
/// <summary>
13+
/// Controller for Azure Search administration.
14+
/// </summary>
15+
public class AzureSearchAdminController : BaseController
16+
{
17+
private readonly IAzureSearchAdminService azureSearchAdminService;
18+
private readonly IFeatureManager featureManager;
19+
private readonly ILogger<AzureSearchAdminController> logger;
20+
21+
/// <summary>
22+
/// Initializes a new instance of the <see cref="AzureSearchAdminController"/> class.
23+
/// </summary>
24+
/// <param name="hostingEnvironment">The hosting environment.</param>
25+
/// <param name="azureSearchAdminService">The Azure Search admin service.</param>
26+
/// <param name="featureManager">The feature manager.</param>
27+
/// <param name="logger">The logger.</param>
28+
public AzureSearchAdminController(
29+
IWebHostEnvironment hostingEnvironment,
30+
IAzureSearchAdminService azureSearchAdminService,
31+
IFeatureManager featureManager,
32+
ILogger<AzureSearchAdminController> logger)
33+
: base(hostingEnvironment)
34+
{
35+
this.azureSearchAdminService = azureSearchAdminService;
36+
this.featureManager = featureManager;
37+
this.logger = logger;
38+
}
39+
40+
/// <summary>
41+
/// Displays the Azure Search Admin dashboard.
42+
/// </summary>
43+
/// <returns>The view.</returns>
44+
[HttpGet]
45+
public async Task<IActionResult> Index()
46+
{
47+
if (!await this.featureManager.IsEnabledAsync(FeatureFlags.AzureSearch))
48+
{
49+
return this.NotFound();
50+
}
51+
52+
var viewModel = new AzureSearchAdminViewModel
53+
{
54+
Indexers = await this.azureSearchAdminService.GetIndexersStatusAsync(),
55+
Indexes = await this.azureSearchAdminService.GetIndexesStatusAsync(),
56+
};
57+
58+
return this.View(viewModel);
59+
}
60+
61+
/// <summary>
62+
/// Triggers an indexer run.
63+
/// </summary>
64+
/// <param name="indexerName">The name of the indexer to run.</param>
65+
/// <returns>Redirects to Index with status message.</returns>
66+
[HttpPost]
67+
[ValidateAntiForgeryToken]
68+
public async Task<IActionResult> RunIndexer(string indexerName)
69+
{
70+
if (!await this.featureManager.IsEnabledAsync(FeatureFlags.AzureSearch))
71+
{
72+
return this.NotFound();
73+
}
74+
75+
if (string.IsNullOrEmpty(indexerName))
76+
{
77+
return this.BadRequest("Indexer name is required.");
78+
}
79+
80+
var success = await this.azureSearchAdminService.RunIndexerAsync(indexerName);
81+
82+
this.TempData["Message"] = success
83+
? $"Indexer '{indexerName}' has been triggered successfully."
84+
: $"Failed to trigger indexer '{indexerName}'.";
85+
this.TempData["IsError"] = !success;
86+
87+
return this.RedirectToAction("Index");
88+
}
89+
90+
/// <summary>
91+
/// Resets an indexer.
92+
/// </summary>
93+
/// <param name="indexerName">The name of the indexer to reset.</param>
94+
/// <returns>Redirects to Index with status message.</returns>
95+
[HttpPost]
96+
[ValidateAntiForgeryToken]
97+
public async Task<IActionResult> ResetIndexer(string indexerName)
98+
{
99+
if (!await this.featureManager.IsEnabledAsync(FeatureFlags.AzureSearch))
100+
{
101+
return this.NotFound();
102+
}
103+
104+
if (string.IsNullOrEmpty(indexerName))
105+
{
106+
return this.BadRequest("Indexer name is required.");
107+
}
108+
109+
var success = await this.azureSearchAdminService.ResetIndexerAsync(indexerName);
110+
111+
this.TempData["Message"] = success
112+
? $"Indexer '{indexerName}' has been reset successfully. You may now run it to perform a full re-index."
113+
: $"Failed to reset indexer '{indexerName}'.";
114+
this.TempData["IsError"] = !success;
115+
116+
return this.RedirectToAction("Index");
117+
}
118+
}
119+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
namespace LearningHub.Nhs.AdminUI.Interfaces
2+
{
3+
using System.Collections.Generic;
4+
using System.Threading.Tasks;
5+
using LearningHub.Nhs.AdminUI.Models;
6+
7+
/// <summary>
8+
/// Interface for Azure Search administration operations.
9+
/// </summary>
10+
public interface IAzureSearchAdminService
11+
{
12+
/// <summary>
13+
/// Gets the status of all indexers.
14+
/// </summary>
15+
/// <returns>A list of indexer statuses.</returns>
16+
Task<List<IndexerStatusViewModel>> GetIndexersStatusAsync();
17+
18+
/// <summary>
19+
/// Gets the status of all indexes.
20+
/// </summary>
21+
/// <returns>A list of index statuses.</returns>
22+
Task<List<IndexStatusViewModel>> GetIndexesStatusAsync();
23+
24+
/// <summary>
25+
/// Runs an indexer manually.
26+
/// </summary>
27+
/// <param name="indexerName">The name of the indexer to run.</param>
28+
/// <returns>True if successful, false otherwise.</returns>
29+
Task<bool> RunIndexerAsync(string indexerName);
30+
31+
/// <summary>
32+
/// Resets an indexer (clears state and allows full reindex).
33+
/// </summary>
34+
/// <param name="indexerName">The name of the indexer to reset.</param>
35+
/// <returns>True if successful, false otherwise.</returns>
36+
Task<bool> ResetIndexerAsync(string indexerName);
37+
}
38+
}

AdminUI/LearningHub.Nhs.AdminUI/LearningHub.Nhs.AdminUI.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@
8989
<PackageReference Include="HtmlSanitizer" Version="6.0.453" />
9090
<PackageReference Include="IdentityModel" Version="4.6.0" />
9191
<PackageReference Include="LearningHub.Nhs.Caching" Version="2.0.2" />
92-
<PackageReference Include="LearningHub.Nhs.Models" Version="4.0.8" />
92+
<PackageReference Include="LearningHub.Nhs.Models" Version="4.0.5" />
9393
<PackageReference Include="Microsoft.ApplicationInsights.AspNetCore" Version="2.19.0" />
9494
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="6.0.36" />
9595
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="6.0.36" />

AdminUI/LearningHub.Nhs.AdminUI/LearningHub.Nhs.AdminUI.csproj.user

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,9 @@
55
</PropertyGroup>
66
<PropertyGroup>
77
<ActiveDebugProfile>Local IIS</ActiveDebugProfile>
8+
<Controller_SelectedScaffolderID>MvcControllerEmptyScaffolder</Controller_SelectedScaffolderID>
9+
<Controller_SelectedScaffolderCategoryPath>root/Common/MVC/Controller</Controller_SelectedScaffolderCategoryPath>
10+
<View_SelectedScaffolderID>RazorViewEmptyScaffolder</View_SelectedScaffolderID>
11+
<View_SelectedScaffolderCategoryPath>root/Common/MVC/View</View_SelectedScaffolderCategoryPath>
812
</PropertyGroup>
913
</Project>
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
namespace LearningHub.Nhs.AdminUI.Models
2+
{
3+
using System.Collections.Generic;
4+
5+
/// <summary>
6+
/// View model for Azure Search Admin page.
7+
/// </summary>
8+
public class AzureSearchAdminViewModel
9+
{
10+
/// <summary>
11+
/// Gets or sets the list of indexer statuses.
12+
/// </summary>
13+
public List<IndexerStatusViewModel> Indexers { get; set; } = new List<IndexerStatusViewModel>();
14+
15+
/// <summary>
16+
/// Gets or sets the list of index statuses.
17+
/// </summary>
18+
public List<IndexStatusViewModel> Indexes { get; set; } = new List<IndexStatusViewModel>();
19+
20+
/// <summary>
21+
/// Gets or sets a message to display to the user.
22+
/// </summary>
23+
public string Message { get; set; }
24+
25+
/// <summary>
26+
/// Gets or sets a value indicating whether the message is an error.
27+
/// </summary>
28+
public bool IsError { get; set; }
29+
}
30+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
namespace LearningHub.Nhs.AdminUI.Models
2+
{
3+
/// <summary>
4+
/// View model for Azure Search index status.
5+
/// </summary>
6+
public class IndexStatusViewModel
7+
{
8+
/// <summary>
9+
/// Gets or sets the name of the index.
10+
/// </summary>
11+
public string Name { get; set; }
12+
13+
/// <summary>
14+
/// Gets or sets the document count in the index.
15+
/// </summary>
16+
public long? DocumentCount { get; set; }
17+
18+
/// <summary>
19+
/// Gets or sets the storage size in bytes.
20+
/// </summary>
21+
public long? StorageSize { get; set; }
22+
}
23+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
namespace LearningHub.Nhs.AdminUI.Models
2+
{
3+
using System;
4+
5+
/// <summary>
6+
/// View model for Azure Search indexer status.
7+
/// </summary>
8+
public class IndexerStatusViewModel
9+
{
10+
/// <summary>
11+
/// Gets or sets the name of the indexer.
12+
/// </summary>
13+
public string Name { get; set; }
14+
15+
/// <summary>
16+
/// Gets or sets the current status of the indexer.
17+
/// </summary>
18+
public string Status { get; set; }
19+
20+
/// <summary>
21+
/// Gets or sets the last run time of the indexer.
22+
/// </summary>
23+
public DateTimeOffset? LastRunTime { get; set; }
24+
25+
/// <summary>
26+
/// Gets or sets the last run status (success/failed/inProgress).
27+
/// </summary>
28+
public string LastRunStatus { get; set; }
29+
30+
/// <summary>
31+
/// Gets or sets the error message if the last run failed.
32+
/// </summary>
33+
public string LastRunErrorMessage { get; set; }
34+
35+
/// <summary>
36+
/// Gets or sets the number of documents indexed in the last run.
37+
/// </summary>
38+
public int? ItemsProcessed { get; set; }
39+
40+
/// <summary>
41+
/// Gets or sets the number of documents that failed in the last run.
42+
/// </summary>
43+
public int? ItemsFailed { get; set; }
44+
}
45+
}

AdminUI/LearningHub.Nhs.AdminUI/ServiceCollectionExtension.cs

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,11 @@ public static void ConfigureServices(this IServiceCollection services, IConfigur
108108
services.AddScoped<IProviderService, ProviderService>();
109109
services.AddScoped<IMoodleApiService, MoodleApiService>();
110110

111+
// Configure Azure Search
112+
services.Configure<AzureSearchConfig>(configuration.GetSection("AzureSearch"));
113+
services.AddHttpClient("AzureSearch");
114+
services.AddScoped<IAzureSearchAdminService, AzureSearchAdminService>();
115+
111116
// web settings binding
112117
var webSettings = new WebSettings();
113118
configuration.Bind("WebSettings", webSettings);
@@ -160,12 +165,12 @@ public static void ConfigureServices(this IServiceCollection services, IConfigur
160165
}).AddCookie(
161166
"Cookies",
162167
options =>
163-
{
164-
options.AccessDeniedPath = "/Authorisation/AccessDenied";
165-
options.ExpireTimeSpan = TimeSpan.FromMinutes(webSettings.AuthTimeout);
166-
options.SlidingExpiration = true;
167-
options.EventsType = typeof(CookieEventHandler);
168-
}).AddOpenIdConnect(
168+
{
169+
options.AccessDeniedPath = "/Authorisation/AccessDenied";
170+
options.ExpireTimeSpan = TimeSpan.FromMinutes(webSettings.AuthTimeout);
171+
options.SlidingExpiration = true;
172+
options.EventsType = typeof(CookieEventHandler);
173+
}).AddOpenIdConnect(
169174
"oidc",
170175
options =>
171176
{
@@ -185,12 +190,12 @@ public static void ConfigureServices(this IServiceCollection services, IConfigur
185190
options.GetClaimsFromUserInfoEndpoint = true;
186191

187192
options.Events.OnRemoteFailure = async context =>
188-
{
189-
context.Response.Redirect("/"); // If login cancelled return to home page
190-
context.HandleResponse();
193+
{
194+
context.Response.Redirect("/"); // If login cancelled return to home page
195+
context.HandleResponse();
191196

192-
await Task.CompletedTask;
193-
};
197+
await Task.CompletedTask;
198+
};
194199

195200
options.ClaimActions.MapUniqueJsonKey("role", "role");
196201
options.ClaimActions.MapUniqueJsonKey("name", "elfh_userName");
@@ -223,11 +228,11 @@ public static void ConfigureServices(this IServiceCollection services, IConfigur
223228

224229
services.Configure<ForwardedHeadersOptions>(
225230
options =>
226-
{
227-
options.ForwardedHeaders = ForwardedHeaders.XForwardedProto | ForwardedHeaders.XForwardedFor;
228-
options.KnownNetworks.Clear();
229-
options.KnownProxies.Clear();
230-
});
231+
{
232+
options.ForwardedHeaders = ForwardedHeaders.XForwardedProto | ForwardedHeaders.XForwardedFor;
233+
options.KnownNetworks.Clear();
234+
options.KnownProxies.Clear();
235+
});
231236

232237
services.AddControllersWithViews(options =>
233238
{

0 commit comments

Comments
 (0)