Upgrade to MudBlazor 7 and add log pages

This commit is contained in:
ahjephson
2024-06-28 18:05:02 +01:00
parent e9e41950f7
commit 5daa68fc7b
43 changed files with 584 additions and 208 deletions

View File

@@ -3,15 +3,13 @@
<MudGrid>
<MudItem xs="12">
<MudFileUpload T="IReadOnlyList<IBrowserFile>" FilesChanged="UploadFiles" Accept=".torrent" MaximumFileCount="50" >
<ButtonTemplate>
<MudButton HtmlTag="label"
Variant="Variant.Filled"
<ActivatorContent>
<MudButton Variant="Variant.Filled"
Color="Color.Primary"
StartIcon="@Icons.Material.Filled.CloudUpload"
for="@context.Id">
StartIcon="@Icons.Material.Filled.CloudUpload">
Choose files
</MudButton>
</ButtonTemplate>
</ActivatorContent>
</MudFileUpload>
</MudItem>
</MudGrid>

View File

@@ -2,7 +2,7 @@
<DialogContent>
<MudGrid>
<MudItem xs="12">
<MudList Clickable="true">
<MudList T="string">
<MudListItem Icon="@Icons.Material.Filled.Add" IconColor="Color.Info" OnClick="AddCategory">Add</MudListItem>
<MudListItem Icon="@Icons.Material.Filled.Remove" IconColor="Color.Error" OnClick="RemoveCategory">Remove</MudListItem>
<MudDivider />

View File

@@ -2,7 +2,7 @@
<DialogContent>
<MudGrid>
<MudItem xs="12">
<MudList Clickable="true">
<MudList T="string">
<MudListItem Icon="@Icons.Material.Filled.Add" IconColor="Color.Info" OnClick="AddTag">Add</MudListItem>
<MudListItem Icon="@Icons.Material.Filled.Remove" IconColor="Color.Error" OnClick="RemoveAllTags">Remove All</MudListItem>
<MudDivider />

View File

@@ -1,10 +1,11 @@
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
using MudBlazor;
using System.Numerics;
namespace Lantean.QBTMudBlade.Components.Dialogs
{
public partial class SliderFieldDialog<T>
public partial class SliderFieldDialog<T> where T : struct, INumber<T>
{
[CascadingParameter]
public MudDialogInstance MudDialog { get; set; } = default!;
@@ -13,13 +14,13 @@ namespace Lantean.QBTMudBlade.Components.Dialogs
public string Label { get; set; } = default!;
[Parameter]
public T? Value { get; set; }
public T Value { get; set; }
[Parameter]
public T? Min { get; set; }
public T Min { get; set; } = T.Zero;
[Parameter]
public T? Max { get; set; }
public T Max { get; set; } = T.One;
protected void Cancel(MouseEventArgs args)
{

View File

@@ -40,7 +40,7 @@
<MudTh Class="@className" Style="@(GetColumnStyle(column))">
@if (column.SortSelector is not null)
{
<SortLabel SortDirectionChanged="@(c => SetSort(column.Id, c))" SortDirection="@(column.Id == _sortColumn ? _sortDirection : SortDirection.None)">@columnHeader</SortLabel>
<SortLabel Class="column-header" SortDirectionChanged="@(c => SetSort(column.Id, c))" SortDirection="@(column.Id == _sortColumn ? _sortDirection : SortDirection.None)">@columnHeader</SortLabel>
}
else
{

View File

@@ -190,6 +190,10 @@ namespace Lantean.QBTMudBlade.Components
protected async Task OnRowClickInternal(TableRowClickEventArgs<T> eventArgs)
{
if (eventArgs.Item is null)
{
return;
}
if (MultiSelection)
{
if (eventArgs.MouseEventArgs.CtrlKey)
@@ -236,7 +240,7 @@ namespace Lantean.QBTMudBlade.Components
//EqualityComparer<T>.Default.Equals(item, SelectedItem) ||
if (SelectedItems.Contains(item))
{
style += " background-color: var(--mud-palette-grey-dark); color: var(--mud-palette-grey-light) !important;";
style += " background-color: var(--mud-palette-gray-dark); color: var(--mud-palette-gray-light) !important;";
}
return style;
}

View File

@@ -17,8 +17,12 @@ namespace Lantean.QBTMudBlade.Components
[Parameter]
public bool Disabled { get; set; }
[Inject]
public ILogger<EnhancedErrorBoundary> Logger { get; set; } = default!;
protected override Task OnErrorAsync(Exception exception)
{
Logger.LogError(exception, exception.Message);
_exceptions.Add(exception);
if (Disabled)

View File

@@ -1,4 +1,4 @@
<MudList Clickable="true">
<MudList T="string">
<MudListItem OnClick="ClearErrors">Clear Errors</MudListItem>
<MudListItem OnClick="ClearErrorsAndResumeAsync">Clear Errors and Resume</MudListItem>
<MudDivider />

View File

@@ -1,20 +1,20 @@
<MudToolBar DisableGutters="true" Dense="true">
<MudIconButton Icon="@Icons.Material.Filled.DriveFileRenameOutline" OnClick="RenameFile" Title="Rename" />
<MudToolBar Gutters="false" Dense="true">
<MudIconButton Icon="@Icons.Material.Filled.DriveFileRenameOutline" OnClick="RenameFile" title="Rename" />
<MudDivider Vertical="true" />
<MudIconButton Icon="@Icons.Material.Outlined.ViewColumn" Color="Color.Inherit" OnClick="ColumnOptions" Title="Choose Columns" />
<MudIconButton Icon="@Icons.Material.Outlined.ViewColumn" Color="Color.Inherit" OnClick="ColumnOptions" title="Choose Columns" />
<MudDivider Vertical="true" />
<MudMenu Icon="@Icons.Material.Outlined.FileDownloadOff" Label="Do Not Download" AnchorOrigin="Origin.BottomLeft" TransformOrigin="Origin.TopLeft" title="Do Not Download">
<MudMenuItem OnClick="DoNotDownloadLessThan100PercentAvailability" OnTouch="DoNotDownloadLessThan100PercentAvailability">Less Than 100% Availability</MudMenuItem>
<MudMenuItem OnClick="DoNotDownloadLessThan80PercentAvailability" OnTouch="DoNotDownloadLessThan80PercentAvailability">Less than 80% Availability</MudMenuItem>
<MudMenuItem OnClick="DoNotDownloadCurrentlyFilteredFiles" OnTouch="NormalPriorityCurrentlyFilteredFiles">Currently Filtered Files</MudMenuItem>
<MudMenuItem OnClick="DoNotDownloadLessThan100PercentAvailability">Less Than 100% Availability</MudMenuItem>
<MudMenuItem OnClick="DoNotDownloadLessThan80PercentAvailability">Less than 80% Availability</MudMenuItem>
<MudMenuItem OnClick="DoNotDownloadCurrentlyFilteredFiles">Currently Filtered Files</MudMenuItem>
</MudMenu>
<MudMenu Icon="@Icons.Material.Outlined.FileDownload" Label="Normal Priority" AnchorOrigin="Origin.BottomLeft" TransformOrigin="Origin.TopLeft" title="Download">
<MudMenuItem OnClick="NormalPriorityLessThan100PercentAvailability" OnTouch="NormalPriorityLessThan100PercentAvailability">Less Than 100% Availability</MudMenuItem>
<MudMenuItem OnClick="NormalPriorityLessThan80PercentAvailability" OnTouch="NormalPriorityLessThan80PercentAvailability">Less than 80% Availability</MudMenuItem>
<MudMenuItem OnClick="NormalPriorityCurrentlyFilteredFiles" OnTouch="NormalPriorityCurrentlyFilteredFiles">Currently Filtered Files</MudMenuItem>
<MudMenuItem OnClick="NormalPriorityLessThan100PercentAvailability">Less Than 100% Availability</MudMenuItem>
<MudMenuItem OnClick="NormalPriorityLessThan80PercentAvailability">Less than 80% Availability</MudMenuItem>
<MudMenuItem OnClick="NormalPriorityCurrentlyFilteredFiles">Currently Filtered Files</MudMenuItem>
</MudMenu>
<MudIconButton Icon="@Icons.Material.Outlined.FilterList" OnClick="ShowFilterDialog" Title="Filter" />
<MudIconButton Icon="@Icons.Material.Outlined.FilterListOff" OnClick="RemoveFilter" Title="Remove Filter" />
<MudIconButton Icon="@Icons.Material.Outlined.FilterList" OnClick="ShowFilterDialog" title="Filter" />
<MudIconButton Icon="@Icons.Material.Outlined.FilterListOff" OnClick="RemoveFilter" title="Remove Filter" />
<MudSpacer />
<MudTextField T="string" Value="SearchText" ValueChanged="SearchTextChanged" Immediate="true" DebounceInterval="500" Placeholder="Filter file list" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Search" IconSize="Size.Medium" Class="mt-0"></MudTextField>
</MudToolBar>

View File

@@ -89,7 +89,7 @@ namespace Lantean.QBTMudBlade.Components
var result = await DialogService.ShowAsync<FilterOptionsDialog<ContentItem>>("Filters", parameters, DialogHelper.FormDialogOptions);
var dialogResult = await result.Result;
if (dialogResult.Canceled)
if (dialogResult is null || dialogResult.Canceled || dialogResult.Data is null)
{
return;
}

View File

@@ -1,4 +1,4 @@
<MudMenu Icon="@Icons.Material.Filled.MoreVert" Color="Color.Inherit" Dense="true" AnchorOrigin="Origin.BottomLeft" TransformOrigin="Origin.TopLeft">
<MudMenu Icon="@Icons.Material.Filled.MoreVert" Color="Color.Inherit" Dense="true" AnchorOrigin="Origin.BottomRight" TransformOrigin="Origin.TopLeft">
<MudMenuItem Icon="@Icons.Material.Filled.PieChart" Href="/statistics">Statistics</MudMenuItem>
<MudMenuItem Icon="@Icons.Material.Filled.Search" Href="/search">Search</MudMenuItem>
<MudMenuItem Icon="@Icons.Material.Filled.RssFeed" Href="/rss">RSS</MudMenuItem>
@@ -6,8 +6,8 @@
<MudMenuItem Icon="@Icons.Material.Filled.DisabledByDefault" Href="/blocks">Blocked IPs</MudMenuItem>
<MudDivider />
<MudMenuItem Icon="@Icons.Material.Filled.Settings" Href="/settings">Settings</MudMenuItem>
<MudMenuItem Icon="@Icons.Material.Filled.Undo" OnClick="ResetWebUI" OnTouch="ResetWebUI">Reset Web UI</MudMenuItem>
<MudMenuItem Icon="@Icons.Material.Filled.Undo" OnClick="ResetWebUI">Reset Web UI</MudMenuItem>
<MudDivider />
<MudMenuItem Icon="@Icons.Material.Filled.Logout" OnClick="Logout" OnTouch="Logout">Logout</MudMenuItem>
<MudMenuItem Icon="@Icons.Material.Filled.ExitToApp" OnClick="Exit" OnTouch="Exit">Exit qBittorrent</MudMenuItem>
<MudMenuItem Icon="@Icons.Material.Filled.Logout" OnClick="Logout">Logout</MudMenuItem>
<MudMenuItem Icon="@Icons.Material.Filled.ExitToApp" OnClick="Exit">Exit qBittorrent</MudMenuItem>
</MudMenu>

View File

@@ -289,7 +289,7 @@
</MudItem>
<MudItem xs="12">
<MudText>Supported parameters (case sensitive):</MudText>
<MudList>
<MudList T="string" ReadOnly="true">
<MudListItem>%N: Torrent name</MudListItem>
<MudListItem>%L: Category</MudListItem>
<MudListItem>%G: Tags (separated by comma)</MudListItem>

View File

@@ -20,7 +20,7 @@ namespace Lantean.QBTMudBlade.Components
[Parameter]
public bool Active { get; set; }
[CascadingParameter]
[CascadingParameter(Name = "RefreshInterval")]
public int RefreshInterval { get; set; }
[CascadingParameter]

View File

@@ -51,9 +51,9 @@ namespace Lantean.QBTMudBlade.Components
}
else
{
downloadingColor = Theme.Palette.Success.ToString(MudBlazor.Utilities.MudColorOutputFormats.RGBA);
haveColor = Theme.Palette.Info.ToString(MudBlazor.Utilities.MudColorOutputFormats.RGBA);
borderColor = Theme.Palette.Black.ToString(MudBlazor.Utilities.MudColorOutputFormats.RGBA);
downloadingColor = Theme.PaletteLight.Success.ToString(MudBlazor.Utilities.MudColorOutputFormats.RGBA);
haveColor = Theme.PaletteLight.Info.ToString(MudBlazor.Utilities.MudColorOutputFormats.RGBA);
borderColor = Theme.PaletteLight.Black.ToString(MudBlazor.Utilities.MudColorOutputFormats.RGBA);
}
await JSRuntime.RenderPiecesBar("progress", Hash, Pieces.Select(s => (int)s).ToArray(), downloadingColor, haveColor, borderColor);
}

View File

@@ -1,6 +1,6 @@
@if (RenderType == RenderType.Toolbar)
{
<MudToolBar Dense="true" DisableGutters="true" WrapContent="true">
<MudToolBar Dense="true" Gutters="false" WrapContent="true">
@ToolbarContent
</MudToolBar>
}
@@ -10,7 +10,7 @@ else if (RenderType == RenderType.ToolbarContents)
}
else if (RenderType == RenderType.MixedToolbar)
{
<MudToolBar Dense="true" DisableGutters="true" WrapContent="true">
<MudToolBar Dense="true" Gutters="false" WrapContent="true">
@MixedToolbarContent
</MudToolBar>
}
@@ -27,7 +27,7 @@ else if (RenderType == RenderType.InitialIconsOnly)
<MudDivider Vertical="true" />
}
<MudIconButton Title="@action.Text" Icon="@action.Icon" Color="action.Color" OnClick="action.Callback" Disabled="Disabled" />
<MudIconButton title="@action.Text" Icon="@action.Icon" Color="action.Color" OnClick="action.Callback" Disabled="Disabled" />
}
@Menu(Actions.Skip(5))
@@ -37,7 +37,7 @@ else if (RenderType == RenderType.Children)
var parent = Actions.FirstOrDefault(a => a.Text == ParentAction?.Text);
if (parent is not null)
{
<MudList Clickable="true">
<MudList T="string">
@foreach (var action in parent.Children)
{
@if (action.SeparatorBefore)
@@ -77,7 +77,7 @@ else
}
else
{
<MudIconButton Title="@action.Text" Icon="@action.Icon" Color="action.Color" OnClick="action.Callback" Disabled="Disabled" />
<MudIconButton title="@action.Text" Icon="@action.Icon" Color="action.Color" OnClick="action.Callback" Disabled="Disabled" />
}
}
else
@@ -115,7 +115,7 @@ else
}
else
{
<MudIconButton Title="@action.Text" Icon="@action.Icon" Color="action.Color" OnClick="action.Callback" Disabled="Disabled" />
<MudIconButton title="@action.Text" Icon="@action.Icon" Color="action.Color" OnClick="action.Callback" Disabled="Disabled" />
}
}
else
@@ -141,7 +141,7 @@ else
<MudDivider />
}
<MudMenuItem Icon="@action.Icon" IconColor="action.Color" OnClick="action.Callback" OnTouch="action.Callback" Disabled="Disabled">@action.Text</MudMenuItem>
<MudMenuItem Icon="@action.Icon" IconColor="action.Color" OnClick="action.Callback" Disabled="Disabled">@action.Text</MudMenuItem>
};
}
@@ -149,7 +149,7 @@ else
{
return __builder =>
{
<MudMenu Dense="true" AnchorOrigin="Origin.BottomLeft" TransformOrigin="Origin.TopLeft" Label="Actions" EndIcon="@Icons.Material.Filled.ArrowDropDown" @ref="ActionsMenu" Disabled="@(!Hashes.Any())">
<MudMenu Dense="true" AnchorOrigin="Origin.BottomLeft" TransformOrigin="Origin.TopLeft" Label="Actions" EndIcon="@Icons.Material.Filled.ArrowDropDown" @ref="ActionsMenu" Disabled="@(!Hashes.Any())" ActivationEvent="MouseEvent.LeftClick">
@foreach (var action in actions)
{
@if (action.SeparatorBefore)
@@ -159,14 +159,14 @@ else
if (!action.Children.Any())
{
<MudMenuItem Icon="@action.Icon" IconColor="action.Color" OnClick="action.Callback" OnTouch="action.Callback" Disabled="Disabled">
<MudMenuItem Icon="@action.Icon" IconColor="action.Color" OnClick="action.Callback" Disabled="Disabled">
@action.Text
</MudMenuItem>
}
else
{
<MudMenuItem Icon="@action.Icon" IconColor="action.Color" OnTouch="@(t => SubMenuTouch(action))" OnClick="@(t => SubMenuTouch(action))">
<MudMenu Dense="true" AnchorOrigin="Origin.TopRight" TransformOrigin="Origin.TopLeft" ActivationEvent="MouseEvent.MouseOver" Icon="@Icons.Material.Filled.ArrowDropDown" DisableElevation="true" DisableRipple="true" Class="sub-menu">
<MudMenuItem Icon="@action.Icon" IconColor="action.Color" OnClick="@(t => SubMenuTouch(action))">
<MudMenu Dense="true" AnchorOrigin="Origin.TopRight" TransformOrigin="Origin.TopLeft" ActivationEvent="MouseEvent.MouseOver" Icon="@Icons.Material.Filled.ArrowDropDown" Ripple="false" Class="sub-menu">
<ActivatorContent>
@action.Text
</ActivatorContent>

View File

@@ -66,7 +66,7 @@ namespace Lantean.QBTMudBlade.Components
{
new TorrentAction("start", "Start", Icons.Material.Filled.PlayArrow, Color.Success, CreateCallback(Resume)),
new TorrentAction("pause", "Pause", Icons.Material.Filled.Pause, Color.Warning, CreateCallback(Pause)),
new TorrentAction("forceStart", "Force start", Icons.Material.Filled.Pause, Color.Warning, CreateCallback(ForceStart)),
new TorrentAction("forceStart", "Force start", Icons.Material.Filled.Forward, Color.Warning, CreateCallback(ForceStart)),
new TorrentAction("delete", "Remove", Icons.Material.Filled.Delete, Color.Error, CreateCallback(Remove), separatorBefore: true),
new TorrentAction("setLocation", "Set location", Icons.Material.Filled.MyLocation, Color.Info, CreateCallback(SetLocation), separatorBefore: true),
new TorrentAction("rename", "Rename", Icons.Material.Filled.DriveFileRenameOutline, Color.Info, CreateCallback(Rename)),
@@ -248,7 +248,10 @@ namespace Lantean.QBTMudBlade.Components
protected async Task Copy(Func<Torrent, object?> selector)
{
await Copy(string.Join(Environment.NewLine, GetTorrents().Select(selector)));
ActionsMenu?.CloseMenu();
if (ActionsMenu is not null)
{
await ActionsMenu.CloseMenuAsync();
}
}
protected async Task Export()
@@ -487,7 +490,6 @@ namespace Lantean.QBTMudBlade.Components
}
else
{
if (actionState.Show is null || actionState.Show.Value)
{
var act = action with { };
@@ -499,6 +501,8 @@ namespace Lantean.QBTMudBlade.Components
{
act.IsChecked = actionState.IsChecked.Value;
}
yield return act;
}
}
}

View File

@@ -3,7 +3,7 @@
return;
}
<MudToolBar Dense="true" DisableGutters="true" WrapContent="true">
<MudToolBar Dense="true" Gutters="false" WrapContent="true">
@{
var (icon, color) = DisplayHelpers.GetStateIcon(Torrent.State);
}

View File

@@ -17,7 +17,7 @@ namespace Lantean.QBTMudBlade.Components
[Parameter]
public bool Active { get; set; }
[CascadingParameter]
[CascadingParameter(Name = "RefreshInterval")]
public int RefreshInterval { get; set; }
[Inject]

View File

@@ -17,7 +17,7 @@ namespace Lantean.QBTMudBlade.Components
[Parameter, EditorRequired]
public string? Hash { get; set; }
[CascadingParameter]
[CascadingParameter(Name = "RefreshInterval")]
public int RefreshInterval { get; set; }
[Inject]

View File

@@ -9,11 +9,11 @@ namespace Lantean.QBTMudBlade
{
public static class DialogHelper
{
public static readonly DialogOptions FormDialogOptions = new() { CloseButton = true, MaxWidth = MaxWidth.Medium, ClassBackground = "background-blur", FullWidth = true };
public static readonly DialogOptions FormDialogOptions = new() { CloseButton = true, MaxWidth = MaxWidth.Medium, BackgroundClass = "background-blur", FullWidth = true };
public static readonly DialogOptions NonBlurFormDialogOptions = new() { CloseButton = true, MaxWidth = MaxWidth.Medium, FullWidth = true };
public static readonly DialogOptions ConfirmDialogOptions = new() { ClassBackground = "background-blur", MaxWidth = MaxWidth.Small, FullWidth = true };
public static readonly DialogOptions ConfirmDialogOptions = new() { BackgroundClass = "background-blur", MaxWidth = MaxWidth.Small, FullWidth = true };
public static readonly DialogOptions NonBlurConfirmDialogOptions = new() { MaxWidth = MaxWidth.Small, FullWidth = true };
@@ -23,7 +23,7 @@ namespace Lantean.QBTMudBlade
{
var result = await dialogService.ShowAsync<AddTorrentFileDialog>("Upload local torrent", FormDialogOptions);
var dialogResult = await result.Result;
if (dialogResult.Canceled)
if (dialogResult is null || dialogResult.Canceled || dialogResult.Data is null)
{
return;
}
@@ -69,7 +69,7 @@ namespace Lantean.QBTMudBlade
{
var result = await dialogService.ShowAsync<AddTorrentLinkDialog>("Download from URLs", FormDialogOptions);
var dialogResult = await result.Result;
if (dialogResult.Canceled)
if (dialogResult is null || dialogResult.Canceled || dialogResult.Data is null)
{
return;
}
@@ -104,13 +104,13 @@ namespace Lantean.QBTMudBlade
};
var reference = await dialogService.ShowAsync<DeleteDialog>($"Remove torrent{(hashes.Length == 1 ? "" : "s")}?", parameters, ConfirmDialogOptions);
var result = await reference.Result;
if (result.Canceled)
var dialogResult = await reference.Result;
if (dialogResult is null || dialogResult.Canceled || dialogResult.Data is null)
{
return;
}
await apiClient.DeleteTorrents(hashes, (bool)result.Data);
await apiClient.DeleteTorrents(hashes, (bool)dialogResult.Data);
}
public static async Task InvokeRenameFilesDialog(this IDialogService dialogService, IApiClient apiClient, string hash)
@@ -121,13 +121,13 @@ namespace Lantean.QBTMudBlade
public static async Task<string?> ShowAddCategoryDialog(this IDialogService dialogService, IApiClient apiClient)
{
var reference = await dialogService.ShowAsync<AddCategoryDialog>("New Category", NonBlurFormDialogOptions);
var result = await reference.Result;
if (result.Canceled)
var dialogResult = await reference.Result;
if (dialogResult is null || dialogResult.Canceled || dialogResult.Data is null)
{
return null;
}
var category = (Category)result.Data;
var category = (Category)dialogResult.Data;
await apiClient.AddCategory(category.Name, category.SavePath);
@@ -136,15 +136,15 @@ namespace Lantean.QBTMudBlade
public static async Task<HashSet<string>?> ShowAddTagsDialog(this IDialogService dialogService, IApiClient apiClient)
{
var dialogReference = await dialogService.ShowAsync<AddTagDialog>("Add Tags", NonBlurFormDialogOptions);
var result = await dialogReference.Result;
var reference = await dialogService.ShowAsync<AddTagDialog>("Add Tags", NonBlurFormDialogOptions);
var dialogResult = await reference.Result;
if (result.Canceled)
if (dialogResult is null || dialogResult.Canceled || dialogResult.Data is null)
{
return null;
}
var tags = (HashSet<string>)result.Data;
var tags = (HashSet<string>)dialogResult.Data;
return tags;
}
@@ -158,7 +158,7 @@ namespace Lantean.QBTMudBlade
var result = await dialogService.ShowAsync<ConfirmDialog>(title, parameters, ConfirmDialogOptions);
var dialogResult = await result.Result;
return !dialogResult.Canceled;
return dialogResult is not null && !dialogResult.Canceled;
}
public static async Task ShowConfirmDialog(this IDialogService dialogService, string title, string content, Func<Task> onSuccess)
@@ -170,7 +170,7 @@ namespace Lantean.QBTMudBlade
var result = await dialogService.ShowAsync<ConfirmDialog>(title, parameters, ConfirmDialogOptions);
var dialogResult = await result.Result;
if (dialogResult.Canceled)
if (dialogResult is null || dialogResult.Canceled || dialogResult.Data is null)
{
return;
}
@@ -198,7 +198,7 @@ namespace Lantean.QBTMudBlade
var result = await dialogService.ShowAsync<SingleFieldDialog<T>>(title, parameters, FormDialogOptions);
var dialogResult = await result.Result;
if (dialogResult.Canceled)
if (dialogResult is null || dialogResult.Canceled || dialogResult.Data is null)
{
return;
}
@@ -217,7 +217,7 @@ namespace Lantean.QBTMudBlade
var result = await dialogService.ShowAsync<SliderFieldDialog<long>>("Download Rate", parameters, FormDialogOptions);
var dialogResult = await result.Result;
if (dialogResult.Canceled)
if (dialogResult is null || dialogResult.Canceled || dialogResult.Data is null)
{
return;
}
@@ -236,7 +236,7 @@ namespace Lantean.QBTMudBlade
var result = await dialogService.ShowAsync<SliderFieldDialog<long>>("Upload Rate", parameters, FormDialogOptions);
var dialogResult = await result.Result;
if (dialogResult.Canceled)
if (dialogResult is null || dialogResult.Canceled || dialogResult.Data is null)
{
return;
}
@@ -255,7 +255,7 @@ namespace Lantean.QBTMudBlade
var result = await dialogService.ShowAsync<SliderFieldDialog<float>>("Upload Rate", parameters, FormDialogOptions);
var dialogResult = await result.Result;
if (dialogResult.Canceled)
if (dialogResult is null || dialogResult.Canceled || dialogResult.Data is null)
{
return;
}
@@ -273,7 +273,7 @@ namespace Lantean.QBTMudBlade
var result = await dialogService.ShowAsync<FilterOptionsDialog<T>>("Filters", parameters, FormDialogOptions);
var dialogResult = await result.Result;
if (dialogResult.Canceled)
if (dialogResult is null || dialogResult.Canceled || dialogResult.Data is null)
{
return null;
}
@@ -291,13 +291,13 @@ namespace Lantean.QBTMudBlade
};
var reference = await dialogService.ShowAsync<ColumnOptionsDialog<T>>("Column Options", parameters, FormDialogOptions);
var result = await reference.Result;
if (result.Canceled)
var dialogResult = await reference.Result;
if (dialogResult is null || dialogResult.Canceled || dialogResult.Data is null)
{
return default;
}
return ((HashSet<string>, Dictionary<string, int?>))result.Data;
return ((HashSet<string>, Dictionary<string, int?>))dialogResult.Data;
}
public static async Task InvokeRssRulesDialog(this IDialogService dialogService)

View File

@@ -14,7 +14,7 @@
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.6" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="8.0.6" PrivateAssets="all" />
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
<PackageReference Include="MudBlazor" Version="6.20.0" />
<PackageReference Include="MudBlazor" Version="7.0.0-rc.2" />
<PackageReference Include="MudBlazor.ThemeManager" Version="1.1.0" />
</ItemGroup>

View File

@@ -1,7 +1,7 @@
@inherits LayoutComponentBase
@layout LoggedInLayout
<MudDrawer Open="DrawerOpen" ClipMode="DrawerClipMode.Always" Elevation="2" DisableOverlay="true">
<MudDrawer Open="DrawerOpen" ClipMode="DrawerClipMode.Always" Elevation="2" Overlay="false">
<TorrentsListNav Torrents="Torrents" SelectedTorrent="@SelectedTorrent" SortDirection="SortDirection" SortColumn="@SortColumn" />
</MudDrawer>
<MudMainContent>

View File

@@ -1,7 +1,7 @@
@inherits LayoutComponentBase
@layout LoggedInLayout
<MudDrawer Open="DrawerOpen" ClipMode="DrawerClipMode.Always" Elevation="2" DisableOverlay="true">
<MudDrawer Open="DrawerOpen" ClipMode="DrawerClipMode.Always" Elevation="2" Overlay="false">
<FiltersNav CategoryChanged="CategoryChanged" StatusChanged="StatusChanged" TagChanged="TagChanged" TrackerChanged="TrackerChanged" />
</MudDrawer>
<MudMainContent>

View File

@@ -5,6 +5,7 @@
<MudThemeProvider @ref="MudThemeProvider" @bind-IsDarkMode="IsDarkMode" Theme="Theme" />
<MudDialogProvider />
<MudSnackbarProvider />
<MudPopoverProvider />
<PageTitle>qBittorrent Web UI</PageTitle>

View File

@@ -1,7 +1,7 @@
@inherits LayoutComponentBase
@layout LoggedInLayout
<MudDrawer Open="DrawerOpen" ClipMode="DrawerClipMode.Always" Elevation="2" DisableOverlay="true">
<MudDrawer Open="DrawerOpen" ClipMode="DrawerClipMode.Always" Elevation="2" Overlay="false">
<MudNavMenu>
<MudNavLink Icon="@(Icons.Material.Outlined.NavigateBefore)" OnClick="NavigateBack">Back</MudNavLink>
<MudDivider />

View File

@@ -0,0 +1,16 @@
namespace Lantean.QBTMudBlade.Models
{
public class LogForm
{
public bool Normal => SelectedTypes.Contains("Normal");
public bool Info => SelectedTypes.Contains("Info");
public bool Warning => SelectedTypes.Contains("Warning");
public bool Critical => SelectedTypes.Contains("Critical");
public int? LastKnownId { get; set; }
public IEnumerable<string> SelectedTypes { get; set; } = new HashSet<string>();
public string? Criteria { get; set; }
}
}

View File

@@ -0,0 +1,16 @@
using System.ComponentModel.DataAnnotations;
using System.Diagnostics.CodeAnalysis;
namespace Lantean.QBTMudBlade.Models
{
public class LoginForm
{
[Required]
[NotNull]
public string? Username { get; set; }
[Required]
[NotNull]
public string? Password { get; set; }
}
}

View File

@@ -0,0 +1,11 @@
namespace Lantean.QBTMudBlade.Models
{
public class SearchForm
{
public string? SearchText { get; set; }
public string SelectedPlugin { get; set; } = "all";
public string SelectedCategory { get; set; } = "all";
}
}

View File

@@ -1,16 +1,59 @@
@page "/blocks"
@layout OtherLayout
<MudToolBar DisableGutters="true" Dense="true">
<MudToolBar Gutters="false" Dense="true">
@if (!DrawerOpen)
{
<MudIconButton Icon="@Icons.Material.Outlined.NavigateBefore" OnClick="NavigateBack" Title="Back to torrent list" />
<MudIconButton Icon="@Icons.Material.Outlined.NavigateBefore" OnClick="NavigateBack" title="Back to torrent list" />
<MudDivider Vertical="true" />
}
<MudDivider Vertical="true" />
<MudText Class="pl-5 no-wrap">Blocked IPs</MudText>
</MudToolBar>
<MudContainer MaxWidth="MaxWidth.ExtraExtraLarge" Class="details-tab-contents">
<p>Coming soon.</p>
</MudContainer>
<MudCard Elevation="1" Class="ml-4 mr-4 mb-4">
<MudCardContent>
<EditForm Model="Model" OnSubmit="Submit">
<MudGrid>
<MudItem md="10">
<MudTextField T="string" Label="Criteria" @bind-Value="Model.Criteria" ShrinkLabel Variant="Variant.Outlined" />
</MudItem>
<MudItem md="2">
<MudButton ButtonType="ButtonType.Submit" FullWidth="true" Color="Color.Primary" EndIcon="@Icons.Material.Filled.Search" Variant="Variant.Filled" Class="mt-6">Filter</MudButton>
</MudItem>
</MudGrid>
</EditForm>
</MudCardContent>
</MudCard>
<MudTable Items="Results"
T="Lantean.QBitTorrentClient.Models.PeerLog"
Hover="true"
FixedHeader="true"
HeaderClass="table-head-bordered"
Dense="true"
Breakpoint="Breakpoint.None"
Bordered="true"
Square="true"
LoadingProgressColor="Color.Info"
HorizontalScrollbar="true"
Virtualize="true"
AllowUnsorted="false"
SelectOnRowClick="false"
Class="search-list"
RowClassFunc="RowClass">
<HeaderContent>
<MudTh>Id</MudTh>
<MudTh>Message</MudTh>
<MudTh>Timestamp</MudTh>
<MudTh>Status</MudTh>
<MudTh>Reason</MudTh>
</HeaderContent>
<RowTemplate>
<MudTd DataLabel="Id">@context.Id</MudTd>
<MudTd DataLabel="IP">@context.IPAddress</MudTd>
<MudTd DataLabel="Timestamp">@DisplayHelpers.DateTime(context.Timestamp)</MudTd>
<MudTd DataLabel="Status">@(context.Blocked ? "Blocked" : "Banned")</MudTd>
<MudTd DataLabel="Reason">@context.Reason</MudTd>
</RowTemplate>
</MudTable>

View File

@@ -1,12 +1,21 @@
using Lantean.QBitTorrentClient;
using Blazored.LocalStorage;
using Lantean.QBitTorrentClient;
using Lantean.QBTMudBlade.Models;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;
using MudBlazor;
using System.Net;
namespace Lantean.QBTMudBlade.Pages
{
public partial class Blocks
public partial class Blocks : IAsyncDisposable
{
private readonly bool _refreshEnabled = true;
private const string _selectedTypesStorageKey = "Blocks.SelectedTypes";
private readonly CancellationTokenSource _timerCancellationToken = new();
private bool _disposedValue;
[Inject]
protected IApiClient ApiClient { get; set; } = default!;
@@ -16,24 +25,129 @@ namespace Lantean.QBTMudBlade.Pages
[Inject]
protected NavigationManager NavigationManager { get; set; } = default!;
[CascadingParameter]
public MainData? MainData { get; set; }
[Inject]
protected ILocalStorageService LocalStorage { get; set; } = default!;
[CascadingParameter(Name = "DrawerOpen")]
public bool DrawerOpen { get; set; }
[Parameter]
public string? Hash { get; set; }
protected LogForm Model { get; set; } = new LogForm();
protected int ActiveTab { get; set; } = 0;
protected List<QBitTorrentClient.Models.PeerLog>? Results { get; private set; }
protected int RefreshInterval => MainData?.ServerState.RefreshInterval ?? 1500;
protected MudSelect<string>? CategoryMudSelect { get; set; }
protected ServerState? ServerState => MainData?.ServerState;
protected override async Task OnInitializedAsync()
{
var selectedTypes = await LocalStorage.GetItemAsync<IEnumerable<string>>(_selectedTypesStorageKey);
if (selectedTypes is not null)
{
Model.SelectedTypes = selectedTypes;
}
else
{
Model.SelectedTypes = ["Normal"];
}
await DoSearch();
}
protected void NavigateBack()
{
NavigationManager.NavigateTo("/");
}
protected async Task SelectedValuesChanged(IEnumerable<string> values)
{
Model.SelectedTypes = values;
await LocalStorage.SetItemAsync(_selectedTypesStorageKey, Model.SelectedTypes);
}
protected static string GenerateSelectedText(List<string> values)
{
if (values.Count == 4)
{
return "All";
}
return $"{values.Count} selected";
}
protected Task Submit(EditContext editContext)
{
return DoSearch();
}
private async Task DoSearch()
{
var results = await ApiClient.GetPeerLog(Model.LastKnownId);
if (results.Count > 0)
{
Results ??= [];
Results.AddRange(results);
Model.LastKnownId = results[^1].Id;
}
}
protected static string RowClass(QBitTorrentClient.Models.PeerLog log, int index)
{
return $"log-{(log.Blocked ? "critical" : "normal")}";
}
public async ValueTask DisposeAsync()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
await DisposeAsync(disposing: true);
GC.SuppressFinalize(this);
}
protected virtual async Task DisposeAsync(bool disposing)
{
if (!_disposedValue)
{
if (disposing)
{
_timerCancellationToken.Cancel();
_timerCancellationToken.Dispose();
await Task.CompletedTask;
}
_disposedValue = true;
}
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (!_refreshEnabled)
{
return;
}
if (!firstRender)
{
return;
}
using (var timer = new PeriodicTimer(TimeSpan.FromMilliseconds(1500)))
{
while (!_timerCancellationToken.IsCancellationRequested && await timer.WaitForNextTickAsync())
{
try
{
await DoSearch();
}
catch (HttpRequestException exception) when (exception.StatusCode == HttpStatusCode.Forbidden || exception.StatusCode == HttpStatusCode.NotFound)
{
_timerCancellationToken.CancelIfNotDisposed();
return;
}
await InvokeAsync(StateHasChanged);
}
}
}
}
}

View File

@@ -1,10 +1,10 @@
@page "/details/{hash}"
@layout DetailsLayout
<MudToolBar DisableGutters="true" Dense="true">
<MudToolBar Gutters="false" Dense="true">
@if (!DrawerOpen)
{
<MudIconButton Icon="@Icons.Material.Outlined.NavigateBefore" OnClick="NavigateBack" Title="Back to torrent list" />
<MudIconButton Icon="@Icons.Material.Outlined.NavigateBefore" OnClick="NavigateBack" title="Back to torrent list" />
<MudDivider Vertical="true" />
}
@if (Hash is not null)
@@ -17,7 +17,7 @@
@if (ShowTabs)
{
<CascadingValue Value="RefreshInterval">
<CascadingValue Value="RefreshInterval" Name="RefreshInterval">
<MudTabs Elevation="2" ApplyEffectsToContainer="true" @bind-ActivePanelIndex="ActiveTab" KeepPanelsAlive="true" Border="true">
<MudTabPanel Text="General">
<GeneralTab Hash="@Hash" Active="@(ActiveTab == 0)" />

View File

@@ -1,16 +1,65 @@
@page "/log"
@layout OtherLayout
<MudToolBar DisableGutters="true" Dense="true">
<MudToolBar Gutters="false" Dense="true">
@if (!DrawerOpen)
{
<MudIconButton Icon="@Icons.Material.Outlined.NavigateBefore" OnClick="NavigateBack" Title="Back to torrent list" />
<MudIconButton Icon="@Icons.Material.Outlined.NavigateBefore" OnClick="NavigateBack" title="Back to torrent list" />
<MudDivider Vertical="true" />
}
<MudDivider Vertical="true" />
<MudText Class="pl-5 no-wrap">Execution Log</MudText>
</MudToolBar>
<MudContainer MaxWidth="MaxWidth.ExtraExtraLarge" Class="details-tab-contents">
<p>Coming soon.</p>
</MudContainer>
<MudCard Elevation="1" Class="ml-4 mr-4 mb-4">
<MudCardContent>
<EditForm Model="Model" OnSubmit="Submit">
<MudGrid>
<MudItem md="8">
<MudTextField T="string" Label="Criteria" @bind-Value="Model.Criteria" ShrinkLabel Variant="Variant.Outlined" />
</MudItem>
<MudItem md="2">
<MudSelect @ref="CategoryMudSelect" T="string" Label="Categories" SelectedValues="Model.SelectedTypes" SelectedValuesChanged="SelectedValuesChanged" ShrinkLabel Variant="Variant.Outlined" MultiSelection="true" MultiSelectionTextFunc="GenerateSelectedText" SelectAll="true">
<MudSelectItem Value="@("Normal")">Normal</MudSelectItem>
<MudSelectItem Value="@("Info")">Info</MudSelectItem>
<MudSelectItem Value="@("Warning")">Warning</MudSelectItem>
<MudSelectItem Value="@("Critical")">Critical</MudSelectItem>
</MudSelect>
</MudItem>
<MudItem md="2">
<MudButton ButtonType="ButtonType.Submit" FullWidth="true" Color="Color.Primary" EndIcon="@Icons.Material.Filled.Search" Variant="Variant.Filled" Class="mt-6">Filter</MudButton>
</MudItem>
</MudGrid>
</EditForm>
</MudCardContent>
</MudCard>
<MudTable Items="Results"
T="Lantean.QBitTorrentClient.Models.Log"
Hover="true"
FixedHeader="true"
HeaderClass="table-head-bordered"
Dense="true"
Breakpoint="Breakpoint.None"
Bordered="true"
Square="true"
LoadingProgressColor="Color.Info"
HorizontalScrollbar="true"
Virtualize="true"
AllowUnsorted="false"
SelectOnRowClick="false"
Class="search-list"
RowClassFunc="RowClass">
<HeaderContent>
<MudTh>Id</MudTh>
<MudTh>Message</MudTh>
<MudTh>Timestamp</MudTh>
<MudTh>Log Type</MudTh>
</HeaderContent>
<RowTemplate>
<MudTd DataLabel="Od">@context.Id</MudTd>
<MudTd DataLabel="Message">@context.Message</MudTd>
<MudTd DataLabel="Timestamp">@DisplayHelpers.DateTime(context.Timestamp)</MudTd>
<MudTd DataLabel="Log Type">@context.Type</MudTd>
</RowTemplate>
</MudTable>

View File

@@ -1,12 +1,21 @@
using Lantean.QBitTorrentClient;
using Blazored.LocalStorage;
using Lantean.QBitTorrentClient;
using Lantean.QBTMudBlade.Models;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;
using MudBlazor;
using System.Net;
namespace Lantean.QBTMudBlade.Pages
{
public partial class Log
public partial class Log : IAsyncDisposable
{
private readonly bool _refreshEnabled = true;
private const string _selectedTypesStorageKey = "Log.SelectedTypes";
private readonly CancellationTokenSource _timerCancellationToken = new();
private bool _disposedValue;
[Inject]
protected IApiClient ApiClient { get; set; } = default!;
@@ -16,24 +25,129 @@ namespace Lantean.QBTMudBlade.Pages
[Inject]
protected NavigationManager NavigationManager { get; set; } = default!;
[CascadingParameter]
public MainData? MainData { get; set; }
[Inject]
protected ILocalStorageService LocalStorage { get; set; } = default!;
[CascadingParameter(Name = "DrawerOpen")]
public bool DrawerOpen { get; set; }
[Parameter]
public string? Hash { get; set; }
protected LogForm Model { get; set; } = new LogForm();
protected int ActiveTab { get; set; } = 0;
protected List<QBitTorrentClient.Models.Log>? Results { get; private set; }
protected int RefreshInterval => MainData?.ServerState.RefreshInterval ?? 1500;
protected MudSelect<string>? CategoryMudSelect { get; set; }
protected ServerState? ServerState => MainData?.ServerState;
protected override async Task OnInitializedAsync()
{
var selectedTypes = await LocalStorage.GetItemAsync<IEnumerable<string>>(_selectedTypesStorageKey);
if (selectedTypes is not null)
{
Model.SelectedTypes = selectedTypes;
}
else
{
Model.SelectedTypes = ["Normal"];
}
await DoSearch();
}
protected void NavigateBack()
{
NavigationManager.NavigateTo("/");
}
protected async Task SelectedValuesChanged(IEnumerable<string> values)
{
Model.SelectedTypes = values;
await LocalStorage.SetItemAsync(_selectedTypesStorageKey, Model.SelectedTypes);
}
protected static string GenerateSelectedText(List<string> values)
{
if (values.Count == 4)
{
return "All";
}
return $"{values.Count} selected";
}
protected Task Submit(EditContext editContext)
{
return DoSearch();
}
private async Task DoSearch()
{
var results = await ApiClient.GetLog(Model.Normal, Model.Info, Model.Warning, Model.Critical, Model.LastKnownId);
if (results.Count > 0)
{
Results ??= [];
Results.AddRange(results);
Model.LastKnownId = results[^1].Id;
}
}
protected static string RowClass(QBitTorrentClient.Models.Log log, int index)
{
return $"log-{log.Type.ToString().ToLower()}";
}
public async ValueTask DisposeAsync()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
await DisposeAsync(disposing: true);
GC.SuppressFinalize(this);
}
protected virtual async Task DisposeAsync(bool disposing)
{
if (!_disposedValue)
{
if (disposing)
{
_timerCancellationToken.Cancel();
_timerCancellationToken.Dispose();
await Task.CompletedTask;
}
_disposedValue = true;
}
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (!_refreshEnabled)
{
return;
}
if (!firstRender)
{
return;
}
using (var timer = new PeriodicTimer(TimeSpan.FromMilliseconds(1500)))
{
while (!_timerCancellationToken.IsCancellationRequested && await timer.WaitForNextTickAsync())
{
try
{
await DoSearch();
}
catch (HttpRequestException exception) when (exception.StatusCode == HttpStatusCode.Forbidden || exception.StatusCode == HttpStatusCode.NotFound)
{
_timerCancellationToken.CancelIfNotDisposed();
return;
}
await InvokeAsync(StateHasChanged);
}
}
}
}
}

View File

@@ -1,8 +1,7 @@
using Lantean.QBitTorrentClient;
using Lantean.QBTMudBlade.Models;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics.CodeAnalysis;
using System.Net;
namespace Lantean.QBTMudBlade.Pages
@@ -15,7 +14,7 @@ namespace Lantean.QBTMudBlade.Pages
[Inject]
protected NavigationManager NavigationManager { get; set; } = default!;
protected LoginModel Model { get; set; } = new LoginModel();
protected LoginForm Model { get; set; } = new LoginForm();
protected string? ApiError { get; set; }
@@ -49,19 +48,8 @@ namespace Lantean.QBTMudBlade.Pages
#if DEBUG
protected override Task OnInitializedAsync()
{
return DoLogin("admin", "6K3mtPNnQ");
return DoLogin("admin", "STMeVwB22");
}
#endif
}
public class LoginModel
{
[Required]
[NotNull]
public string? Username { get; set; }
[Required]
[NotNull]
public string? Password { get; set; }
}
}

View File

@@ -3,7 +3,7 @@
<NavigationLock ConfirmExternalNavigation="@(UpdatePreferences is not null)" OnBeforeInternalNavigation="ValidateExit" />
<MudToolBar DisableGutters="true" Dense="true">
<MudToolBar Gutters="false" Dense="true">
@if (!DrawerOpen)
{
<MudIconButton Icon="@Icons.Material.Outlined.NavigateBefore" OnClick="NavigateBack" />

View File

@@ -1,10 +1,10 @@
@page "/rss"
@layout OtherLayout
<MudToolBar DisableGutters="true" Dense="true">
<MudToolBar Gutters="false" Dense="true">
@if (!DrawerOpen)
{
<MudIconButton Icon="@Icons.Material.Outlined.NavigateBefore" OnClick="NavigateBack" Title="Back to torrent list" />
<MudIconButton Icon="@Icons.Material.Outlined.NavigateBefore" OnClick="NavigateBack" title="Back to torrent list" />
<MudDivider Vertical="true" />
}
<MudDivider Vertical="true" />

View File

@@ -1,24 +1,25 @@
@page "/search"
@layout OtherLayout
<MudToolBar DisableGutters="true" Dense="true">
<MudToolBar Gutters="false" Dense="true">
@if (!DrawerOpen)
{
<MudIconButton Icon="@Icons.Material.Outlined.NavigateBefore" OnClick="NavigateBack" Title="Back to torrent list" />
<MudIconButton Icon="@Icons.Material.Outlined.NavigateBefore" OnClick="NavigateBack" title="Back to torrent list" />
<MudDivider Vertical="true" />
}
<MudDivider Vertical="true" />
<MudText Class="pl-5 no-wrap">Search</MudText>
</MudToolBar>
<MudCard Elevation="1" Class="ml-4 mr-4 mb-4 mt-4">
<MudCardContent Class="pt-0">
<MudCard Elevation="1" Class="ml-4 mr-4 mb-4">
<MudCardContent>
<EditForm Model="Model" OnValidSubmit="DoSearch">
<MudGrid>
<MudItem xs="12" md="6">
<MudTextField T="string" Label="Search" Value="SearchText" ValueChanged="SearchTextChanged" ShrinkLabel Variant="Variant.Outlined" />
<MudTextField T="string" Label="Criteria" @bind-Value="Model.SearchText" ShrinkLabel Variant="Variant.Outlined" />
</MudItem>
<MudItem md="2">
<MudSelect T="string" Label="Categories" Value="SelectedCategory" ValueChanged="SelectedCategoryChanged" ShrinkLabel Variant="Variant.Outlined">
<MudSelect T="string" Label="Categories" @bind-Value="Model.SelectedCategory" ShrinkLabel Variant="Variant.Outlined">
@foreach (var (value, name) in Categories)
{
<MudSelectItem Value="value">@name</MudSelectItem>
@@ -30,7 +31,7 @@
</MudSelect>
</MudItem>
<MudItem md="2">
<MudSelect T="string" Label="Plugins" Value="SelectedPlugin" ValueChanged="SelectedPluginChanged" ShrinkLabel Variant="Variant.Outlined">
<MudSelect T="string" Label="Plugins" @bind-Value="Model.SelectedPlugin" ShrinkLabel Variant="Variant.Outlined">
<MudSelectItem Value="@("all")">All</MudSelectItem>
<MudDivider />
@foreach (var (value, name) in Plugins)
@@ -40,10 +41,11 @@
</MudSelect>
</MudItem>
<MudItem md="2">
<MudButton OnClick="DoSearch" FullWidth="true" Variant="Variant.Outlined">@(_searchId is null ? "Search" : "Stop")</MudButton>
<MudButton ButtonType="ButtonType.Submit" FullWidth="true" Color="Color.Primary" EndIcon="@Icons.Material.Filled.Search" Variant="Variant.Filled" Class="mt-6">@(_searchId is null ? "Search" : "Stop")</MudButton>
</MudItem>
</MudGrid>
</EditForm>
</MudCardContent>
</MudCard>
@@ -62,7 +64,7 @@
Virtualize="true"
AllowUnsorted="false"
SelectOnRowClick="false"
Class="details-list">
Class="search-list">
<HeaderContent>
<MudTh>Name</MudTh>
<MudTh>Size</MudTh>

View File

@@ -1,6 +1,7 @@
using Lantean.QBitTorrentClient;
using Lantean.QBTMudBlade.Models;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;
using MudBlazor;
namespace Lantean.QBTMudBlade.Pages
@@ -33,21 +34,11 @@ namespace Lantean.QBTMudBlade.Pages
[Parameter]
public string? Hash { get; set; }
protected int ActiveTab { get; set; } = 0;
protected int RefreshInterval => MainData?.ServerState.RefreshInterval ?? 1500;
protected ServerState? ServerState => MainData?.ServerState;
protected string? SearchText { get; set; }
protected string SelectedPlugin { get; set; } = "all";
protected string SelectedCategory { get; set; } = "all";
protected SearchForm Model { get; set; } = new SearchForm();
protected Dictionary<string, string> Plugins => _plugins is null ? [] : _plugins.ToDictionary(a => a.Name, a => a.FullName);
protected Dictionary<string, string> Categories => GetCategories(SelectedPlugin);
protected Dictionary<string, string> Categories => GetCategories(Model.SelectedPlugin);
protected IEnumerable<QBitTorrentClient.Models.SearchResult>? Results => _searchResults?.Results;
@@ -97,21 +88,6 @@ namespace Lantean.QBTMudBlade.Pages
NavigationManager.NavigateTo("/");
}
protected void SearchTextChanged(string value)
{
SearchText = value;
}
protected void SelectedCategoryChanged(string value)
{
SelectedCategory = value;
}
protected void SelectedPluginChanged(string value)
{
SelectedPlugin = value;
}
private Dictionary<string, string> GetCategories(string plugin)
{
if (_plugins is null)
@@ -133,17 +109,17 @@ namespace Lantean.QBTMudBlade.Pages
return pluginItem.SupportedCategories.ToDictionary(a => a.Id, a => a.Name);
}
protected async Task DoSearch()
protected async Task DoSearch(EditContext editContext)
{
if (_searchId is null)
{
if (string.IsNullOrEmpty(SearchText))
if (string.IsNullOrEmpty(Model.SearchText))
{
return;
}
_searchResults = null;
_searchId = await ApiClient.StartSearch(SearchText, [SelectedPlugin], SelectedCategory);
_searchId = await ApiClient.StartSearch(Model.SearchText, [Model.SelectedPlugin], Model.SelectedCategory);
}
else
{

View File

@@ -1,10 +1,10 @@
@page "/statistics"
@layout OtherLayout
<MudToolBar DisableGutters="true" Dense="true">
<MudToolBar Gutters="false" Dense="true">
@if (!DrawerOpen)
{
<MudIconButton Icon="@Icons.Material.Outlined.NavigateBefore" OnClick="NavigateBack" Title="Back to torrent list" />
<MudIconButton Icon="@Icons.Material.Outlined.NavigateBefore" OnClick="NavigateBack" title="Back to torrent list" />
<MudDivider Vertical="true" />
}
<MudDivider Vertical="true" />

View File

@@ -22,13 +22,14 @@ namespace Lantean.QBTMudBlade.Pages
[CascadingParameter(Name = "DrawerOpen")]
public bool DrawerOpen { get; set; }
[CascadingParameter(Name = "RefreshInterval")]
public int RefreshInterval { get; set; }
[Parameter]
public string? Hash { get; set; }
protected int ActiveTab { get; set; } = 0;
protected int RefreshInterval => MainData?.ServerState.RefreshInterval ?? 1500;
protected ServerState? ServerState => MainData?.ServerState;
protected void NavigateBack()

View File

@@ -1,14 +1,14 @@
@page "/"
@layout ListLayout
<MudToolBar DisableGutters="true" Dense="true">
<MudIconButton Icon="@Icons.Material.Outlined.AddLink" OnClick="AddTorrentLink" Title="Add torrent link" />
<MudIconButton Icon="@Icons.Material.Outlined.AddCircle" OnClick="AddTorrentFile" Title="Add torrent file" />
<MudToolBar Gutters="false" Dense="true">
<MudIconButton Icon="@Icons.Material.Outlined.AddLink" OnClick="AddTorrentLink" title="Add torrent link" />
<MudIconButton Icon="@Icons.Material.Outlined.AddCircle" OnClick="AddTorrentFile" title="Add torrent file" />
<MudDivider Vertical="true" />
<TorrentActions RenderType="RenderType.InitialIconsOnly" Hashes="GetSelectedTorrents()" />
<MudDivider Vertical="true" />
<MudIconButton Icon="@Icons.Material.Outlined.Info" Color="Color.Inherit" Disabled="@(!ToolbarButtonsEnabled)" OnClick="ShowTorrent" Title="View torrent details" />
<MudIconButton Icon="@Icons.Material.Outlined.ViewColumn" Color="Color.Inherit" OnClick="ColumnOptions" Title="Choose Columns" />
<MudIconButton Icon="@Icons.Material.Outlined.Info" Color="Color.Inherit" Disabled="@(!ToolbarButtonsEnabled)" OnClick="ShowTorrent" title="View torrent details" />
<MudIconButton Icon="@Icons.Material.Outlined.ViewColumn" Color="Color.Inherit" OnClick="ColumnOptions" title="Choose Columns" />
<MudSpacer />
<MudTextField Value="SearchText" TextChanged="SearchTextChanged" Immediate="true" DebounceInterval="1000" Placeholder="Filter torrent list" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Search" IconSize="Size.Medium" Class="mt-0"></MudTextField>
</MudToolBar>

View File

@@ -171,3 +171,23 @@ td.no-wrap {
height: calc(100vh - 200px);
overflow: auto;
}
.search-list .mud-table-container {
height: calc(100vh - 260px);
}
tr.log-normal td {
color: var(--mud-palette-text-primary) !important;
}
tr.log-info td {
color: var(--mud-palette-info) !important;
}
tr.log-warning td {
color: var(--mud-palette-warning) !important;
}
tr.log-critical td {
color: var(--mud-palette-error) !important;
}

View File

@@ -887,10 +887,10 @@ namespace Lantean.QBitTorrentClient
{
var content = new FormUrlEncodedBuilder()
.AddAllOrPipeSeparated("hashes", all, hashes)
.Add("enable", value)
.Add("value", value)
.ToFormUrlEncodedContent();
var response = await _httpClient.PostAsync("torrents/setFOrceStart", content);
var response = await _httpClient.PostAsync("torrents/setForceStart", content);
response.EnsureSuccessStatusCode();
}
@@ -1104,17 +1104,31 @@ namespace Lantean.QBitTorrentClient
}
private async Task<IReadOnlyList<T>> GetJsonList<T>(HttpContent content)
{
try
{
var items = await GetJson<IEnumerable<T>>(content);
return items.ToList().AsReadOnly();
}
catch
{
return [];
}
}
private async Task<IReadOnlyDictionary<TKey, TValue>> GetJsonDictionary<TKey, TValue>(HttpContent content) where TKey : notnull
{
try
{
var items = await GetJson<IDictionary<TKey, TValue>>(content);
return items.AsReadOnly();
}
catch
{
return new Dictionary<TKey, TValue>().AsReadOnly();
}
}
}
}