The status PATCH needs statusName alongside statusId or the API 400s, so every write-back had been silently failing. The board's flat assigneeName is null on every live ticket; the real assignee list is ticketAssignees[]. Import now also picks up In Bearbeitung tickets, not just Offen ones. Both issues are live-verified against the real API, so the feature flag flips back on.
106 lines
3.5 KiB
C#
106 lines
3.5 KiB
C#
using ClaudeDo.Ui.Localization;
|
|
using ClaudeDo.Ui.Services;
|
|
using CommunityToolkit.Mvvm.ComponentModel;
|
|
using CommunityToolkit.Mvvm.Input;
|
|
|
|
namespace ClaudeDo.Ui.ViewModels.Modals.Settings;
|
|
|
|
public sealed partial class TicketSettingsTabViewModel : ViewModelBase
|
|
{
|
|
/// <summary>
|
|
/// Der Aus-Schalter der gesamten Ticketsystem-Anbindung in der UI, wie bei
|
|
/// <c>SettingsModalViewModel.ShowOnlineInbox</c>. Er versteckt drei Einstiegspunkte: diesen
|
|
/// Settings-Tab, das Ticket-Projekt-Dropdown in den Listen-Einstellungen und den
|
|
/// Kontextmenü-Eintrag zum Importieren.
|
|
///
|
|
/// Pausiert am 2026-08-27 (API unter dem Hostnamen nicht erreichbar), reaktiviert am
|
|
/// 2026-09-01: die API läuft unter <c>http://192.168.45.161</c> und alle vier Routen sind
|
|
/// live verifiziert. Base-URL ist deshalb eine IP, kein Hostname — <c>bandelapis.fb-tuning.local</c>
|
|
/// lehnt die Verbindung weiterhin ab.
|
|
/// </summary>
|
|
public const bool FeatureEnabled = true;
|
|
|
|
private readonly IWorkerClient _worker;
|
|
|
|
[ObservableProperty] private string _apiBaseUrl = "";
|
|
[ObservableProperty] private string _token = "";
|
|
[ObservableProperty]
|
|
[NotifyPropertyChangedFor(nameof(TokenPlaceholder))]
|
|
private bool _tokenSet;
|
|
[ObservableProperty] private bool _isBusy;
|
|
[ObservableProperty] private string _statusMessage = "";
|
|
|
|
// Never shows the stored token — TicketSettingsDto only ever carries the bool.
|
|
public string TokenPlaceholder => TokenSet ? Loc.T("settings.tickets.tokenSet") : "";
|
|
|
|
public TicketSettingsTabViewModel(IWorkerClient worker) => _worker = worker;
|
|
|
|
public async Task LoadAsync()
|
|
{
|
|
var dto = await _worker.GetTicketSettingsAsync();
|
|
if (dto is null)
|
|
{
|
|
StatusMessage = Loc.T("vm.tickets.workerOffline");
|
|
return;
|
|
}
|
|
ApiBaseUrl = dto.ApiBaseUrl ?? "";
|
|
TokenSet = dto.TokenSet;
|
|
}
|
|
|
|
public async Task SaveAsync()
|
|
{
|
|
await _worker.SetTicketApiBaseUrlAsync(ApiBaseUrl);
|
|
if (!string.IsNullOrWhiteSpace(Token))
|
|
{
|
|
await _worker.SetTicketTokenAsync(Token);
|
|
Token = "";
|
|
TokenSet = true;
|
|
}
|
|
}
|
|
|
|
[RelayCommand]
|
|
private async Task TestConnection()
|
|
{
|
|
IsBusy = true;
|
|
StatusMessage = "";
|
|
try
|
|
{
|
|
await SaveAsync();
|
|
var result = await _worker.TestTicketConnectionAsync();
|
|
if (result is null || !result.Ok)
|
|
{
|
|
StatusMessage = result?.Error ?? Loc.T("vm.tickets.workerOffline");
|
|
return;
|
|
}
|
|
|
|
var missing = RequiredScopes.Where(s => !result.Scopes.Contains(s)).ToList();
|
|
StatusMessage = missing.Count == 0
|
|
? Loc.T("vm.tickets.connectionOk", result.UserName!)
|
|
: Loc.T("vm.tickets.connectionMissingScopes", result.UserName!, string.Join(", ", missing));
|
|
}
|
|
finally
|
|
{
|
|
IsBusy = false;
|
|
}
|
|
}
|
|
|
|
[RelayCommand]
|
|
private async Task ClearToken()
|
|
{
|
|
await _worker.ClearTicketTokenAsync();
|
|
Token = "";
|
|
TokenSet = false;
|
|
StatusMessage = Loc.T("vm.tickets.tokenCleared");
|
|
}
|
|
|
|
// Ohne diese drei Scopes scheitert entweder der Import oder die Statusrueckmeldung —
|
|
// der mit Abstand haeufigste Konfigurationsfehler, deshalb wird er beim Testen benannt
|
|
// statt nur geloggt.
|
|
private static readonly string[] RequiredScopes =
|
|
[
|
|
"pat:projects:read",
|
|
"pat:board:read",
|
|
"pat:tickets:write",
|
|
];
|
|
}
|