2026-10-05 06:20:18 +02:00
@inherits LocalizableComponentBase
@* Servers blocked for everyone here (/clientapi/admin/domainblocks): suspended (nothing from or to them) or silenced
2026-10-17 04:32:57 +02:00
(kept out of the public feeds), their media refused or not, with a comment for the public and one for the admins.
A block is published (/api/v1/instance/domain_blocks, the About page) only when asked, since that tells the blocked
server and everyone else; published, its name can be partly starred. Blocking a server again changes its block, so
Edit fills the form with it. *@
2026-10-05 06:20:18 +02:00
<div class="flex flex-col gap-3 w-full" data-server-blocks>
<EditForm Model="form" OnValidSubmit="Block" class="flex flex-col gap-2 p-3 rounded-xl neo-inset neo-2xs">
<DataAnnotationsValidator />
<div class="flex flex-wrap gap-2 items-center">
<DInput TValue="string" class="flex-1 min-w-48" @bind-Value="form.Domain" Immediate="true" autocomplete="off" maxlength="253"
placeholder="@Localizer["A server, e.g. example.social"]" data-server-block-domain>
<Prefix><i class="ion-md-globe"></i></Prefix>
</DInput>
<DToggle LabelText="@Localizer["Suspend (otherwise silence)"]" @bind-Value="form.Suspend" />
<DToggle LabelText="@Localizer["Refuse its media"]" @bind-Value="form.RejectMedia" />
</div>
2026-10-17 04:32:57 +02:00
<div class="flex flex-wrap gap-2 items-center">
<DToggle LabelText="@Localizer["Publish it"]" Value="form.Published" ValueChanged="Publish" ValueExpression="() => form.Published"
data-server-block-published />
<DToggle LabelText="@Localizer["Hide part of its name"]" @bind-Value="form.Obfuscate" disabled="@(!form.Published)" data-server-block-obfuscate />
</div>
<p class="text-xs opacity-70">@Localizer["A published block is listed on the About page and in the instance API: the blocked server, and everyone, can read it."]</p>
2026-10-05 06:20:18 +02:00
<DInput TValue="string" @bind-Value="form.PublicComment" Immediate="true" autocomplete="off" maxlength="1000"
placeholder="@Localizer["Why, for everyone to read"]" />
<DInput TValue="string" @bind-Value="form.PrivateComment" Immediate="true" autocomplete="off" maxlength="1000"
placeholder="@Localizer["Why, for the admins only"]" />
<ErrorViewer />
<DButton Type="submit" Size="DSize.Sm" Color="DColor.Error" class="self-end"
2026-10-17 04:32:57 +02:00
Disabled="@(IsDefaultDisabled || isSaving || string.IsNullOrWhiteSpace(form.Domain))" data-server-block-save>
2026-10-05 06:20:18 +02:00
@Localizer["Block server"]
</DButton>
</EditForm>
@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 (blocks.Count == 0)
{
<p class="text-sm opacity-70">@Localizer["No server is blocked."]</p>
}
@foreach (var block in blocks)
{
<div class="flex flex-col gap-1 p-2 rounded-lg neo-raised neo-2xs" data-server-block="@block.Domain">
<div class="flex flex-wrap items-center gap-2">
<b class="flex-1 min-w-0 truncate">@block.Domain</b>
<DBadge Size="DSize.Sm" Variant="DBadgeVariant.Soft" Color="@(block.Severity == "Suspend" ? DColor.Error : DColor.Warning)">
@(block.Severity == "Suspend" ? Localizer["Suspended"] : Localizer["Silenced"])
</DBadge>
@if (block.RejectMedia)
{
<DBadge Size="DSize.Sm" Variant="DBadgeVariant.Soft">@Localizer["Media refused"]</DBadge>
}
2026-10-17 04:32:57 +02:00
@if (block.Published)
{
<DBadge Size="DSize.Sm" Variant="DBadgeVariant.Soft" Color="DColor.Info" data-server-block-public>
@(block.Obfuscate ? Localizer["Published, partly hidden"] : Localizer["Published"])
</DBadge>
}
2026-10-05 06:20:18 +02:00
<span class="text-xs opacity-70" title="@block.CreatedAt.ToLocalTime().ToString(SUtility.dateTimeFormat)">@block.CreatedAt.GetPassedTime(Localizer)</span>
2026-10-17 04:32:57 +02:00
<DButton Size="DSize.Sm" Disabled="@(IsDefaultDisabled || busy.Contains(block.Domain))" OnClick="() => Edit(block)" data-server-block-edit>
@Localizer["Edit"]
</DButton>
2026-10-05 06:20:18 +02:00
<DButton Size="DSize.Sm" Disabled="@(IsDefaultDisabled || busy.Contains(block.Domain))" OnClick="() => Unblock(block)">
@Localizer["Unblock"]
</DButton>
</div>
@if (block.PublicComment is { Length: > 0 } publicComment)
{
<p class="text-xs">@publicComment</p>
}
@if (block.PrivateComment is { Length: > 0 } privateComment)
{
<p class="text-xs opacity-70"><i class="ion-md-lock"></i> @privateComment</p>
}
</div>
}
</div>
@code {
[Inject] AdminService AdminService { get; set; }
List<ViewDomainBlock> blocks = [];
DomainBlockForm form = new();
bool isSaving;
HashSet<string> busy = [];
protected override async Task OnInitializedAsync()
{
if (CascadingState.IsOffline)
{
WhenOnline(LoadData);
StartOnlinePolling();
IsLoading = false;
return;
}
await LoadData();
IsLoading = false;
}
async Task LoadData() => blocks = await AdminService.ServerBlocks();
2026-10-17 04:32:57 +02:00
// a block that isn't published can't be partly hidden either
void Publish(bool published)
{
form.Published = published;
if (!published)
form.Obfuscate = false;
}
void Edit(ViewDomainBlock block) => form = new()
{
Domain = block.Domain,
Suspend = block.Severity == "Suspend",
RejectMedia = block.RejectMedia,
Published = block.Published,
Obfuscate = block.Obfuscate,
PublicComment = block.PublicComment,
PrivateComment = block.PrivateComment
};
2026-10-05 06:20:18 +02:00
async Task Block()
{
isSaving = true;
var result = await AdminService.BlockServer(form);
isSaving = false;
if (result.IsInvalid)
{
Toast.AddResponseError(result);
return;
}
var block = (ViewDomainBlock)result.Data;
blocks = [.. blocks.Where(b => b.Domain != block.Domain).Append(block).OrderBy(b => b.Domain, StringComparer.Ordinal)];
form = new();
Toast.AddResponseSuccess(Localizer["{0} is blocked.", block.Domain]);
}
async Task Unblock(ViewDomainBlock block)
{
busy.Add(block.Domain);
var result = await AdminService.UnblockServer(block.Domain);
busy.Remove(block.Domain);
if (result.IsInvalid)
{
Toast.AddResponseError(result);
return;
}
blocks.Remove(block);
}
}