@inherits LocalizableComponentBase
@* PrivaPub's federation statistics (/clientapi/admin/statistics): the last days at a glance, the servers exchanged with
(by traffic, failures, latency or name, 50 a page; a server's name opens its detail, HostDetailDialog), how far the
crawler got, the ledger's events (by server, channel and outcome, 100 at a time, older ones from the last one's time)
and the server's own days (its client API, the documents it served, its ledger). Counted per server, never per
account. *@
@Localizer["Period"]
@foreach (var period in Periods)
{
@Localizer["{0} days", period]
}
@Localizer["Servers by"]
@Localizer["Traffic"]
@Localizer["Failures"]
@Localizer["Latency"]
@Localizer["Name"]
@if (IsLoading)
{
@Localizer["loading"]
}
else if (overview is null)
{
@Localizer["The statistics could not be read."]
}
else
{
@Localizer["Deliveries that arrived"]
@(overview.DeliverySuccess is { } success ? $"{success * 100:0.#}%" : "—")
@Localizer["p50 {0} ms, p95 {1} ms", Ms(overview.DeliveryLatencyMs.P50), Ms(overview.DeliveryLatencyMs.P95)]
@Localizer["servers"]
@overview.Hosts.Active
@Localizer["{0} known", overview.Hosts.Known]
@Localizer["Accounts seen"]
@overview.Accounts
@Localizer["ledger {0} written, {1} dropped", overview.Ledger.Written, overview.Ledger.Dropped]
@foreach (var (title, counts) in Outcomes)
{
@title
@foreach (var (outcome, count) in counts.OrderByDescending(c => c.Value))
{
@outcome @count
}
}
@if (hosts is not null)
{
@Localizer["server"]
@Localizer["software"]
@Localizer["In"]
@Localizer["Out"]
@Localizer["Refused"]
@Localizer["Failures"]
p50
@Localizer["Delivery"]
@foreach (var host in hosts.Hosts)
{
openHost = host.Host" data-statistics-host-open>@host.Host
@host.Software @host.Version
@host.In
@host.Out
@host.Refused
@host.Failures
@Ms(host.LatencyP50)
@if (host.Delivery?.UnavailableUntil is { } until && until > DateTime.UtcNow)
{
@Localizer["paused"]
}
else if (host.Delivery?.ConsecutiveFailures > 0)
{
@Localizer["{0} failing", host.Delivery.ConsecutiveFailures]
}
}
@if (hosts.Total > PageSize)
{
}
@hosts.Attribution
}
@if (openHost is { } detailHost)
{
}
@if (crawler is not null)
{
@Localizer["Crawler"]
@(crawler.Enabled ? Localizer["on"] : Localizer["off"]) ·
@Localizer["{0} servers touched, {1} only crawled, {2} due, {3} refuse robots", crawler.Touched, crawler.Crawled, crawler.Due, crawler.RobotsDisallowed]
@foreach (var host in crawler.Recent)
{
@host.Host
@host.Software
@if (host.LastCrawledAt is { } crawled)
{
@crawled.GetPassedTime(Localizer)
}
@if (host.CrawlError is { Length: > 0 } crawlError)
{
@crawlError
}
}
}
@Localizer["Events"]
@Localizer["server"]
@Localizer["Channel"]
@Localizer["All"]
@foreach (var channel in EventChannels)
{
@channel
}
@Localizer["Outcome"]
@Localizer["All"]
@foreach (var outcome in EventOutcomes)
{
@outcome
}
@if (events.Count == 0)
{
@Localizer["Empty"]
}
else
{
}
@if (hasMoreEvents)
{
@if (isLoadingMore)
{
}
@Localizer["Load more"]
}
@Localizer["Server days"]
@if (serverDays.Count == 0)
{
@Localizer["Empty"]
}
else
{
@Localizer["Day"]
@Localizer["Client API"]
4xx
5xx
@Localizer["Served"]
@Localizer["Ledger"]
@Localizer["Accounts"]
@Localizer["servers"]
@foreach (var day in Enumerable.Reverse(serverDays))
{
@day.Day.ToString(SUtility.dateTimeFormat)
@ClientRequests(day, null)
@ClientRequests(day, ":4xx")
@ClientRequests(day, ":5xx")
@day.Served.Values.Sum()
@day.LedgerWritten / @day.LedgerDropped / @day.LedgerFailed
@day.Accounts
@day.Hosts
}
}
}
@code {
[Inject] AdminService AdminService { get; set; }
const int PageSize = 50;
const int EventsPageSize = 100;
// the ledger's channels and outcomes, as it keys them
static readonly string[] EventChannels = ["recv", "in", "out", "http", "preview", "crawl"];
static readonly string[] EventOutcomes = ["queued", "accepted", "refused", "dropped", "rejected", "ok", "failed", "deferred", "retry", "dead"];
static readonly int[] Periods = [1, 7, 30, 90];
ViewStatisticsOverview overview;
ViewHostsPage hosts;
ViewCrawler crawler;
List