Some checks are pending
CI / Build og unit tests (push) Waiting to run
CI / Integration (mcr.microsoft.com/mssql/server:2017-latest) (push) Blocked by required conditions
CI / Integration (mcr.microsoft.com/mssql/server:2019-latest) (push) Blocked by required conditions
CI / Integration (mcr.microsoft.com/mssql/server:2022-latest) (push) Blocked by required conditions
CI / Integration (mcr.microsoft.com/mssql/server:2025-latest) (push) Blocked by required conditions
Server-rendered portal on the Hub: staff login (PBKDF2 hashes, rate limited, antiforgery, local-only return URLs), customer overview with the most widespread problems across customers, customer and finding pages, support cases linked to findings, and license revocation that takes effect on the next agent submission. add-staff CLI command. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E3EExwndy8Z22EK2Qpt6BK
69 lines
3.3 KiB
Text
69 lines
3.3 KiB
Text
@page "/portal"
|
||
@attribute [Authorize]
|
||
@inject PortalQueries Queries
|
||
|
||
<PageTitle>Overblik · SQLInsight Hub</PageTitle>
|
||
|
||
<h1>Kundeoverblik</h1>
|
||
<p class="lead">Betalende kunders instanser og deres aktuelle problemer.</p>
|
||
|
||
<div class="kpis">
|
||
<div class="kpi"><div><strong>@_customers.Count</strong><span>kunder</span></div></div>
|
||
<div class="kpi"><div><strong>@_customers.Sum(c => c.Instances)</strong><span>instanser</span></div></div>
|
||
<div class="kpi sev-critical"><div><strong>@_customers.Sum(c => c.Critical)</strong><span>kritiske fund</span></div></div>
|
||
<div class="kpi sev-high"><div><strong>@_customers.Sum(c => c.High)</strong><span>høje fund</span></div></div>
|
||
<div class="kpi"><div><strong>@_new</strong><span>nye fund, 7 dage</span></div></div>
|
||
<div class="kpi sev-info"><div><strong>@_silent</strong><span>instanser uden data i 1 time</span></div></div>
|
||
</div>
|
||
|
||
<section class="card">
|
||
<div class="card-head"><h2>Kunder</h2><span class="muted">sorteret efter alvor</span></div>
|
||
<table>
|
||
<thead><tr><th>Kunde</th><th>Aftale</th><th>Licens udløber</th><th class="num">Instanser</th><th>Fund</th><th>Seneste data</th></tr></thead>
|
||
<tbody>
|
||
@foreach (var c in _customers)
|
||
{
|
||
<tr>
|
||
<td><a href="/portal/kunder/@c.Id"><strong>@c.Name</strong></a></td>
|
||
<td>@(c.Plan ?? "–")</td>
|
||
<td class="nowrap @(c.LicenseExpires < Queries.Now.AddDays(30) ? "bad" : "")">@(c.LicenseExpires?.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture) ?? "–")</td>
|
||
<td class="num">@c.Instances</td>
|
||
<td class="counts">
|
||
@if (c.Critical > 0) { <Sev Severity="4" Count="c.Critical" /> }
|
||
@if (c.High > 0) { <Sev Severity="3" Count="c.High" /> }
|
||
@if (c.Critical == 0 && c.High == 0) { <span class="muted">ingen kritiske/høje</span> }
|
||
</td>
|
||
<td class="nowrap @(c.LastSubmission < Queries.Now.AddHours(-1) ? "bad" : "")">@(c.LastSubmission?.ToString("yyyy-MM-dd HH:mm", CultureInfo.InvariantCulture) ?? "aldrig") UTC</td>
|
||
</tr>
|
||
}
|
||
</tbody>
|
||
</table>
|
||
</section>
|
||
|
||
<section class="card">
|
||
<div class="card-head"><h2>Mest udbredte problemer</h2><span class="muted">hvor en vejledning eller en produktændring hjælper flest</span></div>
|
||
<table>
|
||
<thead><tr><th>Regel</th><th>Eksempel</th><th class="num">Kunder</th><th class="num">Fund</th><th>Værst</th></tr></thead>
|
||
<tbody>
|
||
@foreach (var p in _common)
|
||
{
|
||
<tr><td class="mono">@p.RuleId</td><td>@p.Title</td><td class="num">@p.Customers</td><td class="num">@p.Findings</td><td><Sev Severity="p.WorstSeverity" /></td></tr>
|
||
}
|
||
</tbody>
|
||
</table>
|
||
</section>
|
||
|
||
@code {
|
||
private IReadOnlyList<CustomerRow> _customers = [];
|
||
private IReadOnlyList<CommonProblem> _common = [];
|
||
private int _new;
|
||
private int _silent;
|
||
|
||
protected override async Task OnInitializedAsync()
|
||
{
|
||
_customers = await Queries.CustomersAsync();
|
||
_common = await Queries.CommonProblemsAsync();
|
||
_new = await Queries.NewFindingsSinceAsync(Queries.Now.AddDays(-7));
|
||
_silent = await Queries.SilentInstancesAsync();
|
||
}
|
||
}
|