R4 the client catches up: follow requests, followers, downvotes, dismissing, filtering a post, quote policies, statistics in detail
Build / Build (push) Successful in 1m8s
Build / Build (push) Successful in 1m8s
Follow requests answered on the persona's own profile and from the Notifications tab; its followers and the accounts it follows listed there, a follower removed (asked first). Downvotes on threadiverse posts, after the public-vote notice. A notification dismissed from its line. "Filter this post" from a card's menu, and the Filters page showing the posts a filter holds. Who may quote the persona's own posts, and revoking a quote of one. Statistics: a server in detail with "describe again", the ledger's events, the server's own days. The composer deletes an upload taken out before posting. MediaType.Documents goes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WpVCoDeM1qUTRp4fXQdfof
This commit is contained in:
1 parent
b731d8c8c0
commit
b8d7858acb
25 files changed
+1543
-61
No files matched your search
@@ -0,0 +1,155 @@
|
||||
@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();
|
||||
}
|
||||
Reference in new issue
Block a user