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 { /// /// Der Aus-Schalter der gesamten Ticketsystem-Anbindung in der UI — hier auf true /// für die Reaktivierung, wie bei SettingsModalViewModel.ShowOnlineInbox. /// /// Pausiert am 2026-08-27: die Bandel-API war unter dem eingetragenen Hostnamen nicht /// erreichbar (Verbindung aktiv abgelehnt — vermutlich fehlende IIS-Hostname-Bindung oder /// falscher Port, siehe deploy/deploy-iis-winrm.ps1). Solange das offen ist, würde der Tab /// nur eine Einrichtung anbieten, die nicht funktionieren kann. /// /// Der Schalter versteckt drei Einstiegspunkte: diesen Settings-Tab, das Ticket-Projekt- /// Dropdown in den Listen-Einstellungen und den Kontextmenü-Eintrag zum Importieren. /// Gespeicherte Werte (Base-URL, PAT, verknüpfte Projekte) bleiben unangetastet und greifen /// beim Umlegen sofort wieder. /// public const bool FeatureEnabled = false; 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", ]; }