Files
decePubClient/Components/Admin/HostDetailDialog.razor
T

155 lines
6.5 KiB
Plaintext
Raw Normal View History

@inherits LocalizableComponentBase
@* One server of the federation statistics (/clientapi/admin/statistics/hosts/{host}), opened from its row: what it says
of itself and what PrivaPub knows of its deliveries (as nerd rows, by their field names), its days with the main
counters (newest first; a live day is today's, or one not rolled up yet), and its latest events. "Describe again"
asks PrivaPub to read its NodeInfo and instance description anew. Counted per server, never per account. *@
<DModal Placement="DModalPlacement.ResponsiveBottom" IsOpen="true" IsOpenChanged="Closed" CloseText="@Localizer["Close"]" BoxClass="md:w-11/12 md:max-w-4xl"
data-host-detail="@HostName">
<Heading><span class="break-all">@HostName</span></Heading>
<ChildContent>
<div class="flex flex-col gap-4 mt-3">
<div class="flex flex-wrap items-center gap-2">
<DButton Size="DSize.Sm" Disabled="@(IsDefaultDisabled || isDescribing)" OnClick="Describe" data-host-describe>
<i class="ion-md-refresh" aria-hidden="true"></i><span>@Localizer["Describe again"]</span>
</DButton>
</div>
@if (IsLoading)
{
<p class="flex items-center gap-2"><DLoading Variant="DLoadingVariant.Dots" LabelText="@Localizer["loading"]" />@Localizer["loading"]</p>
}
else if (detail is null)
{
<p class="text-sm opacity-70">@Localizer["The statistics could not be read."]</p>
}
else
{
@if (detail.Instance is { } instance)
{
<NerdRows Rows="InstanceRows(instance)" />
}
<DTable Size="DSize.Xs" Zebra="true" WrapperClass="max-h-72 rounded-xl neo-inset neo-2xs" data-host-days data-scroll-x>
<thead>
<tr>
<th>@Localizer["Day"]</th>
<th class="text-right">@Localizer["Received"]</th>
<th class="text-right">@Localizer["In"]</th>
<th class="text-right">@Localizer["Out"]</th>
<th class="text-right">@Localizer["Failures"]</th>
<th class="text-right">@Localizer["Accounts"]</th>
</tr>
</thead>
<tbody>
@foreach (var day in detail.Days.OrderByDescending(d => d.Day))
{
<tr data-host-day="@NerdRow.Time(day.Day)">
<td class="whitespace-nowrap">
@day.Day.ToString(SUtility.dateTimeFormat)
@if (day.Live)
{
<DBadge Size="DSize.Xs" Color="DColor.Info">@Localizer["live"]</DBadge>
}
</td>
<td class="text-right tabular-nums">@Channel(day.Counters, "recv")</td>
<td class="text-right tabular-nums">@Channel(day.Counters, "in")</td>
<td class="text-right tabular-nums">@Channel(day.Counters, "out")</td>
<td class="text-right tabular-nums">@Failures(day.Counters)</td>
<td class="text-right tabular-nums">@day.Accounts</td>
</tr>
}
</tbody>
</DTable>
@if (detail.Events.Count > 0)
{
<b class="text-sm">@Localizer["Last events"]</b>
<EventsTable Events="detail.Events" ShowHost="false" />
}
<p class="text-xs opacity-70">@detail.Attribution</p>
}
</div>
</ChildContent>
</DModal>
@code {
[Inject] AdminService AdminService { get; set; }
[Parameter, EditorRequired] public string HostName { get; set; }
/// <summary>How many days back its days go.</summary>
[Parameter] public int Days { get; set; } = 30;
[Parameter] public EventCallback OnClosed { get; set; }
ViewHostDetail detail;
bool isDescribing;
protected override async Task OnInitializedAsync()
{
if (CascadingState.IsOffline)
{
WhenOnline(LoadData);
StartOnlinePolling();
IsLoading = false;
return;
}
await LoadData();
IsLoading = false;
}
async Task LoadData() => detail = await AdminService.Host(HostName, Days);
async Task Describe()
{
isDescribing = true;
var result = await AdminService.Describe(HostName);
isDescribing = false;
if (result.IsInvalid)
{
Toast.AddResponseError(result);
return;
}
Toast.AddResponseSuccess(Localizer["PrivaPub will read what {0} says of itself again.", HostName]);
}
/// <summary>A channel's counters of a day (recv: deliveries to PrivaPub's inboxes; in: activities taken in; out: sent).</summary>
static long Channel(Dictionary<string, long> counters, string channel) =>
counters.Where(c => c.Key.StartsWith(channel + ":", StringComparison.Ordinal)).Sum(c => c.Value);
// the deliveries out that failed, are retried or were given up, as the servers table counts them
static long Failures(Dictionary<string, long> counters) =>
counters.Where(c => c.Key.Split(':') is { Length: >= 4 } parts && parts[0] == "out" && parts[3] is "retry" or "dead" or "failed").Sum(c => c.Value);
static IEnumerable<NerdRow> InstanceRows(ViewHostInstance instance)
{
yield return NerdRow.Of("software", $"{instance.Software} {instance.SoftwareVersion}".Trim());
yield return NerdRow.Of("nodeName", instance.NodeName);
yield return NerdRow.Of("title", instance.Title);
yield return NerdRow.Of("nodeDescription", instance.NodeDescription);
yield return NerdRow.Of("openRegistrations", instance.OpenRegistrations);
yield return NerdRow.Of("registrationMode", instance.RegistrationMode);
yield return NerdRow.Of("usersTotal", instance.UsersTotal);
yield return NerdRow.Of("usersActiveMonth", instance.UsersActiveMonth);
yield return NerdRow.Of("localPosts", instance.LocalPosts);
yield return NerdRow.Of("languages", instance.Languages.Count > 0 ? string.Join(", ", instance.Languages) : null);
yield return NerdRow.Of("seen", instance.Seen);
yield return NerdRow.Of("firstSeenAt", instance.FirstSeenAt);
yield return NerdRow.Of("lastSeenAt", instance.LastSeenAt);
yield return NerdRow.Of("describedAt", instance.DescribedAt);
yield return NerdRow.Of("descriptionError", instance.DescriptionError);
yield return NerdRow.Of("lastSuccessAt", instance.LastSuccessAt);
yield return NerdRow.Of("lastFailureAt", instance.LastFailureAt);
yield return NerdRow.Of("lastError", instance.LastError);
yield return NerdRow.Of("consecutiveFailures", instance.ConsecutiveFailures > 0 ? instance.ConsecutiveFailures : null);
yield return NerdRow.Of("unavailableUntil", instance.UnavailableUntil);
yield return NerdRow.Of("place", string.Join(", ", new[] { instance.City, instance.Country }.Where(p => !string.IsNullOrWhiteSpace(p))) is { Length: > 0 } place ? place : null);
yield return NerdRow.Of("asn", instance.Asn is { } asn ? $"AS{asn} {instance.AsnOrg}".Trim() : null);
yield return NerdRow.Of("cdn", instance.Cdn);
yield return NerdRow.Of("instanceApi", instance.InstanceApi);
yield return NerdRow.Of("sourceUrl", instance.SourceUrl);
}
Task Closed(bool open) => open ? Task.CompletedTask : OnClosed.InvokeAsync();
}