mirror of
https://github.com/Artemis-RGB/Artemis
synced 2026-11-12 11:56:30 +00:00
Plugins - Fixed cancelling prerequisite installation
Plugins - Tweaked prerequisite installation popup size Account settings - Added personal access token management Workshop - Added plugin loading, installing and removal
This commit is contained in:
1 parent
21b8112de5
commit
f2c8de1746
33 files changed
+516
-113
No files matched your search
@@ -0,0 +1,19 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:account="clr-namespace:Artemis.UI.Screens.Settings.Account"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Artemis.UI.Screens.Settings.Account.CreatePersonalAccessTokenView"
|
||||
x:DataType="account:CreatePersonalAccessTokenViewModel">
|
||||
<StackPanel Spacing="5" Width="300">
|
||||
<Label>Description</Label>
|
||||
<TextBox Text="{CompiledBinding Description}"/>
|
||||
<Label>Expiration date</Label>
|
||||
<CalendarDatePicker SelectedDate="{CompiledBinding ExpirationDate}"
|
||||
DisplayDateStart="{CompiledBinding StartDate}"
|
||||
DisplayDateEnd="{CompiledBinding EndDate}"
|
||||
HorizontalAlignment="Stretch"/>
|
||||
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,14 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Screens.Settings.Account;
|
||||
|
||||
public partial class CreatePersonalAccessTokenView : ReactiveUserControl<CreatePersonalAccessTokenViewModel>
|
||||
{
|
||||
public CreatePersonalAccessTokenView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
using System;
|
||||
using System.Reactive;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Artemis.UI.Shared;
|
||||
using Artemis.UI.Shared.Services;
|
||||
using Artemis.WebClient.Workshop.Services;
|
||||
using PropertyChanged.SourceGenerator;
|
||||
using ReactiveUI;
|
||||
using ReactiveUI.Validation.Extensions;
|
||||
|
||||
namespace Artemis.UI.Screens.Settings.Account;
|
||||
|
||||
public partial class CreatePersonalAccessTokenViewModel : ContentDialogViewModelBase
|
||||
{
|
||||
private readonly IUserManagementService _userManagementService;
|
||||
private readonly IWindowService _windowService;
|
||||
[Notify] private string _description = string.Empty;
|
||||
[Notify] private DateTime _expirationDate = DateTime.Today.AddDays(180);
|
||||
|
||||
public CreatePersonalAccessTokenViewModel(IUserManagementService userManagementService, IWindowService windowService)
|
||||
{
|
||||
_userManagementService = userManagementService;
|
||||
_windowService = windowService;
|
||||
Submit = ReactiveCommand.CreateFromTask(ExecuteSubmit, ValidationContext.Valid);
|
||||
|
||||
this.ValidationRule(vm => vm.Description, e => e != null, "You must specify a description");
|
||||
this.ValidationRule(vm => vm.Description, e => e == null || e.Length >= 10, "You must specify a description of at least 10 characters");
|
||||
this.ValidationRule(vm => vm.Description, e => e == null || e.Length <= 150, "You must specify a description of less than 150 characters");
|
||||
this.ValidationRule(vm => vm.ExpirationDate, e => e >= DateTime.Today.AddDays(1), "Expiration date must be at least 24 hours from now");
|
||||
}
|
||||
|
||||
public DateTime StartDate => DateTime.Today.AddDays(1);
|
||||
public DateTime EndDate => DateTime.Today.AddDays(365);
|
||||
public ReactiveCommand<Unit, Unit> Submit { get; }
|
||||
|
||||
private async Task ExecuteSubmit(CancellationToken cts)
|
||||
{
|
||||
string result = await _userManagementService.CreatePersonAccessToken(Description, ExpirationDate, cts);
|
||||
await _windowService.CreateContentDialog()
|
||||
.WithTitle("Personal Access Token")
|
||||
.WithViewModel(out PersonalAccessTokenViewModel _, result)
|
||||
.WithFullScreen()
|
||||
.ShowAsync();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:account="clr-namespace:Artemis.UI.Screens.Settings.Account"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Artemis.UI.Screens.Settings.Account.PersonalAccessTokenView"
|
||||
x:DataType="account:PersonalAccessTokenViewModel">
|
||||
<StackPanel>
|
||||
<TextBlock>
|
||||
Your token has been created, please copy it now as you cannot view it again later.
|
||||
</TextBlock>
|
||||
<TextBox Margin="0 10 0 0"
|
||||
VerticalAlignment="Top"
|
||||
TextWrapping="Wrap"
|
||||
IsReadOnly="True"
|
||||
Text="{CompiledBinding Token, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,14 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Screens.Settings.Account;
|
||||
|
||||
public partial class PersonalAccessTokenView : ReactiveUserControl<PersonalAccessTokenViewModel>
|
||||
{
|
||||
public PersonalAccessTokenView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using Artemis.UI.Shared;
|
||||
|
||||
namespace Artemis.UI.Screens.Settings.Account;
|
||||
|
||||
public class PersonalAccessTokenViewModel : ContentDialogViewModelBase
|
||||
{
|
||||
public string Token { get; }
|
||||
|
||||
public PersonalAccessTokenViewModel(string token)
|
||||
{
|
||||
Token = token;
|
||||
}
|
||||
}
|
||||
@@ -5,36 +5,41 @@
|
||||
xmlns:settings="clr-namespace:Artemis.UI.Screens.Settings"
|
||||
xmlns:asyncImageLoader="clr-namespace:AsyncImageLoader;assembly=AsyncImageLoader.Avalonia"
|
||||
xmlns:loaders="clr-namespace:AsyncImageLoader.Loaders;assembly=AsyncImageLoader.Avalonia"
|
||||
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||
xmlns:models="clr-namespace:Artemis.WebClient.Workshop.Models;assembly=Artemis.WebClient.Workshop"
|
||||
xmlns:converters="clr-namespace:Artemis.UI.Converters"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="800"
|
||||
x:Class="Artemis.UI.Screens.Settings.AccountTabView"
|
||||
x:DataType="settings:AccountTabViewModel">
|
||||
<UserControl.Resources>
|
||||
<converters:DateTimeConverter x:Key="DateTimeConverter" />
|
||||
</UserControl.Resources>
|
||||
<Panel>
|
||||
<StackPanel IsVisible="{CompiledBinding !IsLoggedIn^}" Margin="0 50 0 0">
|
||||
<StackPanel.Styles>
|
||||
<Styles>
|
||||
<Style Selector="TextBlock">
|
||||
<Setter Property="TextAlignment" Value="Center"></Setter>
|
||||
<Setter Property="TextWrapping" Value="Wrap"></Setter>
|
||||
</Style>
|
||||
</Styles>
|
||||
</StackPanel.Styles>
|
||||
<TextBlock Theme="{StaticResource TitleTextBlockStyle}">You are not logged in</TextBlock>
|
||||
<TextBlock>
|
||||
<Run>In order to manage your account you must be logged in.</Run>
|
||||
<LineBreak />
|
||||
<Run>Creating an account is free and we'll not bother you with a newsletter or crap like that.</Run>
|
||||
</TextBlock>
|
||||
|
||||
<Lottie Path="/Assets/Animations/login-pending.json" RepeatCount="1" Width="350" Height="350"></Lottie>
|
||||
|
||||
<TextBlock>
|
||||
<Run>Click Log In below to (create an account) and log in.</Run>
|
||||
<LineBreak />
|
||||
<Run>You'll also be able to log in with Google or Discord.</Run>
|
||||
</TextBlock>
|
||||
<Button HorizontalAlignment="Center" Command="{CompiledBinding Login}" Margin="0 25 0 0">Login</Button>
|
||||
</StackPanel>
|
||||
<!-- <StackPanel IsVisible="{CompiledBinding !IsLoggedIn^}" Margin="0 50 0 0"> -->
|
||||
<!-- <StackPanel.Styles> -->
|
||||
<!-- <Styles> -->
|
||||
<!-- <Style Selector="TextBlock"> -->
|
||||
<!-- <Setter Property="TextAlignment" Value="Center"></Setter> -->
|
||||
<!-- <Setter Property="TextWrapping" Value="Wrap"></Setter> -->
|
||||
<!-- </Style> -->
|
||||
<!-- </Styles> -->
|
||||
<!-- </StackPanel.Styles> -->
|
||||
<!-- <TextBlock Theme="{StaticResource TitleTextBlockStyle}">You are not logged in</TextBlock> -->
|
||||
<!-- <TextBlock> -->
|
||||
<!-- <Run>In order to manage your account you must be logged in.</Run> -->
|
||||
<!-- <LineBreak /> -->
|
||||
<!-- <Run>Creating an account is free and we'll not bother you with a newsletter or crap like that.</Run> -->
|
||||
<!-- </TextBlock> -->
|
||||
<!-- -->
|
||||
<!-- <Lottie Path="/Assets/Animations/login-pending.json" RepeatCount="1" Width="350" Height="350"></Lottie> -->
|
||||
<!-- -->
|
||||
<!-- <TextBlock> -->
|
||||
<!-- <Run>Click Log In below to (create an account) and log in.</Run> -->
|
||||
<!-- <LineBreak /> -->
|
||||
<!-- <Run>You'll also be able to log in with Google or Discord.</Run> -->
|
||||
<!-- </TextBlock> -->
|
||||
<!-- <Button HorizontalAlignment="Center" Command="{CompiledBinding Login}" Margin="0 25 0 0">Login</Button> -->
|
||||
<!-- </StackPanel> -->
|
||||
|
||||
<ScrollViewer IsVisible="{CompiledBinding IsLoggedIn^}" HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto">
|
||||
<StackPanel Margin="15" MaxWidth="1000">
|
||||
@@ -46,7 +51,7 @@
|
||||
</asyncImageLoader:AdvancedImage.Loader>
|
||||
</asyncImageLoader:AdvancedImage>
|
||||
</Border>
|
||||
<TextBlock Grid.Row="0" Grid.Column="1" Padding="0" VerticalAlignment="Bottom" Text="{CompiledBinding Name}" Classes="h3 no-margin"/>
|
||||
<TextBlock Grid.Row="0" Grid.Column="1" Padding="0" VerticalAlignment="Bottom" Text="{CompiledBinding Name}" Classes="h3 no-margin" />
|
||||
<TextBlock Classes="subtitle" Grid.Column="1" Grid.Row="1" Padding="0" VerticalAlignment="Top" Text="{CompiledBinding Email}" />
|
||||
</Grid>
|
||||
|
||||
@@ -66,8 +71,8 @@
|
||||
<Button Width="150" Content="Change password" Command="{CompiledBinding ChangePasswordAddress}" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
<Border Classes="card-separator" IsVisible="{CompiledBinding CanChangePassword}"/>
|
||||
|
||||
<Border Classes="card-separator" IsVisible="{CompiledBinding CanChangePassword}" />
|
||||
|
||||
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
|
||||
<StackPanel Grid.Column="0" VerticalAlignment="Center">
|
||||
<TextBlock>
|
||||
@@ -99,16 +104,45 @@
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- <TextBlock Classes="card-title"> -->
|
||||
<!-- Personal access tokens -->
|
||||
<!-- </TextBlock> -->
|
||||
<!-- <Border Classes="card" VerticalAlignment="Stretch" Margin="0,0,5,0"> -->
|
||||
<!-- <StackPanel> -->
|
||||
<!-- <TextBlock>TODO :)</TextBlock> -->
|
||||
<!-- </StackPanel> -->
|
||||
<!-- </Border> -->
|
||||
<TextBlock Classes="card-title">
|
||||
Personal access tokens
|
||||
</TextBlock>
|
||||
<Border Classes="card" VerticalAlignment="Stretch" Margin="0,0,5,0">
|
||||
<StackPanel>
|
||||
<Button HorizontalAlignment="Right" Command="{CompiledBinding GenerateToken}">Generate token</Button>
|
||||
<Grid ColumnDefinitions="2*,*,*,*" Margin="12 6">
|
||||
<TextBlock Grid.Column="0">Description</TextBlock>
|
||||
<TextBlock Grid.Column="1">Created at</TextBlock>
|
||||
<TextBlock Grid.Column="2" Grid.ColumnSpan="2">Expires at</TextBlock>
|
||||
</Grid>
|
||||
|
||||
<ItemsControl ItemsSource="{CompiledBinding PersonalAccessTokens}">
|
||||
<ItemsControl.Styles>
|
||||
<Style Selector="ContentPresenter:nth-child(even) > Border">
|
||||
<Setter Property="Background" Value="{StaticResource ControlStrokeColorOnAccentDefault}"></Setter>
|
||||
</Style>
|
||||
</ItemsControl.Styles>
|
||||
<ItemsControl.DataTemplates>
|
||||
<DataTemplate DataType="models:PersonalAccessToken">
|
||||
<Border CornerRadius="4" Padding="12 6">
|
||||
<Grid ColumnDefinitions="2*,*,*,*">
|
||||
<TextBlock Grid.Column="0" VerticalAlignment="Center" Text="{CompiledBinding Description}" />
|
||||
<TextBlock Grid.Column="1" VerticalAlignment="Center" Text="{CompiledBinding CreationTime, Converter={StaticResource DateTimeConverter}}" />
|
||||
<TextBlock Grid.Column="2" VerticalAlignment="Center" Text="{CompiledBinding Expiration, Converter={StaticResource DateTimeConverter}}" />
|
||||
<Button Grid.Column="3" HorizontalAlignment="Right"
|
||||
Classes="icon-button"
|
||||
Command="{Binding $parent[settings:AccountTabView].DataContext.DeleteToken}"
|
||||
CommandParameter="{CompiledBinding}">
|
||||
<avalonia:MaterialIcon Kind="Trash" />
|
||||
</Button>
|
||||
</Grid>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ItemsControl.DataTemplates>
|
||||
</ItemsControl>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</Panel>
|
||||
|
||||
</UserControl>
|
||||
@@ -1,7 +1,10 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reactive;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading;
|
||||
@@ -12,6 +15,7 @@ using Artemis.UI.Shared.Routing;
|
||||
using Artemis.UI.Shared.Services;
|
||||
using Artemis.WebClient.Workshop;
|
||||
using Artemis.WebClient.Workshop.Handlers.UploadHandlers;
|
||||
using Artemis.WebClient.Workshop.Models;
|
||||
using Artemis.WebClient.Workshop.Services;
|
||||
using IdentityModel;
|
||||
using PropertyChanged.SourceGenerator;
|
||||
@@ -29,6 +33,7 @@ public partial class AccountTabViewModel : RoutableScreen
|
||||
[Notify(Setter.Private)] private string? _name;
|
||||
[Notify(Setter.Private)] private string? _email;
|
||||
[Notify(Setter.Private)] private string? _avatarUrl;
|
||||
[Notify(Setter.Private)] private ObservableCollection<PersonalAccessToken> _personalAccessTokens = new();
|
||||
|
||||
public AccountTabViewModel(IWindowService windowService, IAuthenticationService authenticationService, IUserManagementService userManagementService)
|
||||
{
|
||||
@@ -36,10 +41,11 @@ public partial class AccountTabViewModel : RoutableScreen
|
||||
_authenticationService = authenticationService;
|
||||
_userManagementService = userManagementService;
|
||||
_authenticationService.AutoLogin(true);
|
||||
|
||||
|
||||
DisplayName = "Account";
|
||||
IsLoggedIn = _authenticationService.IsLoggedIn;
|
||||
|
||||
DeleteToken = ReactiveCommand.CreateFromTask<PersonalAccessToken>(ExecuteDeleteToken);
|
||||
|
||||
this.WhenActivated(d =>
|
||||
{
|
||||
_canChangePassword = _authenticationService.GetClaim(JwtClaimTypes.AuthenticationMethod).Select(c => c?.Value == "pwd").ToProperty(this, vm => vm.CanChangePassword);
|
||||
@@ -50,12 +56,13 @@ public partial class AccountTabViewModel : RoutableScreen
|
||||
|
||||
public bool CanChangePassword => _canChangePassword?.Value ?? false;
|
||||
public IObservable<bool> IsLoggedIn { get; }
|
||||
public ReactiveCommand<PersonalAccessToken,Unit> DeleteToken { get; }
|
||||
|
||||
public async Task Login()
|
||||
{
|
||||
await _windowService.CreateContentDialog().WithViewModel(out WorkshopLoginViewModel _).WithTitle("Account login").ShowAsync();
|
||||
}
|
||||
|
||||
|
||||
public async Task ChangeAvatar()
|
||||
{
|
||||
string[]? result = await _windowService.CreateOpenFileDialog().HavingFilter(f => f.WithBitmaps()).ShowAsync();
|
||||
@@ -85,7 +92,7 @@ public partial class AccountTabViewModel : RoutableScreen
|
||||
.HavingPrimaryButton(b => b.WithText("Submit").WithCommand(vm.Submit))
|
||||
.ShowAsync();
|
||||
}
|
||||
|
||||
|
||||
public async Task ChangePasswordAddress()
|
||||
{
|
||||
await _windowService.CreateContentDialog().WithTitle("Change password")
|
||||
@@ -94,7 +101,7 @@ public partial class AccountTabViewModel : RoutableScreen
|
||||
.HavingPrimaryButton(b => b.WithText("Submit").WithCommand(vm.Submit))
|
||||
.ShowAsync();
|
||||
}
|
||||
|
||||
|
||||
public async Task RemoveAccount()
|
||||
{
|
||||
await _windowService.CreateContentDialog().WithTitle("Remove account")
|
||||
@@ -104,11 +111,36 @@ public partial class AccountTabViewModel : RoutableScreen
|
||||
.ShowAsync();
|
||||
}
|
||||
|
||||
private void LoadCurrentUser()
|
||||
public async Task GenerateToken()
|
||||
{
|
||||
await _windowService.CreateContentDialog().WithTitle("Create Personal Access Token")
|
||||
.WithViewModel(out CreatePersonalAccessTokenViewModel vm)
|
||||
.WithCloseButtonText("Cancel")
|
||||
.HavingPrimaryButton(b => b.WithText("Submit").WithCommand(vm.Submit))
|
||||
.ShowAsync();
|
||||
|
||||
List<PersonalAccessToken> personalAccessTokens = await _userManagementService.GetPersonAccessTokens(CancellationToken.None);
|
||||
PersonalAccessTokens = new ObservableCollection<PersonalAccessToken>(personalAccessTokens);
|
||||
}
|
||||
|
||||
private async Task ExecuteDeleteToken(PersonalAccessToken token)
|
||||
{
|
||||
bool confirm = await _windowService.ShowConfirmContentDialog("Delete Personal Access Token", "Are you sure you want to delete this token? Any services using it will stop working");
|
||||
if (!confirm)
|
||||
return;
|
||||
|
||||
await _userManagementService.DeletePersonAccessToken(token, CancellationToken.None);
|
||||
PersonalAccessTokens.Remove(token);
|
||||
}
|
||||
|
||||
private async Task LoadCurrentUser()
|
||||
{
|
||||
string? userId = _authenticationService.Claims.FirstOrDefault(c => c.Type == "sub")?.Value;
|
||||
Name = _authenticationService.Claims.FirstOrDefault(c => c.Type == "name")?.Value;
|
||||
Email = _authenticationService.Claims.FirstOrDefault(c => c.Type == "email")?.Value;
|
||||
AvatarUrl = $"{WorkshopConstants.AUTHORITY_URL}/user/avatar/{userId}";
|
||||
|
||||
List<PersonalAccessToken> personalAccessTokens = await _userManagementService.GetPersonAccessTokens(CancellationToken.None);
|
||||
PersonalAccessTokens = new ObservableCollection<PersonalAccessToken>(personalAccessTokens);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user