191 lines
6.4 KiB
Plaintext
191 lines
6.4 KiB
Plaintext
@inherits LocalizableComponentBase
|
|||
|
|
|
||
|
|
@* The relays the server's actor subscribes to (/clientapi/admin/relays): the configuration's (Federation:Relays, removed
|
||
|
|
only there) and the ones added here, each with its subscription's state. PrivaPub answers an addition or a removal at
|
||
|
|
once (202) and subscribes or unsubscribes in the background, since reaching a relay takes a fetch, so the panel reads
|
||
|
|
the list again a few seconds later. *@
|
||
|
|
<div class="flex flex-col gap-3 w-full" data-relays>
|
||
|
|
<div class="flex flex-wrap items-center gap-2 pointer-coarse:gap-2">
|
||
|
|
<DInput TValue="string" class="flex-1 min-w-48" Type="url" @bind-Value="address" Immediate="true" autocomplete="off" maxlength="2048"
|
||
|
|
placeholder="@Localizer["https://relay.example/actor"]" aria-label="@Localizer["The relay's address"]" data-relay-address>
|
||
|
|
<Prefix><i class="ion-md-radio" aria-hidden="true"></i></Prefix>
|
||
|
|
</DInput>
|
||
|
|
<DButton Size="DSize.Sm" Color="DColor.Primary" Disabled="IsDefaultDisabled || isSaving || string.IsNullOrWhiteSpace(address)" OnClick="Add" data-relay-add>
|
||
|
|
<i class="ion-md-add" aria-hidden="true"></i><span>@Localizer["Add the relay"]</span>
|
||
|
|
</DButton>
|
||
|
|
</div>
|
||
|
|
<p class="text-xs opacity-70">@Localizer["The https address of the relay's actor, or of its inbox. Public posts of this server go to it, and its posts come here."]</p>
|
||
|
|
|
||
|
|
@if (IsLoading)
|
||
|
|
{
|
||
|
|
<p class="flex w-full items-center justify-center gap-2">
|
||
|
|
<DLoading Variant="DLoadingVariant.Dots" LabelText="@Localizer["loading"]" />
|
||
|
|
@Localizer["loading"]
|
||
|
|
</p>
|
||
|
|
}
|
||
|
|
else if (relays.Count == 0)
|
||
|
|
{
|
||
|
|
<p class="text-sm opacity-70" data-relays-empty>@Localizer["The server subscribes to no relay."]</p>
|
||
|
|
}
|
||
|
|
|
||
|
|
@foreach (var relay in relays)
|
||
|
|
{
|
||
|
|
<div class="flex flex-col gap-1 p-2 rounded-lg neo-raised neo-2xs" data-relay="@relay.Address" data-relay-state="@relay.State"
|
||
|
|
data-relay-source="@relay.Source">
|
||
|
|
<div class="flex flex-wrap items-center gap-2 pointer-coarse:gap-2">
|
||
|
|
<b class="flex-1 min-w-0 break-all text-sm">@relay.Address</b>
|
||
|
|
@switch (relay.State)
|
||
|
|
{
|
||
|
|
case "accepted":
|
||
|
|
<DBadge Size="DSize.Sm" Variant="DBadgeVariant.Soft" Color="DColor.Success">@Localizer["Subscribed"]</DBadge>
|
||
|
|
break;
|
||
|
|
case "pending":
|
||
|
|
<DBadge Size="DSize.Sm" Variant="DBadgeVariant.Soft" Color="DColor.Warning">@Localizer["Waiting for the relay"]</DBadge>
|
||
|
|
break;
|
||
|
|
case "rejected":
|
||
|
|
<DBadge Size="DSize.Sm" Variant="DBadgeVariant.Soft" Color="DColor.Error">@Localizer["Refused by the relay"]</DBadge>
|
||
|
|
break;
|
||
|
|
default:
|
||
|
|
<DBadge Size="DSize.Sm" Variant="DBadgeVariant.Soft">@Localizer["Not asked yet"]</DBadge>
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
@if (relay.Source == "configuration")
|
||
|
|
{
|
||
|
|
<DBadge Size="DSize.Sm" Variant="DBadgeVariant.Outline" title="@Localizer["Named by the configuration (Federation:Relays): removed only there."]">
|
||
|
|
@Localizer["Configured"]
|
||
|
|
</DBadge>
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
<DButton Size="DSize.Sm" Disabled="IsDefaultDisabled || isSaving" OnClick="() => Remove(relay)" data-relay-remove>
|
||
|
|
<i class="ion-md-trash text-error" aria-hidden="true"></i><span>@Localizer["Remove"]</span>
|
||
|
|
</DButton>
|
||
|
|
}
|
||
|
|
</div>
|
||
|
|
@if (relay.ActorURI is { Length: > 0 } actor && actor != relay.Address)
|
||
|
|
{
|
||
|
|
<p class="text-xs opacity-70 break-all">@Localizer["Its actor: {0}", actor]</p>
|
||
|
|
}
|
||
|
|
<p class="text-xs opacity-70">
|
||
|
|
@if (relay.RequestedAt is { } requested)
|
||
|
|
{
|
||
|
|
<span title="@requested.ToLocalTime().ToString(SUtility.dateTimeFormatExt)">@Localizer["Asked {0} ago", requested.GetPassedTime(Localizer)]</span>
|
||
|
|
}
|
||
|
|
@if (relay.AnsweredAt is { } answered)
|
||
|
|
{
|
||
|
|
<span title="@answered.ToLocalTime().ToString(SUtility.dateTimeFormatExt)"> · @Localizer["answered {0} ago", answered.GetPassedTime(Localizer)]</span>
|
||
|
|
}
|
||
|
|
</p>
|
||
|
|
@if (relay.Source == "configuration")
|
||
|
|
{
|
||
|
|
<p class="text-xs opacity-70">@Localizer["Named by the configuration (Federation:Relays): removed only there."]</p>
|
||
|
|
}
|
||
|
|
</div>
|
||
|
|
}
|
||
|
|
</div>
|
||
|
|
|
||
|
|
@code {
|
||
|
|
// when the list is read again after a change: subscribing to a relay is a fetch, then its answer
|
||
|
|
static readonly TimeSpan[] Rereads = [TimeSpan.FromSeconds(4), TimeSpan.FromSeconds(15)];
|
||
|
|
|
||
|
|
[Inject] InstanceAdminService InstanceAdminService { get; set; }
|
||
|
|
|
||
|
|
List<ViewRelay> relays = [];
|
||
|
|
string address;
|
||
|
|
bool isSaving;
|
||
|
|
CancellationTokenSource rereading;
|
||
|
|
|
||
|
|
protected override async Task OnInitializedAsync()
|
||
|
|
{
|
||
|
|
if (CascadingState.IsOffline)
|
||
|
|
{
|
||
|
|
WhenOnline(LoadData);
|
||
|
|
StartOnlinePolling();
|
||
|
|
IsLoading = false;
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
await LoadData();
|
||
|
|
IsLoading = false;
|
||
|
|
}
|
||
|
|
|
||
|
|
async Task LoadData() => relays = await InstanceAdminService.Relays();
|
||
|
|
|
||
|
|
async Task Add()
|
||
|
|
{
|
||
|
|
isSaving = true;
|
||
|
|
var result = await InstanceAdminService.AddRelay(address);
|
||
|
|
isSaving = false;
|
||
|
|
if (result.IsInvalid)
|
||
|
|
{
|
||
|
|
Toast.AddResponseError(result);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
relays = (List<ViewRelay>)result.Data;
|
||
|
|
address = null;
|
||
|
|
Toast.AddResponseSuccess(Localizer["The relay is added: the server subscribes to it now."]);
|
||
|
|
await ReadAgainSoon();
|
||
|
|
}
|
||
|
|
|
||
|
|
async Task Remove(ViewRelay relay)
|
||
|
|
{
|
||
|
|
isSaving = true;
|
||
|
|
var result = await InstanceAdminService.RemoveRelay(relay.Address);
|
||
|
|
isSaving = false;
|
||
|
|
if (result.IsInvalid)
|
||
|
|
{
|
||
|
|
Toast.AddResponseError(result);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
relays = (List<ViewRelay>)result.Data;
|
||
|
|
Toast.AddResponseSuccess(Localizer["The relay is removed: the server unsubscribes from it now."]);
|
||
|
|
await ReadAgainSoon();
|
||
|
|
}
|
||
|
|
|
||
|
|
// the background subscription answers later: the list is read again, the last change's timer replacing the one before
|
||
|
|
async Task ReadAgainSoon()
|
||
|
|
{
|
||
|
|
if (rereading is not null)
|
||
|
|
{
|
||
|
|
await rereading.CancelAsync();
|
||
|
|
rereading.Dispose();
|
||
|
|
}
|
||
|
|
rereading = new();
|
||
|
|
_ = ReadAgain(rereading.Token);
|
||
|
|
}
|
||
|
|
|
||
|
|
async Task ReadAgain(CancellationToken cancellationToken)
|
||
|
|
{
|
||
|
|
foreach (var pause in Rereads)
|
||
|
|
{
|
||
|
|
try
|
||
|
|
{
|
||
|
|
await Task.Delay(pause, cancellationToken);
|
||
|
|
}
|
||
|
|
catch (OperationCanceledException)
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
var read = await InstanceAdminService.Relays(cancellationToken);
|
||
|
|
if (cancellationToken.IsCancellationRequested)
|
||
|
|
return;
|
||
|
|
// an empty answer to a list that had relays is a failed read: a change here showed its own list at once
|
||
|
|
if (read.Count == 0 && relays.Count > 0)
|
||
|
|
continue;
|
||
|
|
relays = read;
|
||
|
|
await InvokeAsync(StateHasChanged);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public override async ValueTask DisposeAsync()
|
||
|
|
{
|
||
|
|
if (rereading is not null)
|
||
|
|
{
|
||
|
|
await rereading.CancelAsync();
|
||
|
|
rereading.Dispose();
|
||
|
|
}
|
||
|
|
await base.DisposeAsync();
|
||
|
|
}
|
||
|
|
}
|