214 lines
9.1 KiB
Plaintext
214 lines
9.1 KiB
Plaintext
@inherits LocalizableComponentBase
|
|||
|
|
|
||
|
|
@* The media limits the administrator sets over the configuration's (Media:*, /clientapi/admin/settings): what a login's
|
||
|
|
media may take in all, how big a picture and a video may be, and how long a sound or a video may last. Left empty, a
|
||
|
|
limit is the configuration's; beside each, the configured value and the one in force now, and PrivaPub's hard caps
|
||
|
|
are the maxima (40 MB, 99 MB, four hours). Saving starts from the settings PrivaPub has now
|
||
|
|
(InstanceAdminService.SaveSettings), so what Instance or Rules saved stays. *@
|
||
|
|
<div class="flex flex-col gap-3 w-full" data-media-limits>
|
||
|
|
@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 (settings is null)
|
||
|
|
{
|
||
|
|
<div class="flex flex-wrap items-center gap-2">
|
||
|
|
<p class="text-sm text-error">@Localizer["The server's settings could not be read."]</p>
|
||
|
|
<DButton Size="DSize.Sm" Disabled="IsDefaultDisabled" OnClick="Retry">@Localizer["Retry"]</DButton>
|
||
|
|
</div>
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
<p class="text-xs opacity-70">@Localizer["Empty, a limit is the one the server's configuration sets. Every change applies at once, to the next upload."]</p>
|
||
|
|
|
||
|
|
<div class="flex flex-col gap-1 p-2 rounded-lg neo-raised neo-2xs" data-media-limit="quota">
|
||
|
|
<label class="flex flex-wrap items-center gap-2">
|
||
|
|
<span class="text-sm font-semibold flex-1 min-w-40">@Localizer["Media a login may keep in all"]</span>
|
||
|
|
<DInput TValue="long?" Type="number" Size="DSize.Sm" class="w-36" @bind-Value="quotaMb" Immediate="true" autocomplete="off" min="0" step="1"
|
||
|
|
placeholder="@Localizer["as configured"]" data-media-quota>
|
||
|
|
<Suffix><span class="text-xs opacity-70">@Localizer["MB"]</span></Suffix>
|
||
|
|
</DInput>
|
||
|
|
</label>
|
||
|
|
<p class="text-xs opacity-70">
|
||
|
|
@Localizer["Configured: {0}", Quota(settings.Media.ConfiguredQuotaBytesPerRoot)] ·
|
||
|
|
<span data-media-effective>@Localizer["In force: {0}", Quota(settings.Media.EffectiveQuotaBytesPerRoot)]</span> ·
|
||
|
|
@Localizer["0: no quota"]
|
||
|
|
</p>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="flex flex-col gap-1 p-2 rounded-lg neo-raised neo-2xs" data-media-limit="image">
|
||
|
|
<label class="flex flex-wrap items-center gap-2">
|
||
|
|
<span class="text-sm font-semibold flex-1 min-w-40">@Localizer["Largest picture"]</span>
|
||
|
|
<DInput TValue="long?" Type="number" Size="DSize.Sm" class="w-36" @bind-Value="imageMb" Immediate="true" autocomplete="off" min="1"
|
||
|
|
max="@(settings.Media.HardMaxImageBytes / Megabyte)" step="1" placeholder="@Localizer["as configured"]" data-media-image>
|
||
|
|
<Suffix><span class="text-xs opacity-70">@Localizer["MB"]</span></Suffix>
|
||
|
|
</DInput>
|
||
|
|
</label>
|
||
|
|
<p class="text-xs opacity-70">
|
||
|
|
@Localizer["Configured: {0}", settings.Media.ConfiguredMaxImageBytes.GetFileSize(Localizer)] ·
|
||
|
|
<span data-media-effective>@Localizer["In force: {0}", settings.Media.EffectiveMaxImageBytes.GetFileSize(Localizer)]</span> ·
|
||
|
|
@Localizer["At most {0}", settings.Media.HardMaxImageBytes.GetFileSize(Localizer)]
|
||
|
|
</p>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="flex flex-col gap-1 p-2 rounded-lg neo-raised neo-2xs" data-media-limit="video">
|
||
|
|
<label class="flex flex-wrap items-center gap-2">
|
||
|
|
<span class="text-sm font-semibold flex-1 min-w-40">@Localizer["Largest video or sound"]</span>
|
||
|
|
<DInput TValue="long?" Type="number" Size="DSize.Sm" class="w-36" @bind-Value="videoMb" Immediate="true" autocomplete="off" min="1"
|
||
|
|
max="@(settings.Media.HardMaxVideoBytes / Megabyte)" step="1" placeholder="@Localizer["as configured"]" data-media-video>
|
||
|
|
<Suffix><span class="text-xs opacity-70">@Localizer["MB"]</span></Suffix>
|
||
|
|
</DInput>
|
||
|
|
</label>
|
||
|
|
<p class="text-xs opacity-70">
|
||
|
|
@Localizer["Configured: {0}", settings.Media.ConfiguredMaxVideoBytes.GetFileSize(Localizer)] ·
|
||
|
|
<span data-media-effective>@Localizer["In force: {0}", settings.Media.EffectiveMaxVideoBytes.GetFileSize(Localizer)]</span> ·
|
||
|
|
@Localizer["At most {0}", settings.Media.HardMaxVideoBytes.GetFileSize(Localizer)]
|
||
|
|
</p>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="flex flex-col gap-1 p-2 rounded-lg neo-raised neo-2xs" data-media-limit="seconds">
|
||
|
|
<label class="flex flex-wrap items-center gap-2">
|
||
|
|
<span class="text-sm font-semibold flex-1 min-w-40">@Localizer["Longest video or sound"]</span>
|
||
|
|
<DInput TValue="int?" Type="number" Size="DSize.Sm" class="w-36" @bind-Value="seconds" Immediate="true" autocomplete="off" min="1"
|
||
|
|
max="@settings.Media.HardMaxSeconds" step="1" placeholder="@Localizer["as configured"]" data-media-seconds>
|
||
|
|
<Suffix><span class="text-xs opacity-70">@Localizer["seconds"]</span></Suffix>
|
||
|
|
</DInput>
|
||
|
|
</label>
|
||
|
|
<p class="text-xs opacity-70">
|
||
|
|
@Localizer["Configured: {0}", Length(settings.Media.ConfiguredMaxSeconds)] ·
|
||
|
|
<span data-media-effective>@Localizer["In force: {0}", Length(settings.Media.EffectiveMaxSeconds)]</span> ·
|
||
|
|
@Localizer["At most {0}", Length(settings.Media.HardMaxSeconds)]
|
||
|
|
</p>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<ErrorViewer AdditionalErrors="errors" />
|
||
|
|
<div class="flex flex-wrap items-center justify-end gap-2 pointer-coarse:gap-2">
|
||
|
|
<DButton Size="DSize.Sm" Disabled="IsDefaultDisabled || isSaving" OnClick="UseConfigured" data-media-use-configured>
|
||
|
|
@Localizer["Use the configured values"]
|
||
|
|
</DButton>
|
||
|
|
<DButton Size="DSize.Sm" Disabled="IsDefaultDisabled || isSaving" OnClick="Reset" data-media-reset>@Localizer["Discard changes"]</DButton>
|
||
|
|
<DButton Size="DSize.Sm" Color="DColor.Primary" Disabled="IsDefaultDisabled || isSaving" OnClick="Save" data-media-save>
|
||
|
|
@if (isSaving)
|
||
|
|
{
|
||
|
|
<DLoading Size="DSize.Sm" LabelText="@Localizer["loading"]" />
|
||
|
|
}
|
||
|
|
<span>@Localizer["Save the limits"]</span>
|
||
|
|
</DButton>
|
||
|
|
</div>
|
||
|
|
}
|
||
|
|
</div>
|
||
|
|
|
||
|
|
@code {
|
||
|
|
const long Megabyte = 1024 * 1024;
|
||
|
|
|
||
|
|
[Inject] InstanceAdminService InstanceAdminService { get; set; }
|
||
|
|
|
||
|
|
ViewServerSettings settings;
|
||
|
|
// the overrides as being written, in megabytes and seconds; null: the configured value
|
||
|
|
long? quotaMb;
|
||
|
|
long? imageMb;
|
||
|
|
long? videoMb;
|
||
|
|
int? seconds;
|
||
|
|
List<string> errors = [];
|
||
|
|
bool isSaving;
|
||
|
|
|
||
|
|
protected override async Task OnInitializedAsync()
|
||
|
|
{
|
||
|
|
if (CascadingState.IsOffline)
|
||
|
|
{
|
||
|
|
WhenOnline(LoadData);
|
||
|
|
StartOnlinePolling();
|
||
|
|
IsLoading = false;
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
await LoadData();
|
||
|
|
IsLoading = false;
|
||
|
|
}
|
||
|
|
|
||
|
|
async Task LoadData()
|
||
|
|
{
|
||
|
|
settings = await InstanceAdminService.Settings();
|
||
|
|
Reset();
|
||
|
|
}
|
||
|
|
|
||
|
|
async Task Retry()
|
||
|
|
{
|
||
|
|
IsLoading = true;
|
||
|
|
await LoadData();
|
||
|
|
IsLoading = false;
|
||
|
|
}
|
||
|
|
|
||
|
|
void Reset()
|
||
|
|
{
|
||
|
|
errors = [];
|
||
|
|
if (settings is null)
|
||
|
|
return;
|
||
|
|
quotaMb = Megabytes(settings.Media.QuotaBytesPerRoot);
|
||
|
|
imageMb = Megabytes(settings.Media.MaxImageBytes);
|
||
|
|
videoMb = Megabytes(settings.Media.MaxVideoBytes);
|
||
|
|
seconds = settings.Media.MaxSeconds;
|
||
|
|
}
|
||
|
|
|
||
|
|
void UseConfigured()
|
||
|
|
{
|
||
|
|
errors = [];
|
||
|
|
quotaMb = null;
|
||
|
|
imageMb = null;
|
||
|
|
videoMb = null;
|
||
|
|
seconds = null;
|
||
|
|
}
|
||
|
|
|
||
|
|
string Quota(long bytes) => bytes <= 0 ? Localizer["no quota"] : bytes.GetFileSize(Localizer);
|
||
|
|
|
||
|
|
static string Length(int value) => NerdRow.Duration(TimeSpan.FromSeconds(value));
|
||
|
|
|
||
|
|
static long? Megabytes(long? bytes) => bytes is { } value ? (long)Math.Round(value / (double)Megabyte) : null;
|
||
|
|
|
||
|
|
// what was set stays to the byte while its megabytes are left as they were shown
|
||
|
|
static long? Bytes(long? megabytes, long? before) =>
|
||
|
|
megabytes is not { } value ? null : before is { } kept && Megabytes(kept) == value ? kept : value * Megabyte;
|
||
|
|
|
||
|
|
async Task Save()
|
||
|
|
{
|
||
|
|
errors = [];
|
||
|
|
var media = settings.Media;
|
||
|
|
if (quotaMb is < 0)
|
||
|
|
errors.Add(Localizer["The quota can't be below 0."]);
|
||
|
|
if (imageMb is { } image && (image < 1 || image * Megabyte > media.HardMaxImageBytes))
|
||
|
|
errors.Add(Localizer["A picture's limit goes from 1 MB to {0}.", media.HardMaxImageBytes.GetFileSize(Localizer)]);
|
||
|
|
if (videoMb is { } video && (video < 1 || video * Megabyte > media.HardMaxVideoBytes))
|
||
|
|
errors.Add(Localizer["A video's limit goes from 1 MB to {0}.", media.HardMaxVideoBytes.GetFileSize(Localizer)]);
|
||
|
|
if (seconds is { } length && (length < 1 || length > media.HardMaxSeconds))
|
||
|
|
errors.Add(Localizer["A length goes from 1 second to {0}.", Length(media.HardMaxSeconds)]);
|
||
|
|
if (errors.Count > 0)
|
||
|
|
return;
|
||
|
|
|
||
|
|
// only the limits changed here: what another administrator saved meanwhile to the others stays
|
||
|
|
var quotaChanged = quotaMb != Megabytes(media.QuotaBytesPerRoot);
|
||
|
|
var imageChanged = imageMb != Megabytes(media.MaxImageBytes);
|
||
|
|
var videoChanged = videoMb != Megabytes(media.MaxVideoBytes);
|
||
|
|
var secondsChanged = seconds != media.MaxSeconds;
|
||
|
|
isSaving = true;
|
||
|
|
var result = await InstanceAdminService.SaveSettings(latest => latest.Media = new ServerMediaLimitsForm
|
||
|
|
{
|
||
|
|
QuotaBytesPerRoot = quotaChanged ? Bytes(quotaMb, media.QuotaBytesPerRoot) : latest.Media.QuotaBytesPerRoot,
|
||
|
|
MaxImageBytes = imageChanged ? Bytes(imageMb, media.MaxImageBytes) : latest.Media.MaxImageBytes,
|
||
|
|
MaxVideoBytes = videoChanged ? Bytes(videoMb, media.MaxVideoBytes) : latest.Media.MaxVideoBytes,
|
||
|
|
MaxSeconds = secondsChanged ? seconds : latest.Media.MaxSeconds
|
||
|
|
});
|
||
|
|
isSaving = false;
|
||
|
|
if (result.IsInvalid)
|
||
|
|
{
|
||
|
|
Toast.AddResponseError(result);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
settings = (ViewServerSettings)result.Data;
|
||
|
|
Reset();
|
||
|
|
Toast.AddResponseSuccess(Localizer["The media limits are saved."]);
|
||
|
|
}
|
||
|
|
}
|