Added avalonia integration and remote proof of concept

This commit is contained in:
MaddoScientisto 2026-02-28 15:30:57 +01:00
commit 4a0973b681
23 changed files with 2043 additions and 6 deletions

View file

@ -5,20 +5,43 @@ using Avalonia.Media.Imaging;
using Avalonia.Platform.Storage;
using Avalonia.Styling;
using Avalonia.Threading;
using Catalog.Communication.Abstractions;
using Catalog.Communication.Models;
using ImageCatalog;
using Microsoft.Extensions.Logging;
using System;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
namespace ImageCatalog_2;
public partial class AvaloniaMainWindow : Window
{
private const string ApiLoginKey = "ApiTest.Login";
private const string ApiPasswordKey = "ApiTest.Password";
private readonly DataModel _model;
private readonly IRaceUploadCommunicationClient _apiClient;
private readonly ParametriSetup _parametriSetup;
private readonly ILogger<AvaloniaMainWindow> _logger;
private bool _isDarkTheme = false;
public AvaloniaMainWindow(DataModel model)
public AvaloniaMainWindow(
DataModel model,
IRaceUploadCommunicationClient apiClient,
ParametriSetup parametriSetup,
ILogger<AvaloniaMainWindow> logger)
{
InitializeComponent();
_model = model;
_apiClient = apiClient;
_parametriSetup = parametriSetup;
_logger = logger;
DataContext = _model;
// Provide Avalonia dispatcher so DataModel can marshal UI updates
@ -104,6 +127,8 @@ public partial class AvaloniaMainWindow : Window
if (e.PropertyName == nameof(_model.LogoFile))
UpdateLogoPreview(_model.LogoFile);
};
LoadApiTestCredentials();
}
private void ToggleTheme_Click(object? sender, RoutedEventArgs e)
@ -148,4 +173,205 @@ public partial class AvaloniaMainWindow : Window
try { preview.Source = new Avalonia.Media.Imaging.Bitmap(path); }
catch { preview.Source = null; }
}
private void LoadApiTestCredentials()
{
var loginBox = this.FindControl<Avalonia.Controls.TextBox>("ApiLoginTextBox");
var passwordBox = this.FindControl<Avalonia.Controls.TextBox>("ApiPasswordTextBox");
if (loginBox is null || passwordBox is null)
{
return;
}
loginBox.Text = _parametriSetup.LeggiParametroString(ApiLoginKey);
passwordBox.Text = _parametriSetup.LeggiParametroString(ApiPasswordKey);
}
private void SaveApiTestCredentials()
{
var loginBox = this.FindControl<Avalonia.Controls.TextBox>("ApiLoginTextBox");
var passwordBox = this.FindControl<Avalonia.Controls.TextBox>("ApiPasswordTextBox");
if (loginBox is null || passwordBox is null)
{
return;
}
_parametriSetup.AggiornaParametro(ApiLoginKey, loginBox.Text ?? string.Empty);
_parametriSetup.AggiornaParametro(ApiPasswordKey, passwordBox.Text ?? string.Empty);
_parametriSetup.SalvaParametriSetup();
}
private async void ApiTestLoginAndGetRaces_Click(object? sender, RoutedEventArgs e)
{
var loginBox = this.FindControl<Avalonia.Controls.TextBox>("ApiLoginTextBox");
var passwordBox = this.FindControl<Avalonia.Controls.TextBox>("ApiPasswordTextBox");
var outputBox = this.FindControl<Avalonia.Controls.TextBox>("ApiOutputTextBox");
var statusBlock = this.FindControl<TextBlock>("ApiStatusTextBlock");
var testButton = this.FindControl<Avalonia.Controls.Button>("ApiTestButton");
if (loginBox is null || passwordBox is null || outputBox is null || statusBlock is null || testButton is null)
{
return;
}
var login = loginBox.Text?.Trim() ?? string.Empty;
var password = passwordBox.Text ?? string.Empty;
if (string.IsNullOrWhiteSpace(login) || string.IsNullOrWhiteSpace(password))
{
statusBlock.Text = "Inserisci login e password.";
return;
}
testButton.IsEnabled = false;
statusBlock.Text = "Esecuzione test...";
outputBox.Text = string.Empty;
try
{
_logger.LogDebug("Starting API test request from Avalonia tab for user '{User}'.", login);
SaveApiTestCredentials();
var loginResponse = await _apiClient.LoginAdminAsync(
new AdminLoginRequest
{
Login = login,
Password = password,
Command = "check",
},
CancellationToken.None);
var searchResponse = await _apiClient.ExecuteGaraCommandAsync(
new Dictionary<string, string?>
{
["cmd"] = "search",
["pageNumber"] = "1",
},
CancellationToken.None);
_logger.LogDebug(
"API test completed requests. LoginStatus={LoginStatusCode}, SearchStatus={SearchStatusCode}",
(int)loginResponse.StatusCode,
(int)searchResponse.StatusCode);
var extracted = ExtractTopRaceLines(searchResponse.Body, 3);
var sb = new StringBuilder();
sb.AppendLine($"Login HTTP: {(int)loginResponse.StatusCode} {loginResponse.StatusCode}");
sb.AppendLine($"Search HTTP: {(int)searchResponse.StatusCode} {searchResponse.StatusCode}");
sb.AppendLine();
if (extracted.Count > 0)
{
sb.AppendLine("Prime 3 righe gare (estrazione semplice):");
for (var i = 0; i < extracted.Count; i++)
{
sb.AppendLine($"{i + 1}. {extracted[i]}");
}
}
else
{
sb.AppendLine("Nessuna riga gara riconosciuta in modo affidabile. Mostro anteprima raw:");
sb.AppendLine();
sb.AppendLine(Truncate(CollapseWhitespace(searchResponse.Body), 1500));
}
outputBox.Text = sb.ToString();
statusBlock.Text = "Test completato.";
}
catch (Exception ex)
{
_logger.LogError(ex, "API test failed in Avalonia tab.");
_logger.LogDebug("API test exception details: {ExceptionDetails}", ex.ToString());
outputBox.Text = ex.ToString();
statusBlock.Text = "Errore durante il test.";
}
finally
{
testButton.IsEnabled = true;
}
}
private static List<string> ExtractTopRaceLines(string html, int take)
{
if (string.IsNullOrWhiteSpace(html))
{
return new List<string>();
}
var rowMatches = Regex.Matches(html, "<tr[^>]*>(.*?)</tr>", RegexOptions.IgnoreCase | RegexOptions.Singleline);
var lines = new List<string>();
foreach (Match row in rowMatches)
{
var cells = Regex.Matches(row.Groups[1].Value, "<t[dh][^>]*>(.*?)</t[dh]>", RegexOptions.IgnoreCase | RegexOptions.Singleline)
.Select(m => CollapseWhitespace(WebUtility.HtmlDecode(StripTags(m.Groups[1].Value))))
.Where(s => !string.IsNullOrWhiteSpace(s))
.ToArray();
if (cells.Length < 2)
{
continue;
}
var joined = string.Join(" | ", cells);
if (IsHeaderLike(joined))
{
continue;
}
lines.Add(joined);
if (lines.Count >= take)
{
break;
}
}
if (lines.Count > 0)
{
return lines;
}
var textRows = Regex.Matches(html, "(?is)<li[^>]*>(.*?)</li>")
.Select(m => CollapseWhitespace(WebUtility.HtmlDecode(StripTags(m.Groups[1].Value))))
.Where(s => s.Length > 8)
.Take(take)
.ToList();
return textRows;
}
private static bool IsHeaderLike(string text)
{
var lower = text.ToLowerInvariant();
return lower.Contains("descrizione")
|| lower.Contains("data")
|| lower.Contains("stato")
|| lower.Contains("azioni")
|| lower.Contains("cerca");
}
private static string StripTags(string value)
{
return Regex.Replace(value, "<[^>]+>", " ", RegexOptions.Singleline);
}
private static string CollapseWhitespace(string? value)
{
if (string.IsNullOrWhiteSpace(value))
{
return string.Empty;
}
return Regex.Replace(value, "\\s+", " ").Trim();
}
private static string Truncate(string value, int max)
{
if (value.Length <= max)
{
return value;
}
return value.Substring(0, max) + "...";
}
}