Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
f8c2a01ef4 |
|||
|
a843e77ae9 |
11 changed files with 190 additions and 6 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -48,3 +48,4 @@ DataDir/
|
||||||
|
|
||||||
# Exclude test reports (local test report, not for the repo)
|
# Exclude test reports (local test report, not for the repo)
|
||||||
/test-reports
|
/test-reports
|
||||||
|
/doc/**/.*.md
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ public static class ServiceCollectionHelpers
|
||||||
var billingClient = new BillingApiClient(api, () => settings.ApiUrl);
|
var billingClient = new BillingApiClient(api, () => settings.ApiUrl);
|
||||||
var estimateClient = new EstimateApiClient(api, () => settings.ApiUrl);
|
var estimateClient = new EstimateApiClient(api, () => settings.ApiUrl);
|
||||||
var frontClient = new FrontOfficeApiClient(api, () => settings.ApiUrl);
|
var frontClient = new FrontOfficeApiClient(api, () => settings.ApiUrl);
|
||||||
|
var resourceUsageClient = new ResourceUsageApiClient(api, () => settings.ApiUrl);
|
||||||
var userDirectory = new UserDirectory(userSearchClient);
|
var userDirectory = new UserDirectory(userSearchClient);
|
||||||
var reverseGeocoding = new NominatimReverseGeocodingService();
|
var reverseGeocoding = new NominatimReverseGeocodingService();
|
||||||
|
|
||||||
|
|
@ -75,6 +76,7 @@ public static class ServiceCollectionHelpers
|
||||||
services.AddTransient<EstimateListPage>();
|
services.AddTransient<EstimateListPage>();
|
||||||
services.AddTransient<EstimateValidationPage>();
|
services.AddTransient<EstimateValidationPage>();
|
||||||
services.AddTransient<MyFilesPage>();
|
services.AddTransient<MyFilesPage>();
|
||||||
|
services.AddTransient<ResourceUsagePage>();
|
||||||
|
|
||||||
// ViewModels
|
// ViewModels
|
||||||
services.AddSingleton(settings);
|
services.AddSingleton(settings);
|
||||||
|
|
@ -87,6 +89,7 @@ public static class ServiceCollectionHelpers
|
||||||
services.AddSingleton(billingClient);
|
services.AddSingleton(billingClient);
|
||||||
services.AddSingleton(estimateClient);
|
services.AddSingleton(estimateClient);
|
||||||
services.AddSingleton(frontClient);
|
services.AddSingleton(frontClient);
|
||||||
|
services.AddSingleton(resourceUsageClient);
|
||||||
services.AddSingleton(userFilesClient);
|
services.AddSingleton(userFilesClient);
|
||||||
services.AddSingleton<IReverseGeocodingService>(reverseGeocoding);
|
services.AddSingleton<IReverseGeocodingService>(reverseGeocoding);
|
||||||
services.AddSingleton<IUserDirectory>(userDirectory);
|
services.AddSingleton<IUserDirectory>(userDirectory);
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,7 @@ public class ViewLocator : IDataTemplate
|
||||||
EstimateListPageViewModel => services.GetRequiredService<EstimateListPage>(),
|
EstimateListPageViewModel => services.GetRequiredService<EstimateListPage>(),
|
||||||
EstimateValidationPageViewModel => services.GetRequiredService<EstimateValidationPage>(),
|
EstimateValidationPageViewModel => services.GetRequiredService<EstimateValidationPage>(),
|
||||||
MyFilesViewModel => services.GetRequiredService<MyFilesPage>(),
|
MyFilesViewModel => services.GetRequiredService<MyFilesPage>(),
|
||||||
|
ResourceUsagePageViewModel => services.GetRequiredService<ResourceUsagePage>(),
|
||||||
ChatViewModel => services.GetRequiredService<ChatPage>(),
|
ChatViewModel => services.GetRequiredService<ChatPage>(),
|
||||||
null => new TextBlock { Text = "No view for <null>" },
|
null => new TextBlock { Text = "No view for <null>" },
|
||||||
_ => new TextBlock { Text = $"No view for {data.GetType().Name}" }
|
_ => new TextBlock { Text = $"No view for {data.GetType().Name}" }
|
||||||
|
|
|
||||||
|
|
@ -36,12 +36,14 @@ public class HomePageViewModel : ViewModelBase
|
||||||
OpenProviderEstimates = new AsyncRelayCommand(() => OpenEstimateListAsync(EstimateListPerspective.Provider));
|
OpenProviderEstimates = new AsyncRelayCommand(() => OpenEstimateListAsync(EstimateListPerspective.Provider));
|
||||||
OpenBlogs = new AsyncRelayCommand(App.PushBlogsPageAsync);
|
OpenBlogs = new AsyncRelayCommand(App.PushBlogsPageAsync);
|
||||||
OpenMyFiles = new AsyncRelayCommand(OpenMyFilesAsync);
|
OpenMyFiles = new AsyncRelayCommand(OpenMyFilesAsync);
|
||||||
|
OpenResourceUsage = new AsyncRelayCommand(OpenResourceUsageAsync);
|
||||||
OpenChat = new AsyncRelayCommand(OpenChatAsync);
|
OpenChat = new AsyncRelayCommand(OpenChatAsync);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public IAsyncRelayCommand OpenChat { get; }
|
public IAsyncRelayCommand OpenChat { get; }
|
||||||
public IAsyncRelayCommand OpenBlogs { get; }
|
public IAsyncRelayCommand OpenBlogs { get; }
|
||||||
|
public IAsyncRelayCommand OpenResourceUsage { get; }
|
||||||
public IAsyncRelayCommand OpenActivities { get; }
|
public IAsyncRelayCommand OpenActivities { get; }
|
||||||
public IAsyncRelayCommand OpenProviderRequests { get; }
|
public IAsyncRelayCommand OpenProviderRequests { get; }
|
||||||
public IAsyncRelayCommand OpenClientEstimates { get; }
|
public IAsyncRelayCommand OpenClientEstimates { get; }
|
||||||
|
|
@ -138,6 +140,25 @@ public class HomePageViewModel : ViewModelBase
|
||||||
await app.PushPageAsync(vm);
|
await app.PushPageAsync(vm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task OpenResourceUsageAsync()
|
||||||
|
{
|
||||||
|
var app = (App?)Application.Current;
|
||||||
|
if (app is null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Application PostIt indisponible.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var resourceUsageClient = app.ServiceProvider?.GetRequiredService<ResourceUsageApiClient>();
|
||||||
|
if (resourceUsageClient is null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Client usage indisponible.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var vm = new ResourceUsagePageViewModel(resourceUsageClient);
|
||||||
|
await vm.InitializeAsync();
|
||||||
|
await app.PushPageAsync(vm);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Avalonia designer constructor. Builds a self-contained VM
|
/// Avalonia designer constructor. Builds a self-contained VM
|
||||||
/// with a freshly-constructed Settings so the XAML preview can
|
/// with a freshly-constructed Settings so the XAML preview can
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,90 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using CommunityToolkit.Mvvm.Input;
|
||||||
|
using Yavsc.Abstract.Resources;
|
||||||
|
using Yavsc.Api.Client;
|
||||||
|
|
||||||
|
namespace PostIt.ViewModels;
|
||||||
|
|
||||||
|
public partial class ResourceUsagePageViewModel : ViewModelBase
|
||||||
|
{
|
||||||
|
private readonly ResourceUsageApiClient _client;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
public partial bool IsBusy { get; set; }
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
public partial bool IsAdminView { get; set; }
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
public partial ResourceUsageSummary? CurrentSummary { get; set; }
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
public partial ObservableCollection<ResourceUsageSummary> Overview { get; set; } = new();
|
||||||
|
|
||||||
|
public string Heading => IsAdminView ? "Vue d'administration des usages" : "Mon usage des ressources";
|
||||||
|
|
||||||
|
public override bool CanNavigateNext { get => false; protected set { _ = value; } }
|
||||||
|
public override bool CanNavigatePrevious { get => true; protected set { _ = value; } }
|
||||||
|
|
||||||
|
public ResourceUsagePageViewModel(ResourceUsageApiClient client)
|
||||||
|
{
|
||||||
|
_client = client ?? throw new ArgumentNullException(nameof(client));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task InitializeAsync() => RefreshAsync();
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
public async Task RefreshAsync()
|
||||||
|
{
|
||||||
|
IsBusy = true;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Overview.Clear();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var adminOverview = await _client.GetAdminOverviewAsync().ConfigureAwait(true);
|
||||||
|
if (adminOverview is { Count: > 0 })
|
||||||
|
{
|
||||||
|
foreach (var item in adminOverview)
|
||||||
|
{
|
||||||
|
Overview.Add(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
CurrentSummary = adminOverview[0];
|
||||||
|
IsAdminView = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
// Non-admin users are not allowed on the admin route; fall back
|
||||||
|
// to the current-user summary below.
|
||||||
|
}
|
||||||
|
|
||||||
|
var current = await _client.GetCurrentAsync().ConfigureAwait(true);
|
||||||
|
CurrentSummary = current;
|
||||||
|
if (current is not null)
|
||||||
|
{
|
||||||
|
Overview.Clear();
|
||||||
|
Overview.Add(current);
|
||||||
|
}
|
||||||
|
|
||||||
|
IsAdminView = false;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
CurrentSummary = null;
|
||||||
|
Overview.Clear();
|
||||||
|
IsAdminView = false;
|
||||||
|
throw new InvalidOperationException($"Impossible de charger l'usage des ressources : {ex.Message}", ex);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
IsBusy = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -44,5 +44,9 @@
|
||||||
Command="{Binding OpenMyFiles}"
|
Command="{Binding OpenMyFiles}"
|
||||||
HorizontalAlignment="Center"
|
HorizontalAlignment="Center"
|
||||||
IsEnabled="{Binding SessionStatus.IsLoggedIn}"/>
|
IsEnabled="{Binding SessionStatus.IsLoggedIn}"/>
|
||||||
|
<Button Content="Mon usage / administration"
|
||||||
|
Command="{Binding OpenResourceUsage}"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
IsEnabled="{Binding SessionStatus.IsLoggedIn}"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</ContentPage>
|
</ContentPage>
|
||||||
|
|
|
||||||
57
src/PostIt/PostIt/Views/Layout/ResourceUsagePage.axaml
Normal file
57
src/PostIt/PostIt/Views/Layout/ResourceUsagePage.axaml
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
<ContentPage xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:vm="using:PostIt.ViewModels"
|
||||||
|
xmlns:resources="using:Yavsc.Abstract.Resources"
|
||||||
|
x:Class="PostIt.Views.ResourceUsagePage"
|
||||||
|
x:DataType="vm:ResourceUsagePageViewModel"
|
||||||
|
Header="Usage des ressources">
|
||||||
|
|
||||||
|
<StackPanel Margin="16" Spacing="12">
|
||||||
|
<TextBlock Text="{Binding Heading}"
|
||||||
|
FontSize="22"
|
||||||
|
FontWeight="SemiBold"/>
|
||||||
|
|
||||||
|
<Button Content="Actualiser"
|
||||||
|
Command="{Binding RefreshCommand}"
|
||||||
|
IsEnabled="{Binding !IsBusy}"/>
|
||||||
|
|
||||||
|
<ProgressBar IsVisible="{Binding IsBusy}"
|
||||||
|
IsIndeterminate="True"
|
||||||
|
Height="8"/>
|
||||||
|
|
||||||
|
<StackPanel IsVisible="{Binding !IsAdminView}">
|
||||||
|
<TextBlock Text="Résumé personnel" FontWeight="Bold"/>
|
||||||
|
<StackPanel DataContext="{Binding CurrentSummary}" Spacing="4">
|
||||||
|
<TextBlock Text="{Binding UserName, StringFormat='Utilisateur : {0}'}"/>
|
||||||
|
<TextBlock Text="{Binding ApiCalls, StringFormat='Appels API : {0:F0}'}"/>
|
||||||
|
<TextBlock Text="{Binding CpuSeconds, StringFormat='CPU : {0:F3} s'}"/>
|
||||||
|
<TextBlock Text="{Binding BandwidthMb, StringFormat='Bande passante : {0:F3} Mo'}"/>
|
||||||
|
<TextBlock Text="{Binding StorageMb, StringFormat='Stockage : {0:F3} Mo'}"/>
|
||||||
|
<TextBlock Text="{Binding EstimatedCompensation, StringFormat='Estimation : {0:F2}'}"/>
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<ItemsControl ItemsSource="{Binding Overview}"
|
||||||
|
IsVisible="{Binding IsAdminView}">
|
||||||
|
<ItemsControl.ItemTemplate>
|
||||||
|
<DataTemplate x:DataType="resources:ResourceUsageSummary">
|
||||||
|
<Border Margin="0,0,0,8"
|
||||||
|
Padding="12"
|
||||||
|
CornerRadius="8"
|
||||||
|
BorderBrush="#D0D7DE"
|
||||||
|
BorderThickness="1">
|
||||||
|
<StackPanel Spacing="4">
|
||||||
|
<TextBlock Text="{Binding UserName}"
|
||||||
|
FontWeight="Bold"/>
|
||||||
|
<TextBlock Text="{Binding ApiCalls, StringFormat='Appels API : {0:F0}'}"/>
|
||||||
|
<TextBlock Text="{Binding CpuSeconds, StringFormat='CPU : {0:F3} s'}"/>
|
||||||
|
<TextBlock Text="{Binding BandwidthMb, StringFormat='Bande passante : {0:F3} Mo'}"/>
|
||||||
|
<TextBlock Text="{Binding StorageMb, StringFormat='Stockage : {0:F3} Mo'}"/>
|
||||||
|
<TextBlock Text="{Binding EstimatedCompensation, StringFormat='Estimation : {0:F2}'}"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsControl.ItemTemplate>
|
||||||
|
</ItemsControl>
|
||||||
|
</StackPanel>
|
||||||
|
</ContentPage>
|
||||||
11
src/PostIt/PostIt/Views/Layout/ResourceUsagePage.axaml.cs
Normal file
11
src/PostIt/PostIt/Views/Layout/ResourceUsagePage.axaml.cs
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
using Avalonia.Controls;
|
||||||
|
|
||||||
|
namespace PostIt.Views;
|
||||||
|
|
||||||
|
public partial class ResourceUsagePage : ContentPage
|
||||||
|
{
|
||||||
|
public ResourceUsagePage()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
background-position: 0%;
|
background-position: 0%;
|
||||||
background-attachment: fixed;">
|
background-attachment: fixed;">
|
||||||
<a class="navbar-brand logo" href="/">
|
<a class="navbar-brand logo" href="/">
|
||||||
<img src="~/images/yavsc-logo.svg" alt="Yavsc" style="height:32px; margin-right:8px; display:inline-block; vertical-align:middle;" />
|
<img src="~/images/yavsc-logo.svg" alt="Yavsc" style="height:32px; margin-left:8px; display:inline-block; vertical-align:middle;" />
|
||||||
@SiteSettings.Value.Title
|
@SiteSettings.Value.Title
|
||||||
</a>
|
</a>
|
||||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse"
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse"
|
||||||
|
|
|
||||||
|
|
@ -14229,10 +14229,6 @@ body {
|
||||||
--bs-secondary-rgb: transparent
|
--bs-secondary-rgb: transparent
|
||||||
}
|
}
|
||||||
|
|
||||||
.card a {
|
|
||||||
color: #fff
|
|
||||||
}
|
|
||||||
|
|
||||||
.card .text-muted {
|
.card .text-muted {
|
||||||
color: rgba(255, 255, 255, .7) !important
|
color: rgba(255, 255, 255, .7) !important
|
||||||
}
|
}
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
Loading…
Reference in a new issue