Skip to content
This repository was archived by the owner on Mar 31, 2026. It is now read-only.

Commit c8b616c

Browse files
committed
Enhance admin site front page to have basic, non-secret metadata
1 parent 7c8c1b5 commit c8b616c

9 files changed

Lines changed: 279 additions & 8 deletions

File tree

src/Logic/NuGetInsightsSettings.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ public NuGetInsightsSettings()
4646
};
4747
}
4848

49+
public string DeploymentLabel { get; set; }
4950
public string GalleryBaseUrl { get; set; }
5051
public string PackagesContainerBaseUrl { get; set; }
5152
public string SymbolPackagesContainerBaseUrl { get; set; }

src/Website/Controllers/HomeController.cs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,29 @@
1-
// Copyright (c) .NET Foundation. All rights reserved.
1+
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System.Diagnostics;
55
using System.Threading.Tasks;
66
using Microsoft.AspNetCore.Authentication;
77
using Microsoft.AspNetCore.Authentication.Cookies;
88
using Microsoft.AspNetCore.Mvc;
9+
using Microsoft.Extensions.Options;
910

1011
namespace NuGet.Insights.Website.Controllers
1112
{
1213
public class HomeController : Controller
1314
{
15+
private readonly IAdminViewModelCache _cache;
16+
private readonly IOptions<NuGetInsightsWebsiteSettings> _options;
17+
18+
public HomeController(IAdminViewModelCache cache, IOptions<NuGetInsightsWebsiteSettings> options)
19+
{
20+
_cache = cache;
21+
_options = options;
22+
}
23+
1424
public ViewResult Index()
1525
{
16-
return View();
26+
return View(_options.Value.ShowAdminMetadata ? _cache : null);
1727
}
1828

1929
public async Task<RedirectToActionResult> SignOutAndRedirect()
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Diagnostics;
6+
using System.Threading;
7+
using System.Threading.Tasks;
8+
using Microsoft.Extensions.DependencyInjection;
9+
using Microsoft.Extensions.Hosting;
10+
using Microsoft.Extensions.Logging;
11+
using Microsoft.Extensions.Options;
12+
13+
namespace NuGet.Insights.Website
14+
{
15+
public class CachedAdminViewModelService : BackgroundService
16+
{
17+
public class AdminViewModelCache : IAdminViewModelCache
18+
{
19+
public bool Refreshing { get; set; }
20+
public CachedAdminViewModel Value { get; set; }
21+
public NuGetInsightsWebsiteSettings Settings { get; set; }
22+
}
23+
24+
private readonly IServiceProvider _serviceProvider;
25+
private readonly AdminViewModelCache _cache;
26+
private readonly IOptions<NuGetInsightsWebsiteSettings> _options;
27+
private readonly ILogger<CachedAdminViewModelService> _logger;
28+
29+
public CachedAdminViewModelService(
30+
IServiceProvider serviceProvider,
31+
AdminViewModelCache cache,
32+
IOptions<NuGetInsightsWebsiteSettings> options,
33+
ILogger<CachedAdminViewModelService> logger)
34+
{
35+
_serviceProvider = serviceProvider;
36+
_cache = cache;
37+
_options = options;
38+
_logger = logger;
39+
}
40+
41+
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
42+
{
43+
if (!_options.Value.ShowAdminMetadata)
44+
{
45+
return;
46+
}
47+
48+
while (!stoppingToken.IsCancellationRequested)
49+
{
50+
_cache.Refreshing = true;
51+
_logger.LogInformation("Loading latest admin view model.");
52+
var sw = Stopwatch.StartNew();
53+
try
54+
{
55+
var asOfTimestamp = DateTimeOffset.UtcNow;
56+
using (var scope = _serviceProvider.CreateScope())
57+
{
58+
var factory = scope.ServiceProvider.GetRequiredService<ViewModelFactory>();
59+
var data = await factory.GetAdminViewModelAsync();
60+
_cache.Value = new CachedAdminViewModel(asOfTimestamp, data);
61+
_cache.Settings = scope.ServiceProvider.GetRequiredService<IOptions<NuGetInsightsWebsiteSettings>>().Value;
62+
}
63+
_logger.LogInformation(
64+
"Latest admin view model loaded after {DurationMs}ms. Sleeping for {SleepMs}ms.",
65+
sw.Elapsed.TotalMilliseconds,
66+
_options.Value.CachedAdminViewModelMaxAge.TotalMilliseconds);
67+
}
68+
catch (Exception ex)
69+
{
70+
_logger.LogError(
71+
ex,
72+
"Failed to load admin view model after {DurationMs}ms. Sleeping for {SleepMs}ms.",
73+
sw.Elapsed.TotalMilliseconds,
74+
_options.Value.CachedAdminViewModelMaxAge.TotalMilliseconds);
75+
}
76+
finally
77+
{
78+
_cache.Refreshing = false;
79+
}
80+
81+
await Task.Delay(_options.Value.CachedAdminViewModelMaxAge, stoppingToken);
82+
}
83+
}
84+
}
85+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
6+
namespace NuGet.Insights.Website
7+
{
8+
public record CachedAdminViewModel(DateTimeOffset AsOfTimestamp, AdminViewModel Data);
9+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
namespace NuGet.Insights.Website
5+
{
6+
public interface IAdminViewModelCache
7+
{
8+
public bool Refreshing { get; }
9+
public CachedAdminViewModel Value { get; }
10+
public NuGetInsightsWebsiteSettings Settings { get; }
11+
}
12+
}
Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,28 @@
1-
// Copyright (c) .NET Foundation. All rights reserved.
1+
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

4+
using System;
45
using System.Collections.Generic;
56
using NuGet.Insights.Worker;
67

78
namespace NuGet.Insights.Website
89
{
910
public class NuGetInsightsWebsiteSettings : NuGetInsightsWorkerSettings
1011
{
12+
/// <summary>
13+
/// Whether or not to show the admin link on the home page.
14+
/// </summary>
1115
public bool ShowAdminLink { get; set; } = true;
16+
1217
public bool RestrictUsers { get; set; } = true;
1318
public List<AllowedObject> AllowedUsers { get; set; } = new List<AllowedObject>();
1419
public List<AllowedObject> AllowedGroups { get; set; } = new List<AllowedObject>();
20+
21+
/// <summary>
22+
/// Whether or not to show some non-sensitive workflow run and catalog scan metadata on the home page.
23+
/// </summary>
24+
public bool ShowAdminMetadata { get; set; } = true;
25+
26+
public TimeSpan CachedAdminViewModelMaxAge { get; set; } = TimeSpan.FromMinutes(5);
1527
}
1628
}

src/Website/Startup.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,14 @@ public void ConfigureServices(IServiceCollection services)
3636

3737
services.AddSingleton<ControllerInitializer>();
3838
services.AddSingleton<MoveMessagesTaskQueue>();
39+
services.AddSingleton<CachedAdminViewModelService.AdminViewModelCache>();
40+
services.AddSingleton<IAdminViewModelCache>(s => s.GetRequiredService<CachedAdminViewModelService.AdminViewModelCache>());
3941
services.AddScoped<IAuthorizationHandler, AllowListAuthorizationHandler>();
4042
services.AddScoped<AllowListAuthorizationHandler>();
4143
services.AddTransient<ViewModelFactory>();
4244
services.AddHostedService<InitializerHostedService>();
4345
services.AddHostedService<MoveMessagesHostedService>();
46+
services.AddHostedService<CachedAdminViewModelService>();
4447

4548
services.AddApplicationInsightsTelemetry(options =>
4649
{
Lines changed: 141 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,144 @@
1-
@{
1+
@using Humanizer.Localisation;
2+
@using NuGet.Insights.Worker.KustoIngestion;
3+
@using NuGet.Insights.Worker.Workflow;
4+
@model IAdminViewModelCache
5+
@{
26
ViewData["Title"] = "We're up and running.";
37
}
48

5-
<h2>@ViewData["Title"]</h2>
9+
@functions {
10+
private void ShowDate(DateTimeOffset timestamp)
11+
{
12+
<text>
13+
@timestamp.ToZulu()
14+
(@((DateTimeOffset.UtcNow - timestamp).Humanize(2, minUnit: TimeUnit.Second)) ago)
15+
</text>
16+
}
17+
}
18+
19+
@if (Model != null)
20+
{
21+
if (Model.Refreshing)
22+
{
23+
<p><b>This data is currently being refreshed.</b></p>
24+
}
25+
26+
if (Model.Value is not null)
27+
{
28+
<h2>Latest workflow status</h2>
29+
<p>
30+
This summary is cached as of @{
31+
ShowDate(Model.Value.AsOfTimestamp);
32+
}.
33+
</p>
34+
35+
var workflowRun = Model
36+
.Value
37+
.Data
38+
.WorkflowRuns
39+
.MaxBy(x => x.Created);
40+
if (workflowRun is null)
41+
{
42+
<p>There are no workflow runs.</p>
43+
}
44+
else
45+
{
46+
<dl>
47+
<dt>Workflow state</dt>
48+
<dd>@workflowRun.State</dd>
49+
</dl>
50+
<dl>
51+
<dt>Workflow started</dt>
52+
<dd>
53+
@{
54+
ShowDate(workflowRun.Created);
55+
}
56+
</dd>
57+
</dl>
58+
if (workflowRun.Completed.HasValue)
59+
{
60+
<dl>
61+
<dt>Workflow completed</dt>
62+
<dd>
63+
@{
64+
ShowDate(workflowRun.Completed.Value);
65+
}
66+
</dd>
67+
</dl>
68+
}
69+
}
70+
71+
var kustoIngestion = Model
72+
.Value
73+
.Data
74+
.KustoIngestions
75+
.Where(x => x.State == KustoIngestionState.Complete)
76+
.MaxBy(x => x.Completed.Value);
77+
if (kustoIngestion is null)
78+
{
79+
<p>There are no Kusto ingestions.</p>
80+
}
81+
else
82+
{
83+
<dl>
84+
<dt>Latest complete Kusto ingestion</dt>
85+
<dd>
86+
@{
87+
ShowDate(kustoIngestion.Completed.Value);
88+
}
89+
</dd>
90+
</dl>
91+
92+
var maxGroup = Model
93+
.Value
94+
.Data
95+
.CatalogScans
96+
.SelectMany(x => x.LatestScans)
97+
.Where(x => x.State == CatalogIndexScanState.Complete)
98+
.Where(x => x.Completed.Value < kustoIngestion.Completed.Value)
99+
.GroupBy(x => x.Max.Value)
100+
.MaxBy(x => x.Key);
101+
102+
if (maxGroup is not null)
103+
{
104+
<dl>
105+
<dt>Catalog commit ingested into Kusto</dt>
106+
<dd>
107+
@{
108+
ShowDate(maxGroup.Key);
109+
}
110+
</dd>
111+
</dl>
112+
}
113+
}
114+
}
115+
116+
if (Model.Settings is not null)
117+
{
118+
<h2>Current settings</h2>
119+
<dl>
120+
<dt>Deployment label</dt>
121+
<dd>@Model.Settings.DeploymentLabel</dd>
122+
</dl>
123+
<dl>
124+
<dt>V3 service index used for catalog scans</dt>
125+
<dd>
126+
<a href="@Model.Settings.V3ServiceIndex">@Model.Settings.V3ServiceIndex</a>
127+
@{
128+
var environment = Model.Settings.V3ServiceIndex switch
129+
{
130+
"https://api.nuget.org/v3/index.json" => "(NuGet.org PROD environment)",
131+
"https://apiint.nugettest.org/v3/index.json" => "(NuGet.org INT environment)",
132+
"https://apidev.nugettest.org/v3/index.json" => "(NuGet.org DEV environment)",
133+
_ => null
134+
};
135+
}
136+
@environment
137+
</dd>
138+
</dl>
139+
}
140+
}
141+
else
142+
{
143+
<h2>@ViewData["Title"]</h2>
144+
}

src/Website/Views/Shared/_Layout.cshtml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
@inject IOptions<NuGetInsightsWebsiteSettings> Options
1+
@inject IOptions<NuGetInsightsWebsiteSettings> Options
22
<!DOCTYPE html>
33
<html lang="en">
44
<head>
55
<meta charset="utf-8" />
66
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
7-
<title>@ViewData["Title"] - NuGet.Insights</title>
7+
<title>@ViewData["Title"] - NuGet Insights</title>
88
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" asp-append-version="true" />
99
<link rel="stylesheet" href="~/lib/bootstrap-icons/bootstrap-icons.css" asp-append-version="true" />
1010
<link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
@@ -13,7 +13,7 @@
1313
<header>
1414
<nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-light border-bottom box-shadow mb-3">
1515
<div class="container">
16-
<a class="navbar-brand" asp-area="" asp-controller="Home" asp-action="Index">NuGet.Insights</a>
16+
<a class="navbar-brand" asp-area="" asp-controller="Home" asp-action="Index">NuGet Insights</a>
1717
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target=".navbar-collapse" aria-controls="navbarSupportedContent"
1818
aria-expanded="false" aria-label="Toggle navigation">
1919
<span class="navbar-toggler-icon"></span>

0 commit comments

Comments
 (0)