Skip to content

Commit b986aec

Browse files
author
GitHub Copilot
committed
fix: stabilize azure dashboard and deploy
1 parent ab02ad6 commit b986aec

2 files changed

Lines changed: 74 additions & 37 deletions

File tree

‎.github/workflows/deploy-appservice.yml‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,17 +35,20 @@ jobs:
3535
creds: ${{ secrets.AZURE_CREDENTIALS }}
3636

3737
- name: Deploy to Azure App Service
38-
run: az webapp deploy --name "$APP_NAME" --resource-group "$RESOURCE_GROUP" --src-path ./deploy.zip --type zip --clean true --async false
38+
shell: bash
39+
run: |
40+
az webapp deploy --name "$APP_NAME" --resource-group "$RESOURCE_GROUP" --src-path ./deploy.zip --type zip --clean true --async true
41+
az webapp restart --name "$APP_NAME" --resource-group "$RESOURCE_GROUP"
3942
4043
- name: Verify health endpoint
4144
shell: bash
4245
run: |
43-
for i in {1..18}; do
46+
for i in {1..24}; do
4447
if curl -fsS "https://${APP_NAME}.azurewebsites.net/health" > /dev/null; then
4548
echo "Health check passed"
4649
exit 0
4750
fi
48-
echo "Waiting for app warm-up ($i/12)..."
51+
echo "Waiting for app warm-up ($i/24)..."
4952
sleep 10
5053
done
5154
echo "Health check failed"

‎PoPunkouterSoftware.Client/Components/Pages/AzureDashboard.razor‎

Lines changed: 68 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,31 @@
1010

1111
<RadzenText TextStyle="TextStyle.H3" TagName="TagName.H1" class="rz-color-primary rz-mb-2">Azure Dashboard</RadzenText>
1212

13-
@if (report == null)
13+
@if (_loading)
1414
{
1515
<RadzenProgressBar Mode="ProgressBarMode.Indeterminate" class="rz-my-4" />
1616
<RadzenText TextStyle="TextStyle.Body1">Loading Azure report...</RadzenText>
1717
}
18+
else if (!string.IsNullOrWhiteSpace(_loadError))
19+
{
20+
<RadzenAlert AlertStyle="AlertStyle.Warning" Variant="Variant.Outlined" ShowIcon="true" class="rz-my-4">
21+
<div><strong>Azure dashboard load failed.</strong></div>
22+
<div style="margin-top:.35rem">@_loadError</div>
23+
<div style="margin-top:.75rem">
24+
<RadzenButton Text="Retry" Icon="refresh" ButtonStyle="ButtonStyle.Primary" Size="ButtonSize.Small" Click="@(async () => await LoadReportAsync())" />
25+
</div>
26+
</RadzenAlert>
27+
}
1828
else
1929
{
30+
var currentReport = report ?? new AzureReport();
31+
2032
<RadzenText TextStyle="TextStyle.Caption" class="rz-color-secondary rz-mb-2">
2133
<RadzenStack Orientation="Orientation.Horizontal" JustifyContent="JustifyContent.SpaceBetween" AlignItems="AlignItems.Center" Wrap="FlexWrap.Wrap" Gap="8px">
22-
<span>Subscription: <strong>@report.Subscription?.Name</strong> &nbsp;|&nbsp; Generated: <strong>@(report.GeneratedAt?.ToString("yyyy-MM-dd HH:mm") ?? "—")</strong>
23-
@if (report.GeneratedAt.HasValue && (DateTime.UtcNow - report.GeneratedAt.Value).TotalHours > 24)
34+
<span>Subscription: <strong>@currentReport.Subscription?.Name</strong> &nbsp;|&nbsp; Generated: <strong>@(currentReport.GeneratedAt?.ToString("yyyy-MM-dd HH:mm") ?? "—")</strong>
35+
@if (currentReport.GeneratedAt is DateTime generatedAt && (DateTime.UtcNow - generatedAt).TotalHours > 24)
2436
{
25-
<RadzenBadge Text="@($"Data is {(int)(DateTime.UtcNow - report.GeneratedAt.Value).TotalHours}h old — refresh recommended")"
37+
<RadzenBadge Text="@($"Data is {(int)(DateTime.UtcNow - generatedAt).TotalHours}h old — refresh recommended")"
2638
BadgeStyle="BadgeStyle.Warning" IsPill="true" Style="margin-left:.5rem;font-size:.7rem" />
2739
}
2840
</span>
@@ -39,18 +51,18 @@ else
3951

4052
<!-- ── Summary Strip ── -->
4153
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:.75rem;margin-bottom:1.75rem">
42-
@SummaryCard(report.WebServices?.Total.ToString() ?? "0", "Services", "var(--rz-info)")
43-
@SummaryCard(report.WebServices?.ByStatus?.Active.ToString() ?? "0", "Active", "var(--rz-success)")
44-
@SummaryCardLink(report.WebServices?.ByStatus?.Broken.ToString() ?? "0", "Broken ↗", report.WebServices?.ByStatus?.Broken > 0 ? "var(--rz-danger)" : "var(--rz-secondary)", () => _selectedTabIndex = 0)
45-
@SummaryCard(report.Cost?.TotalFormatted ?? "$0.00", "30-Day Cost", "var(--rz-warning)")
54+
@SummaryCard(currentReport.WebServices?.Total.ToString() ?? "0", "Services", "var(--rz-info)")
55+
@SummaryCard(currentReport.WebServices?.ByStatus?.Active.ToString() ?? "0", "Active", "var(--rz-success)")
56+
@SummaryCardLink(currentReport.WebServices?.ByStatus?.Broken.ToString() ?? "0", "Broken ↗", currentReport.WebServices?.ByStatus?.Broken > 0 ? "var(--rz-danger)" : "var(--rz-secondary)", () => _selectedTabIndex = 0)
57+
@SummaryCard(currentReport.Cost?.TotalFormatted ?? "$0.00", "30-Day Cost", "var(--rz-warning)")
4658
@SummaryCardLink(safeToRemove.Count.ToString(), "Safe to Remove ↗", safeToRemove.Count > 0 ? "var(--rz-danger)" : "var(--rz-success)", () => _selectedTabIndex = 1)
4759
</div>
4860

4961
<RadzenTabs @bind-SelectedIndex="_selectedTabIndex">
5062
<Tabs>
5163

5264
<!-- ── Tab 1: Web Services ── -->
53-
<RadzenTabsItem Text="@($"🌐 Web Services ({report.WebServices?.Total ?? 0})")">
65+
<RadzenTabsItem Text="@($"🌐 Web Services ({currentReport.WebServices?.Total ?? 0})")">
5466
<div style="overflow-x:auto">
5567
<RadzenDataGrid Data="@SortedServices" TItem="WebService" AllowFiltering="true" AllowSorting="true"
5668
AllowPaging="true" PageSize="15" ShowPagingSummary="true" AllowColumnResize="true" FilterMode="FilterMode.Simple">
@@ -188,21 +200,21 @@ else
188200
</RadzenTabsItem>
189201

190202
<!-- ── Tab 3: Cost ── -->
191-
<RadzenTabsItem Text="@($"💳 Cost ({report.Cost?.TotalFormatted ?? "$0"})")">
203+
<RadzenTabsItem Text="@($"💳 Cost ({currentReport.Cost?.TotalFormatted ?? "$0"})")">
192204
<div class="rz-mt-2">
193-
@if (report.Cost?.Note != null)
205+
@if (currentReport.Cost?.Note != null)
194206
{
195207
<div style="background:rgba(255,214,0,.08);border:1px solid rgba(255,214,0,.3);border-radius:6px;padding:.7rem 1rem;color:#ffd700;margin-bottom:1rem">
196-
📝 @report.Cost.Note
208+
📝 @currentReport.Cost.Note
197209
</div>
198210
}
199-
@if (report.BurnRate?.DailyCosts.Count > 0)
211+
@if (currentReport.BurnRate?.DailyCosts.Count > 0)
200212
{
201213
<RadzenRow class="rz-mb-4">
202214
<RadzenColumn SizeXS="12" SizeMD="7">
203215
<RadzenText TextStyle="TextStyle.H6" class="rz-mb-1">
204216
Monthly Burn Rate
205-
<RadzenBadge Text="@($"Projected: {report.BurnRate.ProjectedFormatted}")" BadgeStyle="BadgeStyle.Warning" IsPill="true" Style="font-size:.75rem;margin-left:.5rem" />
217+
<RadzenBadge Text="@($"Projected: {currentReport.BurnRate?.ProjectedFormatted ?? "—"}")" BadgeStyle="BadgeStyle.Warning" IsPill="true" Style="font-size:.75rem;margin-left:.5rem" />
206218
</RadzenText>
207219
<RadzenText TextStyle="TextStyle.Caption" class="rz-color-secondary rz-mb-1">Based on daily spend this calendar month only — not rolling 30d</RadzenText>
208220
<RadzenChart Style="height:260px">
@@ -230,7 +242,7 @@ else
230242
</RadzenColumn>
231243
</RadzenRow>
232244
}
233-
<RadzenDataGrid Data="@report.Cost?.TopCostDrivers" TItem="CostDriver" AllowSorting="true" PageSize="30" AllowPaging="false">
245+
<RadzenDataGrid Data="@currentReport.Cost?.TopCostDrivers" TItem="CostDriver" AllowSorting="true" PageSize="30" AllowPaging="false">
234246
<Columns>
235247
<RadzenDataGridColumn TItem="CostDriver" Property="Name" Title="Service / Resource Group" />
236248
<RadzenDataGridColumn TItem="CostDriver" Property="Cost" Title="Cost (USD)" Width="130px" TextAlign="TextAlign.Right">
@@ -243,19 +255,19 @@ else
243255
</Columns>
244256
</RadzenDataGrid>
245257
<RadzenCard class="rz-mt-3" Style="text-align:right">
246-
<RadzenText TextStyle="TextStyle.H6">Total: <span style="color:#ffd700">@(report.Cost?.TotalFormatted ?? "—")</span></RadzenText>
258+
<RadzenText TextStyle="TextStyle.H6">Total: <span style="color:#ffd700">@(currentReport.Cost?.TotalFormatted ?? "—")</span></RadzenText>
247259
</RadzenCard>
248260
</div>
249261
</RadzenTabsItem>
250262

251263
<!-- ── SSL Expiry Tab ── -->
252-
<RadzenTabsItem Text="@($"🔒 SSL ({report.SslExpiry?.Count ?? 0})")">
264+
<RadzenTabsItem Text="@($"🔒 SSL ({currentReport.SslExpiry?.Count ?? 0})")">
253265
<div class="rz-mt-2">
254266
@{
255-
var sslCritical = report.SslExpiry?.Count(x => x.DaysLeft is { } d && d < 14) ?? 0;
256-
var sslWarning = report.SslExpiry?.Count(x => x.DaysLeft is { } d && d is >= 14 and < 60) ?? 0;
257-
var sslErrored = report.SslExpiry?.Count(x => x.Error != null && x.DaysLeft == null) ?? 0;
258-
var sslOk = report.SslExpiry?.Count(x => x.DaysLeft != null) ?? 0;
267+
var sslCritical = currentReport.SslExpiry?.Count(x => x.DaysLeft is { } d && d < 14) ?? 0;
268+
var sslWarning = currentReport.SslExpiry?.Count(x => x.DaysLeft is { } d && d is >= 14 and < 60) ?? 0;
269+
var sslErrored = currentReport.SslExpiry?.Count(x => x.Error != null && x.DaysLeft == null) ?? 0;
270+
var sslOk = currentReport.SslExpiry?.Count(x => x.DaysLeft != null) ?? 0;
259271
}
260272
@if (sslErrored > 0 && sslOk == 0)
261273
{
@@ -275,7 +287,7 @@ else
275287
🟡 @sslWarning certificate(s) expire within 60 days — schedule renewal soon.
276288
</div>
277289
}
278-
<RadzenDataGrid Data="@report.SslExpiry" TItem="SslEntry" AllowSorting="true" AllowFiltering="true"
290+
<RadzenDataGrid Data="@currentReport.SslExpiry" TItem="SslEntry" AllowSorting="true" AllowFiltering="true"
279291
FilterMode="FilterMode.Simple" AllowPaging="true" PageSize="20">
280292
<Columns>
281293
<RadzenDataGridColumn TItem="SslEntry" Property="Name" Title="Service" Width="180px" />
@@ -318,17 +330,17 @@ else
318330
</RadzenTabsItem>
319331

320332
<!-- ── App Insights Tab ── -->
321-
<RadzenTabsItem Text="@($"📊 App Insights ({report.AppInsightsMetrics?.Count ?? 0})")">
333+
<RadzenTabsItem Text="@($"📊 App Insights ({currentReport.AppInsightsMetrics?.Count ?? 0})")">
322334
<div class="rz-mt-2">
323-
@if (report.AppInsightsMetrics?.Any() != true)
335+
@if (currentReport.AppInsightsMetrics?.Any() != true)
324336
{
325337
<div style="background:rgba(100,180,255,.07);border:1px solid rgba(100,180,255,.25);border-radius:6px;padding:.75rem 1rem;color:var(--rz-text-secondary-color)">
326338
ℹ️ No Application Insights components found, or metrics could not be retrieved.
327339
</div>
328340
}
329341
else
330342
{
331-
<RadzenDataGrid Data="@report.AppInsightsMetrics" TItem="AppInsightsMetric"
343+
<RadzenDataGrid Data="@currentReport.AppInsightsMetrics" TItem="AppInsightsMetric"
332344
AllowSorting="true" AllowFiltering="true" FilterMode="FilterMode.Simple"
333345
AllowPaging="true" PageSize="20">
334346
<Columns>
@@ -359,7 +371,7 @@ else
359371
</RadzenTabsItem>
360372

361373
<!-- ── Resource Breakdown Tab ── -->
362-
<RadzenTabsItem Text="@($"📦 Resources ({report.AllResourceSummary?.Total ?? 0})")">
374+
<RadzenTabsItem Text="@($"📦 Resources ({currentReport.AllResourceSummary?.Total ?? 0})")">
363375
<div class="rz-mt-2">
364376
<RadzenRow>
365377
<RadzenColumn SizeXS="12" SizeMD="6">
@@ -419,17 +431,17 @@ else
419431
</RadzenTabsItem>
420432

421433
<!-- ── Orphaned Resources Tab ── -->
422-
<RadzenTabsItem Text="@($"🧟 Orphaned ({report.OrphanedResources?.Count ?? 0})")">
434+
<RadzenTabsItem Text="@($"🧟 Orphaned ({currentReport.OrphanedResources?.Count ?? 0})")">
423435
<div class="rz-mt-2">
424-
@if (report.OrphanedResources?.Any() != true)
436+
@if (currentReport.OrphanedResources?.Any() != true)
425437
{
426438
<div style="background:rgba(50,255,167,.08);border:1px solid rgba(50,255,167,.3);border-radius:6px;padding:.7rem 1rem;color:var(--rz-success-color)">
427439
✅ No orphaned resources — no unattached disks, idle public IPs, or empty App Service Plans.
428440
</div>
429441
}
430442
else
431443
{
432-
<RadzenDataGrid Data="@report.OrphanedResources" TItem="OrphanedResource" AllowSorting="true" AllowPaging="true" PageSize="15">
444+
<RadzenDataGrid Data="@currentReport.OrphanedResources" TItem="OrphanedResource" AllowSorting="true" AllowPaging="true" PageSize="15">
433445
<Columns>
434446
<RadzenDataGridColumn TItem="OrphanedResource" Property="Name" Title="Resource" Width="200px" />
435447
<RadzenDataGridColumn TItem="OrphanedResource" Property="ResourceGroup" Title="Resource Group" Width="160px" />
@@ -460,9 +472,9 @@ else
460472
</RadzenTabsItem>
461473

462474
<!-- ── Storage Tab ── -->
463-
<RadzenTabsItem Text="@($"💾 Storage ({report.StorageInventory?.Count ?? 0})")">
475+
<RadzenTabsItem Text="@($"💾 Storage ({currentReport.StorageInventory?.Count ?? 0})")">
464476
<div class="rz-mt-2">
465-
<RadzenDataGrid Data="@report.StorageInventory" TItem="StorageItem" AllowFiltering="true" AllowSorting="true" PageSize="15" AllowPaging="true" FilterMode="FilterMode.Simple">
477+
<RadzenDataGrid Data="@currentReport.StorageInventory" TItem="StorageItem" AllowFiltering="true" AllowSorting="true" PageSize="15" AllowPaging="true" FilterMode="FilterMode.Simple">
466478
<Columns>
467479
<RadzenDataGridColumn TItem="StorageItem" Property="Name" Title="Account" Width="180px" />
468480
<RadzenDataGridColumn TItem="StorageItem" Property="ResourceGroup" Title="Resource Group" Width="160px" />
@@ -509,6 +521,8 @@ else
509521
private List<WebService> services = new();
510522
private List<SafeToRemoveItem> safeToRemove = new();
511523
private int _selectedTabIndex = 0;
524+
private bool _loading = true;
525+
private string? _loadError;
512526
private IEnumerable<WebService> SortedServices =>
513527
services.OrderBy(s => s.HttpStatus == "active" ? 0 : 1).ThenBy(s => s.FriendlyName ?? s.Name);
514528

@@ -536,16 +550,36 @@ else
536550
// No file system access needed in WASM — all data comes via HTTP.
537551
private async Task LoadReportAsync()
538552
{
553+
_loading = true;
554+
_loadError = null;
555+
StateHasChanged();
556+
539557
try
540558
{
541-
var opts = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
559+
var opts = new JsonSerializerOptions(JsonSerializerDefaults.Web)
560+
{
561+
PropertyNameCaseInsensitive = true
562+
};
563+
542564
report = await Http.GetFromJsonAsync<AzureReport>("/api/diag/report", opts);
543-
services = report?.WebServices?.Services ?? new List<WebService>();
565+
if (report is null)
566+
throw new InvalidOperationException("The Azure report endpoint returned no data.");
567+
568+
services = report.WebServices?.Services ?? new List<WebService>();
544569
safeToRemove = BuildSafeToRemove(report);
545570
}
546571
catch (Exception ex)
547572
{
548-
Console.WriteLine($"Azure dashboard load error: {ex.Message}");
573+
report = null;
574+
services = new List<WebService>();
575+
safeToRemove = new List<SafeToRemoveItem>();
576+
_loadError = ex.Message;
577+
Console.Error.WriteLine($"Azure dashboard load error: {ex}");
578+
}
579+
finally
580+
{
581+
_loading = false;
582+
StateHasChanged();
549583
}
550584
}
551585

0 commit comments

Comments
 (0)