feat(ui): complete Avalonia UI port with 7 main pages, tool settings & top MenuBar

This commit is contained in:
Richard
2026-08-10 10:48:34 +02:00
parent a0e18d2a57
commit b5bf97ae74
187 changed files with 20054 additions and 882 deletions
@@ -0,0 +1,51 @@
<Window 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:vm="using:ClawdDotNet.Desktop.ViewModels"
mc:Ignorable="d" d:DesignWidth="450" d:DesignHeight="380"
Width="480" Height="400"
Padding="16"
WindowStartupLocation="CenterOwner"
CanResize="False"
Title="Neuen Agenten anlegen"
x:Class="ClawdDotNet.Desktop.Views.AddAgentWindow"
x:DataType="vm:AddAgentViewModel">
<DockPanel>
<!-- Bottom Buttons -->
<StackPanel DockPanel.Dock="Bottom" Orientation="Horizontal" HorizontalAlignment="Right" Spacing="8" Margin="0,16,0,0">
<Button Content="Abbrechen" Command="{Binding CancelCommand}" />
<Button Content="Anlegen" Command="{Binding ConfirmCommand}" Classes="accent" />
</StackPanel>
<TextBlock DockPanel.Dock="Bottom" Text="{Binding StatusMessage}" Foreground="Red" Margin="0,8,0,0" />
<StackPanel Spacing="12">
<StackPanel Spacing="4">
<TextBlock Text="Agent-ID (eindeutiger Schlüssel):" FontWeight="Bold" />
<TextBox Text="{Binding AgentId}" PlaceholderText="z.B. dev-assistant" />
</StackPanel>
<StackPanel Spacing="4">
<TextBlock Text="Anzeigename:" FontWeight="Bold" />
<TextBox Text="{Binding DisplayName}" PlaceholderText="z.B. Dev Assistant" />
</StackPanel>
<StackPanel Spacing="4">
<TextBlock Text="Standard-Modell:" FontWeight="Bold" />
<ComboBox ItemsSource="{Binding AvailableModels}" SelectedItem="{Binding SelectedModel}" HorizontalAlignment="Stretch" />
</StackPanel>
<StackPanel Spacing="4">
<TextBlock Text="Beschreibung / Rolle:" FontWeight="Bold" />
<TextBox Text="{Binding Description}" PlaceholderText="Kurze Beschreibung der Rolle..." AcceptsReturn="True" Height="60" />
</StackPanel>
</StackPanel>
</DockPanel>
</Window>
@@ -0,0 +1,21 @@
using Avalonia.Controls;
using ClawdDotNet.Desktop.ViewModels;
namespace ClawdDotNet.Desktop.Views;
public partial class AddAgentWindow : Window
{
public AddAgentWindow()
{
InitializeComponent();
}
protected override void OnDataContextChanged(EventArgs e)
{
base.OnDataContextChanged(e);
if (DataContext is AddAgentViewModel vm)
{
vm.CloseRequested += () => Close();
}
}
}
@@ -0,0 +1,80 @@
<Window 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:vm="using:ClawdDotNet.Desktop.ViewModels"
mc:Ignorable="d" d:DesignWidth="500" d:DesignHeight="450"
Width="520" Height="460"
Padding="16"
WindowStartupLocation="CenterOwner"
CanResize="False"
Title="Job hinzufügen / bearbeiten"
x:Class="ClawdDotNet.Desktop.Views.AddJobWindow"
x:DataType="vm:AddJobViewModel">
<DockPanel>
<!-- Bottom Buttons -->
<StackPanel DockPanel.Dock="Bottom" Orientation="Horizontal" HorizontalAlignment="Right" Spacing="8" Margin="0,16,0,0">
<Button Content="Abbrechen" Command="{Binding CancelCommand}" />
<Button Content="Speichern" Command="{Binding ConfirmCommand}" Classes="accent" />
</StackPanel>
<TextBlock DockPanel.Dock="Bottom" Text="{Binding StatusMessage}" Foreground="Red" Margin="0,8,0,0" />
<ScrollViewer>
<StackPanel Spacing="12">
<!-- Agent Selection -->
<StackPanel Spacing="4">
<TextBlock Text="Agent:" FontWeight="Bold" />
<ComboBox ItemsSource="{Binding AvailableAgents}" SelectedItem="{Binding SelectedAgent}" HorizontalAlignment="Stretch">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding DisplayName}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</StackPanel>
<!-- Job Type RadioButtons -->
<StackPanel Spacing="4" Margin="0,4,0,0">
<TextBlock Text="Job-Typ:" FontWeight="Bold" />
<RadioButton Content="Agent Wakeup (Periodische Ausführung)" IsChecked="{Binding IsAgentWakeup}" />
<RadioButton Content="Tool Job (Werkzeug-spezifischer Job)" IsChecked="{Binding IsToolJob}" />
</StackPanel>
<!-- Tool Job Details -->
<StackPanel Spacing="8" IsVisible="{Binding IsToolJob}" Margin="12,4,0,0">
<StackPanel Spacing="4">
<TextBlock Text="Tool:" FontWeight="Bold" />
<ComboBox ItemsSource="{Binding AvailableTools}" SelectedItem="{Binding SelectedToolName}" HorizontalAlignment="Stretch" />
</StackPanel>
<StackPanel Spacing="4">
<TextBlock Text="Job-Typ-ID / Name:" FontWeight="Bold" />
<TextBox Text="{Binding JobTypeId}" PlaceholderText="z.B. PollMail" />
</StackPanel>
</StackPanel>
<!-- Agent Wakeup Details -->
<StackPanel Spacing="4" IsVisible="{Binding IsAgentWakeup}">
<TextBlock Text="Aufgabenbeschreibung / Nachricht:" FontWeight="Bold" />
<TextBox Text="{Binding TaskMessage}" PlaceholderText="Nachricht an den Agenten..." AcceptsReturn="True" Height="60" />
</StackPanel>
<!-- Cron & Options -->
<StackPanel Spacing="4">
<TextBlock Text="Cron-Ausdruck:" FontWeight="Bold" />
<TextBox Text="{Binding CronExpression}" PlaceholderText="0 * * * *" />
<TextBlock Text="Syntax: Min Std Tag Mon Wochentag (z.B. '*/15 * * * *' alle 15 Minuten)" Classes="caption" />
</StackPanel>
<CheckBox Content="Beim Anwendungsstart einmalig sofort ausführen" IsChecked="{Binding RunOnStart}" Margin="0,4,0,0" />
</StackPanel>
</ScrollViewer>
</DockPanel>
</Window>
@@ -0,0 +1,21 @@
using Avalonia.Controls;
using ClawdDotNet.Desktop.ViewModels;
namespace ClawdDotNet.Desktop.Views;
public partial class AddJobWindow : Window
{
public AddJobWindow()
{
InitializeComponent();
}
protected override void OnDataContextChanged(EventArgs e)
{
base.OnDataContextChanged(e);
if (DataContext is AddJobViewModel vm)
{
vm.CloseRequested += () => Close();
}
}
}
@@ -0,0 +1,53 @@
<Window 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:vm="using:ClawdDotNet.Desktop.ViewModels"
mc:Ignorable="d" d:DesignWidth="450" d:DesignHeight="350"
Width="480" Height="360"
Padding="16"
WindowStartupLocation="CenterOwner"
CanResize="False"
Title="Dienst (Service) hinzufügen"
x:Class="ClawdDotNet.Desktop.Views.AddServiceWindow"
x:DataType="vm:AddServiceViewModel">
<DockPanel>
<!-- Bottom Buttons -->
<StackPanel DockPanel.Dock="Bottom" Orientation="Horizontal" HorizontalAlignment="Right" Spacing="8" Margin="0,16,0,0">
<Button Content="Abbrechen" Command="{Binding CancelCommand}" />
<Button Content="Hinzufügen" Command="{Binding ConfirmCommand}" Classes="accent" />
</StackPanel>
<TextBlock DockPanel.Dock="Bottom" Text="{Binding StatusMessage}" Foreground="Red" Margin="0,8,0,0" />
<StackPanel Spacing="12">
<StackPanel Spacing="4">
<TextBlock Text="Name des Dienstes:" FontWeight="Bold" />
<TextBox Text="{Binding ServiceName}" PlaceholderText="z.B. WebHookService" />
</StackPanel>
<Grid ColumnDefinitions="*,*" RowDefinitions="Auto,Auto">
<StackPanel Grid.Column="0" Spacing="4" Margin="0,0,4,0">
<TextBlock Text="Typ:" FontWeight="Bold" />
<ComboBox ItemsSource="{Binding AvailableServiceTypes}" SelectedItem="{Binding ServiceType}" HorizontalAlignment="Stretch" />
</StackPanel>
<StackPanel Grid.Column="1" Spacing="4" Margin="4,0,0,0">
<TextBlock Text="Port:" FontWeight="Bold" />
<NumericUpDown Value="{Binding ServicePort}" Minimum="1" Maximum="65535" FormatString="0" />
</StackPanel>
</Grid>
<StackPanel Spacing="4">
<TextBlock Text="Beschreibung:" FontWeight="Bold" />
<TextBox Text="{Binding ServiceDescription}" PlaceholderText="Optionale Beschreibung..." AcceptsReturn="True" Height="60" />
</StackPanel>
</StackPanel>
</DockPanel>
</Window>
@@ -0,0 +1,21 @@
using Avalonia.Controls;
using ClawdDotNet.Desktop.ViewModels;
namespace ClawdDotNet.Desktop.Views;
public partial class AddServiceWindow : Window
{
public AddServiceWindow()
{
InitializeComponent();
}
protected override void OnDataContextChanged(EventArgs e)
{
base.OnDataContextChanged(e);
if (DataContext is AddServiceViewModel vm)
{
vm.CloseRequested += () => Close();
}
}
}
@@ -0,0 +1,155 @@
<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:vm="using:ClawdDotNet.Desktop.ViewModels"
mc:Ignorable="d" d:DesignWidth="900" d:DesignHeight="700"
Padding="16"
x:Class="ClawdDotNet.Desktop.Views.AgentsPageView"
x:DataType="vm:AgentsPageViewModel">
<DockPanel>
<!-- Statusbar am unteren Rand -->
<Border Classes="statusbar" DockPanel.Dock="Bottom">
<TextBlock Text="{Binding StatusText}" VerticalAlignment="Center" />
</Border>
<!-- Master-Detail Split -->
<Grid ColumnDefinitions="260,*">
<!-- Linke Spalte: Agenten-Liste -->
<DockPanel Grid.Column="0" Margin="0,0,12,0">
<!-- Toolbar -->
<StackPanel Classes="toolbar" DockPanel.Dock="Top">
<Button Content="Neu" Command="{Binding AddAgentCommand}" Classes="accent" />
<Button Content="Entfernen" Command="{Binding RemoveAgentCommand}" IsEnabled="{Binding SelectedAgent, Converter={x:Static ObjectConverters.IsNotNull}}" />
<Button Content="Speichern" Command="{Binding SaveAgentsCommand}" />
</StackPanel>
<!-- Liste -->
<ListBox ItemsSource="{Binding Agents}" SelectedItem="{Binding SelectedAgent}">
<ListBox.ItemTemplate>
<DataTemplate DataType="vm:AgentItemViewModel">
<StackPanel Margin="4">
<TextBlock Text="{Binding DisplayName}" FontWeight="Bold" />
<TextBlock Text="{Binding Model}" Classes="caption" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DockPanel>
<!-- Rechte Spalte: Agenten-Details -->
<ContentControl Grid.Column="1" Content="{Binding SelectedAgent}">
<ContentControl.ContentTemplate>
<DataTemplate DataType="vm:AgentItemViewModel">
<TabControl>
<!-- TAB 1: Allgemein & Modell -->
<TabItem Header="Allgemein &amp; Modell">
<ScrollViewer Padding="8">
<StackPanel Spacing="12">
<Grid ColumnDefinitions="140,*" RowDefinitions="Auto,Auto,Auto,Auto,Auto">
<TextBlock Grid.Row="0" Grid.Column="0" Text="Agent ID:" FontWeight="Bold" VerticalAlignment="Center" />
<TextBox Grid.Row="0" Grid.Column="1" Text="{Binding AgentId}" IsReadOnly="True" />
<TextBlock Grid.Row="1" Grid.Column="0" Text="Anzeigename:" FontWeight="Bold" VerticalAlignment="Center" Margin="0,8,0,0" />
<TextBox Grid.Row="1" Grid.Column="1" Text="{Binding DisplayName}" Margin="0,8,0,0" />
<TextBlock Grid.Row="2" Grid.Column="0" Text="Modell:" FontWeight="Bold" VerticalAlignment="Center" Margin="0,8,0,0" />
<ComboBox Grid.Row="2" Grid.Column="1" ItemsSource="{Binding $parent[UserControl].((vm:AgentsPageViewModel)DataContext).KnownModels}" SelectedItem="{Binding Model}" HorizontalAlignment="Stretch" Margin="0,8,0,0" />
<TextBlock Grid.Row="3" Grid.Column="0" Text="Rolle / Beschreibung:" FontWeight="Bold" VerticalAlignment="Center" Margin="0,8,0,0" />
<TextBox Grid.Row="3" Grid.Column="1" Text="{Binding Description}" AcceptsReturn="True" Height="60" Margin="0,8,0,0" />
<TextBlock Grid.Row="4" Grid.Column="0" Text="Prompt Caching:" FontWeight="Bold" VerticalAlignment="Center" Margin="0,8,0,0" />
<ComboBox Grid.Row="4" Grid.Column="1" ItemsSource="{Binding $parent[UserControl].((vm:AgentsPageViewModel)DataContext).CachingOptions}" SelectedItem="{Binding PromptCaching}" Width="150" HorizontalAlignment="Left" Margin="0,8,0,0" />
</Grid>
<!-- Limits Card -->
<Border Classes="card" Margin="0,8,0,0">
<StackPanel Spacing="8">
<TextBlock Text="Schutzgrenzen (LoopGuard &amp; Budget)" Classes="heading" />
<Grid ColumnDefinitions="*,*" RowDefinitions="Auto,Auto,Auto">
<StackPanel Grid.Row="0" Grid.Column="0" Spacing="4">
<TextBlock Text="Max. Schritte pro Run:" />
<NumericUpDown Value="{Binding MaxSteps}" Minimum="1" Maximum="100" />
</StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" Spacing="4" Margin="8,0,0,0">
<TextBlock Text="Timeout (Sekunden):" />
<NumericUpDown Value="{Binding TimeoutSeconds}" Minimum="10" Maximum="3600" />
</StackPanel>
<StackPanel Grid.Row="1" Grid.Column="0" Spacing="4" Margin="0,8,0,0">
<TextBlock Text="Max. Kontext-Tokens:" />
<NumericUpDown Value="{Binding MaxContextTokens}" Minimum="1000" Maximum="2000000" />
</StackPanel>
<StackPanel Grid.Row="1" Grid.Column="1" Spacing="4" Margin="8,8,0,0">
<TextBlock Text="Max. Tool-Ergebnis (Zeichen):" />
<NumericUpDown Value="{Binding MaxToolResultChars}" Minimum="1000" Maximum="500000" />
</StackPanel>
</Grid>
</StackPanel>
</Border>
</StackPanel>
</ScrollViewer>
</TabItem>
<!-- TAB 2: System Prompt & Seele -->
<TabItem Header="System-Prompt &amp; Soul">
<ScrollViewer Padding="8">
<StackPanel Spacing="12">
<StackPanel Spacing="4">
<TextBlock Text="System-Prompt (Zusätzliche Anweisungen):" FontWeight="Bold" />
<TextBox Text="{Binding SystemPrompt}" AcceptsReturn="True" Height="120" TextWrapping="Wrap" />
</StackPanel>
<StackPanel Spacing="4">
<TextBlock Text="Identität (Identity):" FontWeight="Bold" />
<TextBox Text="{Binding Identity}" AcceptsReturn="True" Height="100" TextWrapping="Wrap" />
</StackPanel>
<StackPanel Spacing="4">
<TextBlock Text="Seele &amp; Werte (Soul):" FontWeight="Bold" />
<TextBox Text="{Binding Soul}" AcceptsReturn="True" Height="100" TextWrapping="Wrap" />
</StackPanel>
</StackPanel>
</ScrollViewer>
</TabItem>
<!-- TAB 3: Werkzeuge (Tools) -->
<TabItem Header="Werkzeuge (Tools)">
<ScrollViewer Padding="8">
<StackPanel Spacing="8">
<TextBlock Text="Zugelassene Werkzeuge für diesen Agenten:" Classes="heading" />
<ItemsControl ItemsSource="{Binding AvailableTools}">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="vm:ToolItemViewModel">
<Grid ColumnDefinitions="200,Auto" Margin="0,4">
<CheckBox Content="{Binding Name}" IsChecked="{Binding IsEnabled}" Grid.Column="0" VerticalAlignment="Center" />
<Button Content="⚙ Konfigurieren" Command="{Binding $parent[UserControl].((vm:AgentsPageViewModel)DataContext).ConfigureToolCommand}" CommandParameter="{Binding}" IsEnabled="{Binding IsEnabled}" Grid.Column="1" />
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</ScrollViewer>
</TabItem>
</TabControl>
</DataTemplate>
</ContentControl.ContentTemplate>
</ContentControl>
</Grid>
</DockPanel>
</UserControl>
@@ -0,0 +1,46 @@
using Avalonia.Controls;
using ClawdDotNet.Desktop.ViewModels;
namespace ClawdDotNet.Desktop.Views;
public partial class AgentsPageView : UserControl
{
public AgentsPageView()
{
InitializeComponent();
}
protected override void OnDataContextChanged(EventArgs e)
{
base.OnDataContextChanged(e);
if (DataContext is AgentsPageViewModel vm)
{
vm.RequestAddAgentDialog += OnRequestAddAgentDialog;
vm.RequestToolSettingsDialog += OnRequestToolSettingsDialog;
}
}
private async Task<AddAgentResultData?> OnRequestAddAgentDialog(AddAgentViewModel vm)
{
var topLevel = TopLevel.GetTopLevel(this);
if (topLevel is Window window)
{
var dialog = new AddAgentWindow { DataContext = vm };
await dialog.ShowDialog(window);
return vm.Result;
}
return null;
}
private async Task<Dictionary<string, object?>?> OnRequestToolSettingsDialog(ToolSettingsViewModel vm)
{
var topLevel = TopLevel.GetTopLevel(this);
if (topLevel is Window window)
{
var dialog = new ToolSettingsWindow { DataContext = vm };
await dialog.ShowDialog(window);
return vm.Result;
}
return null;
}
}
@@ -0,0 +1,100 @@
<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:vm="using:ClawdDotNet.Desktop.ViewModels"
mc:Ignorable="d" d:DesignWidth="900" d:DesignHeight="700"
Padding="16"
x:Class="ClawdDotNet.Desktop.Views.BackupPageView"
x:DataType="vm:BackupPageViewModel">
<DockPanel>
<!-- Statusbar am unteren Rand -->
<Border Classes="statusbar" DockPanel.Dock="Bottom">
<TextBlock Text="{Binding StatusText}" VerticalAlignment="Center" />
</Border>
<ScrollViewer>
<StackPanel Spacing="16">
<TextBlock Text="Sicherung &amp; Wiederherstellung" Classes="heading" FontSize="20" />
<!-- Card: Neue Sicherung erstellen -->
<Border Classes="card">
<StackPanel Spacing="12">
<TextBlock Text="Manuelle Sicherung erstellen" Classes="heading" />
<Grid ColumnDefinitions="*,Auto" RowDefinitions="Auto">
<TextBox Text="{Binding TargetFolder}" Grid.Column="0" PlaceholderText="Zielordner für Sicherungen..." />
<Button Content="Ordner wählen" Click="OnBrowseTargetFolder" Grid.Column="1" Margin="8,0,0,0" />
</Grid>
<!-- Zugangsdaten Optionen -->
<TextBlock Text="Zugangsdaten (API-Schlüssel, Passwörter):" FontWeight="Bold" Margin="0,4,0,0" />
<RadioButton Content="Zugangsdaten NICHT mit sichern (ohne Passphrase)" IsChecked="{Binding SecretsExclude}" />
<RadioButton Content="Mit Passphrase verschlüsseln" IsChecked="{Binding SecretsPassphrase}" />
<StackPanel Spacing="8" IsVisible="{Binding SecretsPassphrase}" Margin="20,0,0,0">
<TextBox Text="{Binding Passphrase}" PasswordChar="*" PlaceholderText="Passphrase eingeben" Width="300" HorizontalAlignment="Left" />
<TextBox Text="{Binding PassphraseRepeat}" PasswordChar="*" PlaceholderText="Passphrase wiederholen" Width="300" HorizontalAlignment="Left" />
</StackPanel>
<!-- Inhalt Optionen -->
<CheckBox Content="Protokolle (Logs) mit einschließen" IsChecked="{Binding IncludeLogs}" />
<CheckBox Content="Chatverläufe mit einschließen" IsChecked="{Binding IncludeChatHistory}" />
<Button Content="▶ Sicherung jetzt erstellen" Command="{Binding CreateBackupCommand}"
IsEnabled="{Binding !IsBusy}" Classes="accent" HorizontalAlignment="Left" Margin="0,8,0,0" />
</StackPanel>
</Border>
<!-- Card: Automatische Sicherung -->
<Border Classes="card">
<StackPanel Spacing="12">
<TextBlock Text="Automatische Sicherung" Classes="heading" />
<CheckBox Content="Automatische tägliche Sicherung aktivieren" IsChecked="{Binding AutoBackupEnabled}" />
<StackPanel Orientation="Horizontal" Spacing="16" IsEnabled="{Binding AutoBackupEnabled}">
<StackPanel Orientation="Horizontal" Spacing="8">
<TextBlock Text="Uhrzeit:" VerticalAlignment="Center" />
<TextBox Text="{Binding AutoBackupTime}" Width="80" PlaceholderText="03:00" />
</StackPanel>
<StackPanel Orientation="Horizontal" Spacing="8">
<TextBlock Text="Max. Anzahl behalten:" VerticalAlignment="Center" />
<NumericUpDown Value="{Binding KeepCount}" Minimum="1" Maximum="999" Width="120" />
</StackPanel>
</StackPanel>
</StackPanel>
</Border>
<!-- Vorhandene Sicherungen List -->
<Border Classes="card">
<DockPanel Height="260">
<StackPanel Classes="toolbar" DockPanel.Dock="Top">
<TextBlock Text="Vorhandene Sicherungen" Classes="heading" VerticalAlignment="Center" />
<Button Content="Aktualisieren" Command="{Binding RefreshBackupListCommand}" Margin="12,0,0,0" />
<Button Content="Ordner öffnen" Command="{Binding ShowSelectedInFolderCommand}" IsEnabled="{Binding SelectedBackup, Converter={x:Static ObjectConverters.IsNotNull}}" />
<Button Content="Löschen" Command="{Binding DeleteSelectedCommand}" IsEnabled="{Binding SelectedBackup, Converter={x:Static ObjectConverters.IsNotNull}}" />
<Button Content="Wiederherstellen..." Command="{Binding RestoreSelectedCommand}" IsEnabled="{Binding SelectedBackup, Converter={x:Static ObjectConverters.IsNotNull}}" Classes="accent" />
</StackPanel>
<DataGrid ItemsSource="{Binding Backups}" SelectedItem="{Binding SelectedBackup}" AutoGenerateColumns="False" IsReadOnly="True">
<DataGrid.Columns>
<DataGridTextColumn Header="Dateiname" Binding="{Binding FileName}" Width="220" />
<DataGridTextColumn Header="Erstellt am" Binding="{Binding LastWriteTime, StringFormat='{}{0:g}'}" Width="150" />
<DataGridTextColumn Header="Instanz" Binding="{Binding InstanceName}" Width="130" />
<DataGridTextColumn Header="Größe" Binding="{Binding SizeFormatted}" Width="100" />
<DataGridTextColumn Header="Zugangsdaten" Binding="{Binding SecretsInfo}" Width="*" />
</DataGrid.Columns>
</DataGrid>
</DockPanel>
</Border>
</StackPanel>
</ScrollViewer>
</DockPanel>
</UserControl>
@@ -0,0 +1,56 @@
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Platform.Storage;
using ClawdDotNet.Desktop.ViewModels;
namespace ClawdDotNet.Desktop.Views;
public partial class BackupPageView : UserControl
{
public BackupPageView()
{
InitializeComponent();
}
protected override void OnDataContextChanged(EventArgs e)
{
base.OnDataContextChanged(e);
if (DataContext is BackupPageViewModel vm)
{
vm.RequestRestoreDialog += OnRequestRestoreDialog;
}
}
private async Task<RestoreResultData?> OnRequestRestoreDialog(RestoreBackupViewModel vm)
{
var topLevel = TopLevel.GetTopLevel(this);
if (topLevel is Window window)
{
var dialog = new RestoreBackupWindow { DataContext = vm };
await dialog.ShowDialog(window);
return vm.Result;
}
return null;
}
private async void OnBrowseTargetFolder(object? sender, RoutedEventArgs e)
{
if (DataContext is not BackupPageViewModel vm) return;
var topLevel = TopLevel.GetTopLevel(this);
if (topLevel?.StorageProvider is null) return;
var folders = await topLevel.StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions
{
Title = "Zielordner für Sicherungen wählen",
AllowMultiple = false
});
if (folders.Count > 0 && folders[0].TryGetLocalPath() is { } path)
{
vm.TargetFolder = path;
vm.SaveSettings();
vm.RefreshBackupListCommand.Execute(null);
}
}
}
@@ -0,0 +1,74 @@
<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:vm="using:ClawdDotNet.Desktop.ViewModels"
xmlns:conv="using:ClawdDotNet.Desktop.Converters"
mc:Ignorable="d" d:DesignWidth="900" d:DesignHeight="700"
Padding="16"
x:Class="ClawdDotNet.Desktop.Views.ChatPageView"
x:DataType="vm:ChatPageViewModel">
<DockPanel>
<!-- Statusleiste am unteren Rand -->
<Border Classes="statusbar" DockPanel.Dock="Bottom">
<TextBlock Text="{Binding StatusText}" VerticalAlignment="Center" />
</Border>
<!-- Top Toolbar -->
<StackPanel Classes="toolbar" DockPanel.Dock="Top">
<TextBlock Text="Agent:" VerticalAlignment="Center" FontWeight="Bold" />
<ComboBox ItemsSource="{Binding AvailableAgents}" SelectedItem="{Binding SelectedAgent}" MinWidth="200">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding DisplayName}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<Button Content="Verlauf leeren" Command="{Binding ClearHistoryCommand}" Margin="12,0,0,0" />
<Button Content="Abbrechen" Command="{Binding AbortCommand}" IsEnabled="{Binding IsSending}" />
</StackPanel>
<!-- Eingabezeile unten -->
<Grid DockPanel.Dock="Bottom" ColumnDefinitions="*,Auto" Margin="0,8,0,0">
<TextBox Text="{Binding InputPrompt}" PlaceholderText="Nachricht eingeben... (Strg+Eingabe zum Senden)"
AcceptsReturn="True" Height="60" TextWrapping="Wrap" Grid.Column="0" KeyDown="OnInputKeyDown" />
<Button Content="Senden" Command="{Binding SendMessageCommand}" IsEnabled="{Binding !IsSending}"
Classes="accent" VerticalAlignment="Stretch" Margin="8,0,0,0" Grid.Column="1" Width="90" />
</Grid>
<!-- Nachrichtenverlauf (Mitte) -->
<ScrollViewer x:Name="ChatScroller">
<ItemsControl ItemsSource="{Binding ChatEntries}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Spacing="10" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="vm:ChatEntryItemViewModel">
<!-- Distinct styling: User right-aligned bubble vs Assistant left-aligned card -->
<DockPanel HorizontalAlignment="{Binding IsUser, Converter={x:Static conv:ChatAlignmentConverter.Instance}}">
<Border Classes="card" CornerRadius="8" MaxWidth="680"
Background="{Binding IsUser, Converter={x:Static conv:ChatBubbleBrushConverter.Instance}, TargetNullValue={x:Null}}">
<StackPanel Spacing="4">
<DockPanel>
<TextBlock Text="{Binding Sender}" FontWeight="Bold" FontSize="12" />
<TextBlock Text="{Binding Timestamp, StringFormat='{}{0:HH:mm}'}" Classes="caption" HorizontalAlignment="Right" Margin="8,0,0,0" />
</DockPanel>
<SelectableTextBlock Text="{Binding Text}" TextWrapping="Wrap" FontSize="13" />
</StackPanel>
</Border>
</DockPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</DockPanel>
</UserControl>
@@ -0,0 +1,25 @@
using Avalonia.Controls;
using Avalonia.Input;
using ClawdDotNet.Desktop.ViewModels;
namespace ClawdDotNet.Desktop.Views;
public partial class ChatPageView : UserControl
{
public ChatPageView()
{
InitializeComponent();
}
private void OnInputKeyDown(object? sender, KeyEventArgs e)
{
if (e.Key == Key.Enter && e.KeyModifiers.HasFlag(KeyModifiers.Control))
{
if (DataContext is ChatPageViewModel vm && vm.SendMessageCommand.CanExecute(null))
{
vm.SendMessageCommand.Execute(null);
e.Handled = true;
}
}
}
}
@@ -0,0 +1,71 @@
<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:vm="using:ClawdDotNet.Desktop.ViewModels"
mc:Ignorable="d" d:DesignWidth="900" d:DesignHeight="600"
x:Class="ClawdDotNet.Desktop.Views.InfoPageView"
x:DataType="vm:InfoPageViewModel">
<ScrollViewer Padding="16">
<StackPanel Spacing="16">
<!-- Titel -->
<TextBlock Text="Software- &amp; Instanz-Informationen" Classes="heading" FontSize="20" />
<!-- Anwendungs-Details Card -->
<Border Classes="card">
<StackPanel Spacing="8">
<TextBlock Text="Anwendung" Classes="heading" />
<Grid RowDefinitions="Auto,Auto" ColumnDefinitions="140,*">
<TextBlock Grid.Row="0" Grid.Column="0" Text="Version:" FontWeight="Bold" />
<TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding AppVersion}" />
<TextBlock Grid.Row="1" Grid.Column="0" Text="Build-Summary:" FontWeight="Bold" Margin="0,4,0,0" />
<TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding BuildSummary}" Margin="0,4,0,0" TextWrapping="Wrap" />
</Grid>
</StackPanel>
</Border>
<!-- Instanz-Details Card -->
<Border Classes="card">
<StackPanel Spacing="8">
<TextBlock Text="Aktuelle Instanz" Classes="heading" />
<Grid RowDefinitions="Auto,Auto,Auto,Auto,Auto" ColumnDefinitions="140,*">
<TextBlock Grid.Row="0" Grid.Column="0" Text="Name:" FontWeight="Bold" />
<TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding InstanceName}" />
<TextBlock Grid.Row="1" Grid.Column="0" Text="ID:" FontWeight="Bold" Margin="0,4,0,0" />
<TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding InstanceId}" Margin="0,4,0,0" />
<TextBlock Grid.Row="2" Grid.Column="0" Text="Pfad:" FontWeight="Bold" Margin="0,4,0,0" />
<TextBlock Grid.Row="2" Grid.Column="1" Text="{Binding InstancePath}" Margin="0,4,0,0" TextWrapping="Wrap" />
<TextBlock Grid.Row="3" Grid.Column="0" Text="Log-Ordner:" FontWeight="Bold" Margin="0,4,0,0" />
<TextBlock Grid.Row="3" Grid.Column="1" Text="{Binding LogDirectory}" Margin="0,4,0,0" TextWrapping="Wrap" />
<TextBlock Grid.Row="4" Grid.Column="0" Text="OpenRouter Status:" FontWeight="Bold" Margin="0,4,0,0" />
<TextBlock Grid.Row="4" Grid.Column="1" Text="{Binding OpenRouterStatus}" Margin="0,4,0,0" />
</Grid>
</StackPanel>
</Border>
<!-- Assembly Build Info Card -->
<Border Classes="card">
<StackPanel Spacing="8">
<TextBlock Text="Module &amp; Baugruppen" Classes="heading" />
<DataGrid ItemsSource="{Binding Assemblies}" AutoGenerateColumns="False" IsReadOnly="True" GridLinesVisibility="Horizontal">
<DataGrid.Columns>
<DataGridTextColumn Header="Modul" Binding="{Binding ShortName}" Width="160" />
<DataGridTextColumn Header="Build #" Binding="{Binding BuildNumber}" Width="80" />
<DataGridTextColumn Header="Build-Datum" Binding="{Binding BuildDate}" Width="160" />
<DataGridTextColumn Header="Änderungen" Binding="{Binding Changes}" Width="*" />
</DataGrid.Columns>
</DataGrid>
</StackPanel>
</Border>
</StackPanel>
</ScrollViewer>
</UserControl>
@@ -0,0 +1,11 @@
using Avalonia.Controls;
namespace ClawdDotNet.Desktop.Views;
public partial class InfoPageView : UserControl
{
public InfoPageView()
{
InitializeComponent();
}
}
@@ -0,0 +1,73 @@
<Window 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:vm="using:ClawdDotNet.Desktop.ViewModels"
xmlns:models="using:ClawdDotNet.App.Models"
mc:Ignorable="d" d:DesignWidth="720" d:DesignHeight="460"
x:Class="ClawdDotNet.Desktop.Views.InstancePickerWindow"
x:DataType="vm:InstancePickerViewModel"
Icon="/Assets/app.ico"
Title="ClawdDotNet – Instanz wählen"
Width="720" Height="460"
WindowStartupLocation="CenterScreen">
<DockPanel Margin="16">
<TextBlock DockPanel.Dock="Top" Classes="heading" Text="Instanz wählen" />
<TextBlock DockPanel.Dock="Top" Classes="caption" Margin="0,0,0,12"
TextWrapping="Wrap"
Text="{Binding InstancesDirectory, StringFormat='Verzeichnis: {0}'}" />
<!-- Fehlerzeile: nimmt keinen Platz weg, solange nichts anliegt -->
<TextBlock DockPanel.Dock="Bottom"
Margin="0,8,0,0"
Foreground="{DynamicResource SystemFillColorCriticalBrush}"
TextWrapping="Wrap"
Text="{Binding ErrorText}"
IsVisible="{Binding ErrorText, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" />
<!-- Aktionen -->
<Grid DockPanel.Dock="Bottom" ColumnDefinitions="*,Auto,Auto" Margin="0,12,0,0">
<StackPanel Orientation="Horizontal" Spacing="6" Grid.Column="0">
<TextBox Width="200"
Watermark="Name der neuen Instanz"
Text="{Binding NewInstanceName}" />
<Button Content="Anlegen" Command="{Binding CreateCommand}" />
</StackPanel>
<Button Grid.Column="1" Content="Abbrechen" Margin="0,0,6,0"
Command="{Binding CancelCommand}" />
<Button Grid.Column="2" Content="Starten"
Classes="accent"
IsDefault="True"
Command="{Binding StartCommand}" />
</Grid>
<!-- Liste -->
<DataGrid ItemsSource="{Binding Instances}"
SelectedItem="{Binding Selected}"
AutoGenerateColumns="False"
IsReadOnly="True"
SelectionMode="Single"
GridLinesVisibility="Horizontal">
<DataGrid.Columns>
<DataGridTextColumn Header="Instanzname" Width="2*"
Binding="{Binding InstanceName, x:DataType=models:InstanceInfo}" />
<DataGridTextColumn Header="Ordner" Width="2*"
Binding="{Binding FolderName, x:DataType=models:InstanceInfo}" />
<DataGridTextColumn Header="Agenten" Width="Auto"
Binding="{Binding AgentCount, x:DataType=models:InstanceInfo}" />
<DataGridTextColumn Header="API-Key" Width="Auto"
Binding="{Binding ApiKeyStatus, x:DataType=models:InstanceInfo}" />
</DataGrid.Columns>
</DataGrid>
</DockPanel>
</Window>
@@ -0,0 +1,24 @@
using Avalonia.Controls;
using Avalonia.Input;
using ClawdDotNet.Desktop.ViewModels;
namespace ClawdDotNet.Desktop.Views;
public partial class InstancePickerWindow : Window
{
public InstancePickerWindow()
{
InitializeComponent();
// Doppelklick auf eine Zeile startet — wie in der WinForms-Fassung. Das laesst
// sich nicht sinnvoll als Befehl binden, weil DataGrid kein DoubleTapped je
// Zeile anbietet; deshalb hier und nicht im Ansichtsmodell.
AddHandler(DoubleTappedEvent, OnDoubleTapped, handledEventsToo: false);
}
private void OnDoubleTapped(object? sender, TappedEventArgs e)
{
if (DataContext is InstancePickerViewModel vm && vm.StartCommand.CanExecute(null))
vm.StartCommand.Execute(null);
}
}
@@ -0,0 +1,45 @@
<Window 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:vm="using:ClawdDotNet.Desktop.ViewModels"
mc:Ignorable="d" d:DesignWidth="520" d:DesignHeight="320"
x:Class="ClawdDotNet.Desktop.Views.LicenseWindow"
x:DataType="vm:LicenseViewModel"
Icon="/Assets/app.ico"
Title="ClawdDotNet – Lizenz"
Width="520" SizeToContent="Height"
CanResize="False"
WindowStartupLocation="CenterScreen">
<StackPanel Margin="20" Spacing="12">
<TextBlock Classes="heading" Text="Lizenzschlüssel" />
<!-- Nur sichtbar, wenn es einen Grund gibt -->
<Border Classes="card"
IsVisible="{Binding Problem, Converter={x:Static StringConverters.IsNotNullOrEmpty}}">
<TextBlock Text="{Binding Problem}" TextWrapping="Wrap"
Foreground="{DynamicResource SystemFillColorCriticalBrush}" />
</Border>
<TextBox Watermark="XXXXX-XXXXX-XXXXX-XXXXX-XXXXX"
Text="{Binding Key}"
FontFamily="{DynamicResource ContentControlThemeFontFamily}" />
<StackPanel Spacing="4">
<TextBlock Classes="caption" Text="Hardware-ID (für den Support):" />
<SelectableTextBlock Classes="caption"
Text="{Binding HardwareId}"
TextWrapping="Wrap" />
</StackPanel>
<StackPanel Orientation="Horizontal" Spacing="8" HorizontalAlignment="Right">
<Button Content="Beenden" Command="{Binding CancelCommand}" />
<Button Content="Prüfen" Classes="accent" IsDefault="True"
Command="{Binding ConfirmCommand}" />
</StackPanel>
</StackPanel>
</Window>
@@ -0,0 +1,16 @@
using Avalonia.Controls;
using ClawdDotNet.Desktop.ViewModels;
namespace ClawdDotNet.Desktop.Views;
public partial class LicenseWindow : Window
{
public LicenseWindow()
{
InitializeComponent();
// Wer das Fenster ueber das Kreuz schliesst, hat abgebrochen. Ohne das bliebe
// der wartende Task fuer immer offen und der Start haenge.
Closed += (_, _) => (DataContext as LicenseViewModel)?.CancelIfPending();
}
}
@@ -0,0 +1,67 @@
<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:vm="using:ClawdDotNet.Desktop.ViewModels"
xmlns:svc="using:ClawdDotNet.App.Services"
xmlns:conv="using:ClawdDotNet.Desktop.Converters"
mc:Ignorable="d" d:DesignWidth="900" d:DesignHeight="600"
x:Class="ClawdDotNet.Desktop.Views.LogPageView"
x:DataType="vm:LogPageViewModel">
<DockPanel>
<!-- Werkzeugleiste -->
<StackPanel Classes="toolbar" DockPanel.Dock="Top">
<TextBlock Text="Modul:" VerticalAlignment="Center" />
<ComboBox ItemsSource="{Binding Modules}"
SelectedItem="{Binding SelectedModule}"
MinWidth="180" />
<TextBlock Text="Stufe:" VerticalAlignment="Center" Margin="12,0,0,0" />
<ComboBox ItemsSource="{Binding Levels}"
SelectedItem="{Binding SelectedLevel}"
MinWidth="100" />
<Button Content="Aktualisieren" Command="{Binding RefreshModulesCommand}" Margin="12,0,0,0" />
<Button Content="Leeren" Command="{Binding ClearCommand}" />
<Button Content="Ordner öffnen" Command="{Binding OpenFolderCommand}" />
<CheckBox Content="Mitlaufen" IsChecked="{Binding FollowTail}"
VerticalAlignment="Center" Margin="12,0,0,0" />
</StackPanel>
<!--
SelectableTextBlock je Zeile statt eines grossen TextBlocks: So laesst sich eine
einzelne Zeile herauskopieren, und die Virtualisierung des ItemsControl haelt auch
bei 2000 Zeilen die Darstellung fluessig.
-->
<ScrollViewer Name="Scroller" Padding="8">
<ItemsControl ItemsSource="{Binding Lines}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="svc:LogLine">
<SelectableTextBlock Text="{Binding Text}"
FontFamily="Consolas, Menlo, DejaVu Sans Mono, monospace"
FontSize="12"
TextWrapping="NoWrap"
Foreground="{Binding Level,
Converter={x:Static conv:LogLevelBrushConverter.Instance},
TargetNullValue={x:Null}}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</DockPanel>
</UserControl>
@@ -0,0 +1,21 @@
using Avalonia.Controls;
using ClawdDotNet.Desktop.ViewModels;
namespace ClawdDotNet.Desktop.Views;
public partial class LogPageView : UserControl
{
public LogPageView()
{
InitializeComponent();
// Mitlaufen: Nach jeder neuen Zeile ans Ende springen — aber nur, solange der
// Haken gesetzt ist. Das gehoert in die Ansicht, weil nur sie den ScrollViewer
// kennt; das Ansichtsmodell haelt lediglich den Schalter.
Scroller.ScrollChanged += (_, _) =>
{
if (DataContext is LogPageViewModel { FollowTail: true })
Scroller.ScrollToEnd();
};
}
}
@@ -0,0 +1,72 @@
<Window 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:vm="using:ClawdDotNet.Desktop.ViewModels"
mc:Ignorable="d" d:DesignWidth="1100" d:DesignHeight="700"
x:Class="ClawdDotNet.Desktop.Views.MainWindow"
x:DataType="vm:MainWindowViewModel"
Icon="/Assets/app.ico"
Title="ClawdDotNet"
Width="1100" Height="700"
MinWidth="800" MinHeight="500">
<Design.DataContext>
<vm:MainWindowViewModel />
</Design.DataContext>
<DockPanel>
<!-- Haupt-Menüleiste -->
<Menu DockPanel.Dock="Top">
<MenuItem Header="_Datei">
<MenuItem Header="_Beenden" Command="{Binding ExitCommand}" InputGesture="Alt+F4" />
</MenuItem>
<MenuItem Header="_Bearbeiten">
<MenuItem Header="_Instanz-Ordner öffnen" Command="{Binding OpenInstanceFolderCommand}" />
<MenuItem Header="_Log-Ordner öffnen" Command="{Binding OpenLogFolderCommand}" />
</MenuItem>
<MenuItem Header="_Instanz">
<MenuItem Header="_Instanz-Manager..." Command="{Binding OpenInstanceManagerCommand}" />
</MenuItem>
<MenuItem Header="_Ansicht">
<MenuItem Header="_Chat" Command="{Binding SelectPageByTitleCommand}" CommandParameter="Chat" />
<MenuItem Header="_Logs" Command="{Binding SelectPageByTitleCommand}" CommandParameter="Logs" />
<MenuItem Header="_Agenten" Command="{Binding SelectPageByTitleCommand}" CommandParameter="Agenten" />
<MenuItem Header="_Einstellungen" Command="{Binding SelectPageByTitleCommand}" CommandParameter="Einstellungen" />
<MenuItem Header="_Aufgaben" Command="{Binding SelectPageByTitleCommand}" CommandParameter="Aufgaben" />
<MenuItem Header="_Sicherung" Command="{Binding SelectPageByTitleCommand}" CommandParameter="Sicherung" />
<MenuItem Header="_Info" Command="{Binding SelectPageByTitleCommand}" CommandParameter="Info" />
</MenuItem>
<MenuItem Header="_Hilfe">
<MenuItem Header="_Info" Command="{Binding SelectPageByTitleCommand}" CommandParameter="Info" />
</MenuItem>
</Menu>
<!-- Statusleiste -->
<Border Classes="statusbar" DockPanel.Dock="Bottom">
<TextBlock Classes="caption" Text="{Binding StatusText}" />
</Border>
<!-- Bereiche (Seitenleiste links) -->
<TabControl ItemsSource="{Binding Pages}"
SelectedItem="{Binding SelectedPage}"
TabStripPlacement="Left">
<TabControl.ItemTemplate>
<DataTemplate DataType="vm:PageViewModel">
<TextBlock Text="{Binding Title}" Margin="4,8" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate DataType="vm:PageViewModel">
<ContentControl Content="{Binding}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</DockPanel>
</Window>
@@ -0,0 +1,28 @@
using Avalonia.Controls;
using ClawdDotNet.Desktop.ViewModels;
namespace ClawdDotNet.Desktop.Views;
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
protected override void OnDataContextChanged(EventArgs e)
{
base.OnDataContextChanged(e);
if (DataContext is MainWindowViewModel vm)
{
vm.RequestExit += Close;
vm.RequestInstanceManagerDialog += OnOpenInstanceManager;
}
}
private async void OnOpenInstanceManager()
{
var dialog = new InstancePickerWindow();
await dialog.ShowDialog(this);
}
}
@@ -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:vm="using:ClawdDotNet.Desktop.ViewModels"
mc:Ignorable="d" d:DesignWidth="700" d:DesignHeight="400"
x:Class="ClawdDotNet.Desktop.Views.PlaceholderPageView"
x:DataType="vm:PlaceholderPageViewModel">
<Border Classes="card" Margin="16" VerticalAlignment="Top">
<StackPanel Spacing="6">
<TextBlock Classes="heading" Text="{Binding Title}" />
<TextBlock Classes="caption" Text="{Binding Note}" TextWrapping="Wrap" />
<TextBlock Classes="caption" Margin="0,8,0,0"
Text="Dieser Bereich ist noch nicht portiert." />
</StackPanel>
</Border>
</UserControl>
@@ -0,0 +1,8 @@
using Avalonia.Controls;
namespace ClawdDotNet.Desktop.Views;
public partial class PlaceholderPageView : UserControl
{
public PlaceholderPageView() => InitializeComponent();
}
@@ -0,0 +1,66 @@
<Window 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:vm="using:ClawdDotNet.Desktop.ViewModels"
mc:Ignorable="d" d:DesignWidth="600" d:DesignHeight="400"
Width="640" Height="420"
Padding="16"
WindowStartupLocation="CenterOwner"
CanResize="False"
Title="Sicherung wiederherstellen"
x:Class="ClawdDotNet.Desktop.Views.RestoreBackupWindow"
x:DataType="vm:RestoreBackupViewModel">
<DockPanel>
<!-- Buttons unten -->
<StackPanel DockPanel.Dock="Bottom" Orientation="Horizontal" HorizontalAlignment="Right" Spacing="8" Margin="0,16,0,0">
<Button Content="Abbrechen" Command="{Binding CancelCommand}" />
<Button Content="Wiederherstellen" Command="{Binding ConfirmCommand}" Classes="accent" />
</StackPanel>
<!-- Statusmeldung unten -->
<TextBlock DockPanel.Dock="Bottom" Text="{Binding StatusMessage}" Foreground="Red" Margin="0,8,0,0" TextWrapping="Wrap" />
<ScrollViewer>
<StackPanel Spacing="12">
<!-- Information -->
<Border Classes="card">
<StackPanel Spacing="4">
<TextBlock Text="Sicherungsinformationen" Classes="heading" />
<TextBlock Text="{Binding InstanceName, StringFormat='Instanz: {0}'}" />
<TextBlock Text="{Binding CreatedAt, StringFormat='Erstellt: {0:g}'}" />
<TextBlock Text="{Binding FileCount, StringFormat='Dateien: {0}'}" />
<TextBlock Text="{Binding SecretsDescription, StringFormat='Zugangsdaten: {0}'}" />
</StackPanel>
</Border>
<!-- Zielordner -->
<StackPanel Spacing="4">
<TextBlock Text="Zielordner:" FontWeight="Bold" />
<Grid ColumnDefinitions="*,Auto">
<TextBox Text="{Binding TargetDirectory}" Grid.Column="0" />
<Button Content="Ordner wählen" Click="OnBrowseFolder" Grid.Column="1" Margin="8,0,0,0" />
</Grid>
<TextBlock Text="Hinweis: Es wird empfohlen, in einen neuen Ordner wiederherzustellen, um Konflikte mit laufenden Prozessen zu vermeiden."
Classes="caption" TextWrapping="Wrap" />
</StackPanel>
<!-- Passphrase -->
<StackPanel Spacing="4" IsVisible="{Binding NeedsPassphrase}">
<TextBlock Text="Passphrase:" FontWeight="Bold" />
<TextBox Text="{Binding Passphrase}" PasswordChar="*" />
<TextBlock Text="Diese Sicherung enthält geschützte Zugangsdaten." Classes="caption" />
</StackPanel>
<!-- Überschreiben -->
<CheckBox Content="Vorhandene Dateien im Zielordner überschreiben" IsChecked="{Binding Overwrite}" Margin="0,8,0,0" />
</StackPanel>
</ScrollViewer>
</DockPanel>
</Window>
@@ -0,0 +1,42 @@
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Platform.Storage;
using ClawdDotNet.Desktop.ViewModels;
namespace ClawdDotNet.Desktop.Views;
public partial class RestoreBackupWindow : Window
{
public RestoreBackupWindow()
{
InitializeComponent();
}
protected override void OnDataContextChanged(EventArgs e)
{
base.OnDataContextChanged(e);
if (DataContext is RestoreBackupViewModel vm)
{
vm.CloseRequested += () => Close();
}
}
private async void OnBrowseFolder(object? sender, RoutedEventArgs e)
{
if (DataContext is not RestoreBackupViewModel vm) return;
var storage = StorageProvider;
if (storage is null) return;
var folders = await storage.OpenFolderPickerAsync(new FolderPickerOpenOptions
{
Title = "Zielordner für Wiederherstellung wählen",
AllowMultiple = false
});
if (folders.Count > 0 && folders[0].TryGetLocalPath() is { } path)
{
vm.TargetDirectory = path;
}
}
}
@@ -0,0 +1,100 @@
<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:vm="using:ClawdDotNet.Desktop.ViewModels"
mc:Ignorable="d" d:DesignWidth="900" d:DesignHeight="700"
Padding="16"
x:Class="ClawdDotNet.Desktop.Views.SettingsPageView"
x:DataType="vm:SettingsPageViewModel">
<DockPanel>
<!-- Statusbar am unteren Rand -->
<Border Classes="statusbar" DockPanel.Dock="Bottom">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Spacing="8">
<Button Content="Verwerfen / Zurücksetzen" Command="{Binding LoadFromHostCommand}" />
<Button Content="Speichern" Command="{Binding SaveSettingsCommand}" Classes="accent" />
</StackPanel>
</Border>
<ScrollViewer>
<StackPanel Spacing="16">
<TextBlock Text="Anwendungs- &amp; Instanz-Einstellungen" Classes="heading" FontSize="20" />
<!-- Card: Instanz-Einstellungen -->
<Border Classes="card">
<StackPanel Spacing="12">
<TextBlock Text="Aktuelle Instanz" Classes="heading" />
<Grid ColumnDefinitions="180,*" RowDefinitions="Auto,Auto,Auto,Auto">
<TextBlock Grid.Row="0" Grid.Column="0" Text="Instanz-Name:" FontWeight="Bold" VerticalAlignment="Center" />
<TextBox Grid.Row="0" Grid.Column="1" Text="{Binding InstanceName}" />
<TextBlock Grid.Row="1" Grid.Column="0" Text="OpenRouter API-Key:" FontWeight="Bold" VerticalAlignment="Center" Margin="0,8,0,0" />
<TextBox Grid.Row="1" Grid.Column="1" Text="{Binding OpenRouterApiKey}" PasswordChar="*" Margin="0,8,0,0" />
<TextBlock Grid.Row="2" Grid.Column="0" Text="Tagesbudget ($ USD):" FontWeight="Bold" VerticalAlignment="Center" Margin="0,8,0,0" />
<NumericUpDown Grid.Row="2" Grid.Column="1" Value="{Binding InstanceDailyCostUsd}" Minimum="0" Maximum="10000" FormatString="F2" Width="140" HorizontalAlignment="Left" Margin="0,8,0,0" />
<TextBlock Grid.Row="3" Grid.Column="0" Text="Tagesbudget (Tokens):" FontWeight="Bold" VerticalAlignment="Center" Margin="0,8,0,0" />
<NumericUpDown Grid.Row="3" Grid.Column="1" Value="{Binding InstanceDailyTokens}" Minimum="0" Maximum="100000000" FormatString="0" Width="180" HorizontalAlignment="Left" Margin="0,8,0,0" />
</Grid>
</StackPanel>
</Border>
<!-- Card: Allgemein & Pfade -->
<Border Classes="card">
<StackPanel Spacing="12">
<TextBlock Text="Allgemein &amp; Pfade" Classes="heading" />
<Grid ColumnDefinitions="180,*" RowDefinitions="Auto,Auto,Auto,Auto">
<TextBlock Grid.Row="0" Grid.Column="0" Text="Log-Verzeichnis:" FontWeight="Bold" VerticalAlignment="Center" />
<TextBox Grid.Row="0" Grid.Column="1" Text="{Binding LogDirectory}" />
<TextBlock Grid.Row="1" Grid.Column="0" Text="Instanzen-Verzeichnis:" FontWeight="Bold" VerticalAlignment="Center" Margin="0,8,0,0" />
<TextBox Grid.Row="1" Grid.Column="1" Text="{Binding InstancesDirectory}" Margin="0,8,0,0" />
<TextBlock Grid.Row="2" Grid.Column="0" Text="Minimaler Log-Level:" FontWeight="Bold" VerticalAlignment="Center" Margin="0,8,0,0" />
<ComboBox Grid.Row="2" Grid.Column="1" ItemsSource="{Binding LogLevels}" SelectedItem="{Binding MinimumLogLevel}" Width="140" HorizontalAlignment="Left" Margin="0,8,0,0" />
<TextBlock Grid.Row="3" Grid.Column="0" Text="OpenRouter Base-URL:" FontWeight="Bold" VerticalAlignment="Center" Margin="0,8,0,0" />
<TextBox Grid.Row="3" Grid.Column="1" Text="{Binding OpenRouterBaseUrl}" Margin="0,8,0,0" />
</Grid>
</StackPanel>
</Border>
<!-- Card: Deploymentcenter & Lizenz -->
<Border Classes="card">
<StackPanel Spacing="12">
<TextBlock Text="Deploymentcenter &amp; Lizenz" Classes="heading" />
<Grid ColumnDefinitions="180,*" RowDefinitions="Auto,Auto,Auto,Auto,Auto">
<TextBlock Grid.Row="0" Grid.Column="0" Text="Server-URL:" FontWeight="Bold" VerticalAlignment="Center" />
<TextBox Grid.Row="0" Grid.Column="1" Text="{Binding DeploymentcenterUrl}" />
<TextBlock Grid.Row="1" Grid.Column="0" Text="Master-Token:" FontWeight="Bold" VerticalAlignment="Center" Margin="0,8,0,0" />
<TextBox Grid.Row="1" Grid.Column="1" Text="{Binding DeploymentcenterToken}" PasswordChar="*" Margin="0,8,0,0" />
<TextBlock Grid.Row="2" Grid.Column="0" Text="Update-Kanal:" FontWeight="Bold" VerticalAlignment="Center" Margin="0,8,0,0" />
<ComboBox Grid.Row="2" Grid.Column="1" ItemsSource="{Binding UpdateChannels}" SelectedItem="{Binding UpdateChannel}" Width="140" HorizontalAlignment="Left" Margin="0,8,0,0" />
<TextBlock Grid.Row="3" Grid.Column="0" Text="Umgebung:" FontWeight="Bold" VerticalAlignment="Center" Margin="0,8,0,0" />
<ComboBox Grid.Row="3" Grid.Column="1" ItemsSource="{Binding Environments}" SelectedItem="{Binding DeploymentcenterEnvironment}" Width="160" HorizontalAlignment="Left" Margin="0,8,0,0" />
<TextBlock Grid.Row="4" Grid.Column="0" Text="Lizenzschlüssel:" FontWeight="Bold" VerticalAlignment="Center" Margin="0,8,0,0" />
<TextBox Grid.Row="4" Grid.Column="1" Text="{Binding LicenseKey}" PasswordChar="*" Margin="0,8,0,0" />
</Grid>
<CheckBox Content="Fehler automatisch an Deploymentcenter melden" IsChecked="{Binding ErrorReportingEnabled}" Margin="0,4,0,0" />
<CheckBox Content="Beim Start automatisch auf Updates prüfen" IsChecked="{Binding UpdateCheckEnabled}" />
</StackPanel>
</Border>
</StackPanel>
</ScrollViewer>
</DockPanel>
</UserControl>
@@ -0,0 +1,11 @@
using Avalonia.Controls;
namespace ClawdDotNet.Desktop.Views;
public partial class SettingsPageView : UserControl
{
public SettingsPageView()
{
InitializeComponent();
}
}
@@ -0,0 +1,102 @@
<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:vm="using:ClawdDotNet.Desktop.ViewModels"
mc:Ignorable="d" d:DesignWidth="900" d:DesignHeight="700"
Padding="16"
x:Class="ClawdDotNet.Desktop.Views.TasksPageView"
x:DataType="vm:TasksPageViewModel">
<DockPanel>
<!-- Statusbar unten -->
<Border Classes="statusbar" DockPanel.Dock="Bottom">
<TextBlock Text="{Binding StatusText}" VerticalAlignment="Center" />
</Border>
<TabControl>
<!-- TAB 1: Jobs (Scheduled / Worker) -->
<TabItem Header="Geplante Jobs">
<DockPanel Margin="0,8,0,0">
<!-- Toolbar -->
<StackPanel Classes="toolbar" DockPanel.Dock="Top">
<Button Content="Job hinzufügen" Command="{Binding AddJobCommand}" Classes="accent" />
<Button Content="Bearbeiten" Command="{Binding EditJobCommand}" IsEnabled="{Binding SelectedJob, Converter={x:Static ObjectConverters.IsNotNull}}" />
<Button Content="Entfernen" Command="{Binding RemoveJobCommand}" IsEnabled="{Binding SelectedJob, Converter={x:Static ObjectConverters.IsNotNull}}" />
<Button Content="▶ Jetzt ausführen" Command="{Binding RunJobNowCommand}" IsEnabled="{Binding SelectedJob, Converter={x:Static ObjectConverters.IsNotNull}}" Margin="12,0,0,0" />
<Button Content="Aktualisieren" Command="{Binding RefreshJobListCommand}" />
</StackPanel>
<!-- Jobs DataGrid -->
<DataGrid ItemsSource="{Binding JobEntries}" SelectedItem="{Binding SelectedJob}" AutoGenerateColumns="False" IsReadOnly="True" GridLinesVisibility="Horizontal">
<DataGrid.Columns>
<DataGridTextColumn Header="Typ" Binding="{Binding JobType}" Width="120" />
<DataGridTextColumn Header="Agent" Binding="{Binding AgentName}" Width="150" />
<DataGridTextColumn Header="Tool / Nachricht" Binding="{Binding TaskMessage}" Width="*" />
<DataGridTextColumn Header="Cron-Ausdruck" Binding="{Binding CronExpression}" Width="120" />
<DataGridTextColumn Header="Nächster Lauf" Binding="{Binding NextRun}" Width="140" />
<DataGridTextColumn Header="Letzter Lauf" Binding="{Binding LastRun}" Width="140" />
<DataGridTextColumn Header="Status" Binding="{Binding Status}" Width="100" />
</DataGrid.Columns>
</DataGrid>
</DockPanel>
</TabItem>
<!-- TAB 2: Dienste (Services) -->
<TabItem Header="Dienste (Services)">
<DockPanel Margin="0,8,0,0">
<!-- Toolbar -->
<StackPanel Classes="toolbar" DockPanel.Dock="Top">
<Button Content="Dienst hinzufügen" Command="{Binding AddServiceCommand}" Classes="accent" />
<Button Content="Entfernen" Command="{Binding RemoveServiceCommand}" IsEnabled="{Binding SelectedService, Converter={x:Static ObjectConverters.IsNotNull}}" />
<Button Content="Aktualisieren" Command="{Binding RefreshServiceListCommand}" />
</StackPanel>
<!-- Services DataGrid -->
<DataGrid ItemsSource="{Binding ServiceEntries}" SelectedItem="{Binding SelectedService}" AutoGenerateColumns="False" IsReadOnly="True" GridLinesVisibility="Horizontal">
<DataGrid.Columns>
<DataGridTextColumn Header="Dienstname" Binding="{Binding Name}" Width="180" />
<DataGridTextColumn Header="Typ" Binding="{Binding Type}" Width="100" />
<DataGridTextColumn Header="Port" Binding="{Binding Port}" Width="80" />
<DataGridTextColumn Header="Status" Binding="{Binding Status}" Width="110" />
<DataGridTextColumn Header="Beschreibung" Binding="{Binding Description}" Width="*" />
</DataGrid.Columns>
</DataGrid>
</DockPanel>
</TabItem>
<!-- TAB 3: Verlauf (Job History) -->
<TabItem Header="Verlauf (History)">
<DockPanel Margin="0,8,0,0">
<!-- Toolbar -->
<StackPanel Classes="toolbar" DockPanel.Dock="Top">
<Button Content="Verlauf aktualisieren" Command="{Binding RefreshJobHistoryCommand}" />
</StackPanel>
<!-- History DataGrid -->
<DataGrid ItemsSource="{Binding JobHistoryEntries}" AutoGenerateColumns="False" IsReadOnly="True" GridLinesVisibility="Horizontal">
<DataGrid.Columns>
<DataGridTextColumn Header="Zeitpunkt" Binding="{Binding Time, StringFormat='{}{0:g}'}" Width="140" />
<DataGridTextColumn Header="Job-Name" Binding="{Binding JobName}" Width="140" />
<DataGridTextColumn Header="Agent" Binding="{Binding Agent}" Width="140" />
<DataGridTextColumn Header="Beschreibung" Binding="{Binding JobDescription}" Width="220" />
<DataGridTextColumn Header="Status" Binding="{Binding Status}" Width="100" />
<DataGridTextColumn Header="Details / Info" Binding="{Binding Info}" Width="*" />
</DataGrid.Columns>
</DataGrid>
</DockPanel>
</TabItem>
</TabControl>
</DockPanel>
</UserControl>
@@ -0,0 +1,46 @@
using Avalonia.Controls;
using ClawdDotNet.Desktop.ViewModels;
namespace ClawdDotNet.Desktop.Views;
public partial class TasksPageView : UserControl
{
public TasksPageView()
{
InitializeComponent();
}
protected override void OnDataContextChanged(EventArgs e)
{
base.OnDataContextChanged(e);
if (DataContext is TasksPageViewModel vm)
{
vm.RequestAddJobDialog += OnRequestAddJobDialog;
vm.RequestAddServiceDialog += OnRequestAddServiceDialog;
}
}
private async Task<AddJobResultData?> OnRequestAddJobDialog(AddJobViewModel vm)
{
var topLevel = TopLevel.GetTopLevel(this);
if (topLevel is Window window)
{
var dialog = new AddJobWindow { DataContext = vm };
await dialog.ShowDialog(window);
return vm.Result;
}
return null;
}
private async Task<AddServiceResultData?> OnRequestAddServiceDialog(AddServiceViewModel vm)
{
var topLevel = TopLevel.GetTopLevel(this);
if (topLevel is Window window)
{
var dialog = new AddServiceWindow { DataContext = vm };
await dialog.ShowDialog(window);
return vm.Result;
}
return null;
}
}
@@ -0,0 +1,180 @@
<Window 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:vm="using:ClawdDotNet.Desktop.ViewModels"
xmlns:models="using:ClawdDotNet.Models"
xmlns:conv="using:ClawdDotNet.Desktop.Converters"
mc:Ignorable="d" d:DesignWidth="560" d:DesignHeight="480"
Width="580" Height="500"
Padding="16"
WindowStartupLocation="CenterOwner"
CanResize="False"
Title="Werkzeug-Einstellungen"
x:Class="ClawdDotNet.Desktop.Views.ToolSettingsWindow"
x:DataType="vm:ToolSettingsViewModel">
<DockPanel>
<!-- Bottom Buttons -->
<StackPanel DockPanel.Dock="Bottom" Orientation="Horizontal" HorizontalAlignment="Right" Spacing="8" Margin="0,16,0,0">
<Button Content="Abbrechen" Command="{Binding CancelCommand}" />
<Button Content="Speichern" Command="{Binding ConfirmCommand}" Classes="accent" />
</StackPanel>
<ScrollViewer>
<StackPanel Spacing="12">
<TextBlock Text="{Binding ToolName, StringFormat='Einstellungen für {0}'}" Classes="heading" FontSize="18" />
<!-- Forms for each Tool -->
<StackPanel Spacing="8">
<!-- 1. FTP -->
<Grid ColumnDefinitions="140,*" RowDefinitions="Auto,Auto,Auto,Auto,Auto" IsVisible="{Binding ToolName, Converter={x:Static conv:ToolMatchConverter.Instance}, ConverterParameter=FTP}">
<TextBlock Grid.Row="0" Grid.Column="0" Text="Host:" FontWeight="Bold" VerticalAlignment="Center" />
<TextBox Grid.Row="0" Grid.Column="1" Text="{Binding FtpHost}" PlaceholderText="ftp.example.com" />
<TextBlock Grid.Row="1" Grid.Column="0" Text="Port:" FontWeight="Bold" VerticalAlignment="Center" Margin="0,8,0,0" />
<NumericUpDown Grid.Row="1" Grid.Column="1" Value="{Binding FtpPort}" Minimum="1" Maximum="65535" Width="120" HorizontalAlignment="Left" Margin="0,8,0,0" />
<TextBlock Grid.Row="2" Grid.Column="0" Text="Benutzername:" FontWeight="Bold" VerticalAlignment="Center" Margin="0,8,0,0" />
<TextBox Grid.Row="2" Grid.Column="1" Text="{Binding FtpUsername}" Margin="0,8,0,0" />
<TextBlock Grid.Row="3" Grid.Column="0" Text="Passwort:" FontWeight="Bold" VerticalAlignment="Center" Margin="0,8,0,0" />
<TextBox Grid.Row="3" Grid.Column="1" Text="{Binding FtpPassword}" PasswordChar="*" Margin="0,8,0,0" />
<TextBlock Grid.Row="4" Grid.Column="0" Text="Root-Pfad:" FontWeight="Bold" VerticalAlignment="Center" Margin="0,8,0,0" />
<TextBox Grid.Row="4" Grid.Column="1" Text="{Binding FtpRootPath}" Margin="0,8,0,0" />
</Grid>
<!-- 2. Mail -->
<Grid ColumnDefinitions="140,*" RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto" IsVisible="{Binding ToolName, Converter={x:Static conv:ToolMatchConverter.Instance}, ConverterParameter=Mail}">
<TextBlock Grid.Row="0" Grid.Column="0" Text="Benutzername:" FontWeight="Bold" VerticalAlignment="Center" />
<TextBox Grid.Row="0" Grid.Column="1" Text="{Binding MailUsername}" />
<TextBlock Grid.Row="1" Grid.Column="0" Text="Passwort:" FontWeight="Bold" VerticalAlignment="Center" Margin="0,8,0,0" />
<TextBox Grid.Row="1" Grid.Column="1" Text="{Binding MailPassword}" PasswordChar="*" Margin="0,8,0,0" />
<TextBlock Grid.Row="2" Grid.Column="0" Text="IMAP-Host:" FontWeight="Bold" VerticalAlignment="Center" Margin="0,8,0,0" />
<TextBox Grid.Row="2" Grid.Column="1" Text="{Binding MailImapHost}" Margin="0,8,0,0" />
<TextBlock Grid.Row="3" Grid.Column="0" Text="IMAP-Port:" FontWeight="Bold" VerticalAlignment="Center" Margin="0,8,0,0" />
<NumericUpDown Grid.Row="3" Grid.Column="1" Value="{Binding MailImapPort}" Minimum="1" Maximum="65535" Width="120" HorizontalAlignment="Left" Margin="0,8,0,0" />
<TextBlock Grid.Row="4" Grid.Column="0" Text="SMTP-Host:" FontWeight="Bold" VerticalAlignment="Center" Margin="0,8,0,0" />
<TextBox Grid.Row="4" Grid.Column="1" Text="{Binding MailSmtpHost}" Margin="0,8,0,0" />
<TextBlock Grid.Row="5" Grid.Column="0" Text="SMTP-Port:" FontWeight="Bold" VerticalAlignment="Center" Margin="0,8,0,0" />
<NumericUpDown Grid.Row="5" Grid.Column="1" Value="{Binding MailSmtpPort}" Minimum="1" Maximum="65535" Width="120" HorizontalAlignment="Left" Margin="0,8,0,0" />
<TextBlock Grid.Row="6" Grid.Column="0" Text="Erlaubte Empfänger:" FontWeight="Bold" VerticalAlignment="Center" Margin="0,8,0,0" />
<TextBox Grid.Row="6" Grid.Column="1" Text="{Binding MailAllowedRecipients}" PlaceholderText="email1@domain.de, email2@domain.de" Margin="0,8,0,0" />
</Grid>
<!-- 3. Database -->
<Grid ColumnDefinitions="140,*" RowDefinitions="Auto,Auto,Auto,Auto" IsVisible="{Binding ToolName, Converter={x:Static conv:ToolMatchConverter.Instance}, ConverterParameter=Database}">
<TextBlock Grid.Row="0" Grid.Column="0" Text="Typ:" FontWeight="Bold" VerticalAlignment="Center" />
<ComboBox Grid.Row="0" Grid.Column="1" ItemsSource="{Binding DbTypes}" SelectedItem="{Binding DbType}" HorizontalAlignment="Stretch" />
<TextBlock Grid.Row="1" Grid.Column="0" Text="Connection-String:" FontWeight="Bold" VerticalAlignment="Center" Margin="0,8,0,0" />
<TextBox Grid.Row="1" Grid.Column="1" Text="{Binding DbConnectionString}" PasswordChar="*" Margin="0,8,0,0" />
<TextBlock Grid.Row="2" Grid.Column="0" Text="Zugriffsebene:" FontWeight="Bold" VerticalAlignment="Center" Margin="0,8,0,0" />
<ComboBox Grid.Row="2" Grid.Column="1" ItemsSource="{Binding DbAccessLevels}" SelectedItem="{Binding DbAccessLevel}" HorizontalAlignment="Stretch" Margin="0,8,0,0" />
<TextBlock Grid.Row="3" Grid.Column="0" Text="Erlaubte Tabellen:" FontWeight="Bold" VerticalAlignment="Center" Margin="0,8,0,0" />
<TextBox Grid.Row="3" Grid.Column="1" Text="{Binding DbAllowedTables}" PlaceholderText="tabelle1, tabelle2" Margin="0,8,0,0" />
</Grid>
<!-- 4. Telegram -->
<Grid ColumnDefinitions="140,*" RowDefinitions="Auto,Auto,Auto" IsVisible="{Binding ToolName, Converter={x:Static conv:ToolMatchConverter.Instance}, ConverterParameter=Telegram}">
<TextBlock Grid.Row="0" Grid.Column="0" Text="Bot-Token:" FontWeight="Bold" VerticalAlignment="Center" />
<TextBox Grid.Row="0" Grid.Column="1" Text="{Binding TelegramBotToken}" PasswordChar="*" />
<TextBlock Grid.Row="1" Grid.Column="0" Text="Standard Chat-ID:" FontWeight="Bold" VerticalAlignment="Center" Margin="0,8,0,0" />
<TextBox Grid.Row="1" Grid.Column="1" Text="{Binding TelegramDefaultChatId}" Margin="0,8,0,0" />
<TextBlock Grid.Row="2" Grid.Column="0" Text="Erlaubte Chat-IDs:" FontWeight="Bold" VerticalAlignment="Center" Margin="0,8,0,0" />
<TextBox Grid.Row="2" Grid.Column="1" Text="{Binding TelegramAllowedChatIds}" PlaceholderText="id1, id2" Margin="0,8,0,0" />
</Grid>
<!-- 5. DirectAPI -->
<Grid ColumnDefinitions="140,*" RowDefinitions="Auto,Auto,Auto,Auto" IsVisible="{Binding ToolName, Converter={x:Static conv:ToolMatchConverter.Instance}, ConverterParameter=DirectAPI}">
<TextBlock Grid.Row="0" Grid.Column="0" Text="Standard-Provider:" FontWeight="Bold" VerticalAlignment="Center" />
<TextBox Grid.Row="0" Grid.Column="1" Text="{Binding DirectApiDefaultProvider}" />
<TextBlock Grid.Row="1" Grid.Column="0" Text="Cache TTL (Sek.):" FontWeight="Bold" VerticalAlignment="Center" Margin="0,8,0,0" />
<NumericUpDown Grid.Row="1" Grid.Column="1" Value="{Binding DirectApiCacheTtlSeconds}" Minimum="0" Maximum="86400" Width="120" HorizontalAlignment="Left" Margin="0,8,0,0" />
<TextBlock Grid.Row="2" Grid.Column="0" Text="Twelve Data Key:" FontWeight="Bold" VerticalAlignment="Center" Margin="0,8,0,0" />
<TextBox Grid.Row="2" Grid.Column="1" Text="{Binding DirectApiTwelveDataKey}" PasswordChar="*" Margin="0,8,0,0" />
<TextBlock Grid.Row="3" Grid.Column="0" Text="Alpha Vantage Key:" FontWeight="Bold" VerticalAlignment="Center" Margin="0,8,0,0" />
<TextBox Grid.Row="3" Grid.Column="1" Text="{Binding DirectApiAlphaVantageKey}" PasswordChar="*" Margin="0,8,0,0" />
</Grid>
<!-- 6. WebFetch -->
<Grid ColumnDefinitions="140,*" RowDefinitions="Auto,Auto,Auto" IsVisible="{Binding ToolName, Converter={x:Static conv:ToolMatchConverter.Instance}, ConverterParameter=WebFetch}">
<TextBlock Grid.Row="0" Grid.Column="0" Text="Erlaubte Domains:" FontWeight="Bold" VerticalAlignment="Center" />
<TextBox Grid.Row="0" Grid.Column="1" Text="{Binding WebFetchAllowedDomains}" PlaceholderText="reuters.com, bloomberg.com" />
<TextBlock Grid.Row="1" Grid.Column="0" Text="Max Response KB:" FontWeight="Bold" VerticalAlignment="Center" Margin="0,8,0,0" />
<NumericUpDown Grid.Row="1" Grid.Column="1" Value="{Binding WebFetchMaxResponseKb}" Minimum="1" Maximum="10000" Width="120" HorizontalAlignment="Left" Margin="0,8,0,0" />
<TextBlock Grid.Row="2" Grid.Column="0" Text="User Agent:" FontWeight="Bold" VerticalAlignment="Center" Margin="0,8,0,0" />
<TextBox Grid.Row="2" Grid.Column="1" Text="{Binding WebFetchUserAgent}" Margin="0,8,0,0" />
</Grid>
<!-- 7. SocialMediaManager -->
<Grid ColumnDefinitions="160,*" RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto" IsVisible="{Binding ToolName, Converter={x:Static conv:ToolMatchConverter.Instance}, ConverterParameter=SocialMediaManager}">
<TextBlock Grid.Row="0" Grid.Column="0" Text="X (Twitter) API-Key:" FontWeight="Bold" VerticalAlignment="Center" />
<TextBox Grid.Row="0" Grid.Column="1" Text="{Binding SmmXApiKey}" PasswordChar="*" />
<TextBlock Grid.Row="1" Grid.Column="0" Text="X Accounts:" FontWeight="Bold" VerticalAlignment="Center" Margin="0,8,0,0" />
<TextBox Grid.Row="1" Grid.Column="1" Text="{Binding SmmXWatchAccounts}" PlaceholderText="elonmusk, unusual_whales" Margin="0,8,0,0" />
<TextBlock Grid.Row="2" Grid.Column="0" Text="Subreddits:" FontWeight="Bold" VerticalAlignment="Center" Margin="0,8,0,0" />
<TextBox Grid.Row="2" Grid.Column="1" Text="{Binding SmmRedditWatchSubreddits}" PlaceholderText="wallstreetbets, stocks" Margin="0,8,0,0" />
<TextBlock Grid.Row="3" Grid.Column="0" Text="Reddit Post Limit:" FontWeight="Bold" VerticalAlignment="Center" Margin="0,8,0,0" />
<NumericUpDown Grid.Row="3" Grid.Column="1" Value="{Binding SmmRedditPostLimit}" Minimum="1" Maximum="100" Width="120" HorizontalAlignment="Left" Margin="0,8,0,0" />
<TextBlock Grid.Row="4" Grid.Column="0" Text="OpenRouter Key:" FontWeight="Bold" VerticalAlignment="Center" Margin="0,8,0,0" />
<TextBox Grid.Row="4" Grid.Column="1" Text="{Binding SmmOpenRouterApiKey}" PasswordChar="*" Margin="0,8,0,0" />
<TextBlock Grid.Row="5" Grid.Column="0" Text="STT Modell:" FontWeight="Bold" VerticalAlignment="Center" Margin="0,8,0,0" />
<TextBox Grid.Row="5" Grid.Column="1" Text="{Binding SmmSttModel}" Margin="0,8,0,0" />
<TextBlock Grid.Row="6" Grid.Column="0" Text="YouTube Kanäle:" FontWeight="Bold" VerticalAlignment="Center" Margin="0,8,0,0" />
<TextBox Grid.Row="6" Grid.Column="1" Text="{Binding SmmYoutubeChannels}" PlaceholderText="URL1, URL2" Margin="0,8,0,0" />
</Grid>
<!-- 8. FileRW -->
<Grid ColumnDefinitions="180,*" RowDefinitions="Auto,Auto,Auto,Auto" IsVisible="{Binding ToolName, Converter={x:Static conv:ToolMatchConverter.Instance}, ConverterParameter=FileRW}">
<TextBlock Grid.Row="0" Grid.Column="0" Text="Personal Allowed Ext:" FontWeight="Bold" VerticalAlignment="Center" />
<TextBox Grid.Row="0" Grid.Column="1" Text="{Binding FileRwPersonalAllowedExtensions}" />
<TextBlock Grid.Row="1" Grid.Column="0" Text="Shared Access Level:" FontWeight="Bold" VerticalAlignment="Center" Margin="0,8,0,0" />
<ComboBox Grid.Row="1" Grid.Column="1" ItemsSource="{Binding FileRwAccessLevels}" SelectedItem="{Binding FileRwSharedAccessLevel}" HorizontalAlignment="Stretch" Margin="0,8,0,0" />
<TextBlock Grid.Row="2" Grid.Column="0" Text="Shared Allowed Ext:" FontWeight="Bold" VerticalAlignment="Center" Margin="0,8,0,0" />
<TextBox Grid.Row="2" Grid.Column="1" Text="{Binding FileRwSharedAllowedExtensions}" Margin="0,8,0,0" />
<TextBlock Grid.Row="3" Grid.Column="0" Text="Geschützte Pfade:" FontWeight="Bold" VerticalAlignment="Center" Margin="0,8,0,0" />
<TextBox Grid.Row="3" Grid.Column="1" Text="{Binding FileRwProtectedPaths}" Margin="0,8,0,0" />
</Grid>
<!-- Fallback info for tools without extra config -->
<TextBlock Text="Dieses Werkzeug benötigt keine weiteren Einstellungen."
IsVisible="{Binding SettingsObject, Converter={x:Static ObjectConverters.IsNull}}"
FontStyle="Italic" />
</StackPanel>
</StackPanel>
</ScrollViewer>
</DockPanel>
</Window>
@@ -0,0 +1,21 @@
using Avalonia.Controls;
using ClawdDotNet.Desktop.ViewModels;
namespace ClawdDotNet.Desktop.Views;
public partial class ToolSettingsWindow : Window
{
public ToolSettingsWindow()
{
InitializeComponent();
}
protected override void OnDataContextChanged(EventArgs e)
{
base.OnDataContextChanged(e);
if (DataContext is ToolSettingsViewModel vm)
{
vm.CloseRequested += () => Close();
}
}
}