Files
EngineeringSync/EngineeringSync.TrayApp/Views/ProjectManagementWindow.xaml
EngineeringSync 04ae8a0aae Initial commit: EngineeringSync v1.0.0
Vollständige Implementierung des EngineeringSync-Middleware-Tools:
- Windows Service (Kestrel :5050) mit FileSystemWatcher + SignalR
- WPF Tray-App mit PendingChanges- und Projektverwaltungs-Fenster
- Setup-Wizard (8-Schritte-Installer)
- SQLite/EF Core Datenschicht (WAL-Modus)
- SHA-256-basiertes Debouncing (2s Fenster)
- Backup-System mit konfigurierbarer Aufbewahrung

Bugfixes & Verbesserungen:
- BUG-1: AppDbContext OnConfiguring invertierte Bedingung behoben
- BUG-2: Event-Handler-Leak in TrayApp (Fenster-Singleton-Pattern)
- BUG-3: ProjectConfigChanged SignalR-Signal in allen CRUD-Endpoints
- BUG-5: Rename-Sync löscht alte Datei im Simulations-Ordner
- BUG-6: Doppeltes Dispose von SignalR verhindert
- BUG-7: Registry-Deinstallation nur EngineeringSync-Eintrag entfernt
- S1: Path-Traversal-Schutz via SafeCombine() im SyncManager
- E1: FSW Buffer 64KB + automatischer Re-Scan bei Overflow
- E2: Retry-Logik (3x) für gesperrte Dateien mit exponentiellem Backoff
- E4: Channel.Writer.TryComplete() beim Shutdown
- C2: HubMethodNames-Konstanten statt Magic Strings
- E3: Pagination in Changes-API (page/pageSize Query-Parameter)
- A1: Fire-and-Forget mit try/catch + Logging

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-26 21:52:26 +01:00

216 lines
12 KiB
XML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<Window x:Class="EngineeringSync.TrayApp.Views.ProjectManagementWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:conv="clr-namespace:EngineeringSync.TrayApp.Converters"
Title="Projektverwaltung EngineeringSync"
Height="800" Width="800"
WindowStartupLocation="CenterScreen"
Background="#F5F5F5">
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVisConverter"/>
<conv:NewEditHeaderConverter x:Key="NewEditHeaderConverter"/>
<conv:StringToVisibilityConverter x:Key="StringToVisConverter"/>
<conv:InverseBoolToVisibilityConverter x:Key="InverseBoolToVisConverter"/>
</Window.Resources>
<Grid Margin="12">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="350" />
</Grid.ColumnDefinitions>
<!-- Linke Spalte: Projektliste -->
<Grid Grid.Column="0" Margin="0,0,12,0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="Überwachte Projekte" FontWeight="Bold" FontSize="14" Margin="0,0,0,8"/>
<ListView Grid.Row="1" ItemsSource="{Binding Projects}"
SelectedItem="{Binding SelectedProject}"
Background="White" BorderBrush="#DDDDDD">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Margin="4">
<TextBlock Text="{Binding Name}" FontWeight="SemiBold" />
<TextBlock Text="{Binding EngineeringPath}" FontSize="11" Foreground="Gray" />
<StackPanel Orientation="Horizontal">
<TextBlock Text="Aktiv: " FontSize="11" Foreground="Gray"/>
<TextBlock Text="{Binding IsActive}" FontSize="11" Foreground="Gray"/>
</StackPanel>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<StackPanel Grid.Row="2" Orientation="Horizontal" Margin="0,8,0,0">
<Button Content="+ Neues Projekt" Padding="10,6" Margin="0,0,8,0"
Command="{Binding StartNewProjectCommand}" />
<Button Content="Bearbeiten" Padding="10,6" Margin="0,0,8,0"
Command="{Binding EditProjectCommand}"
CommandParameter="{Binding SelectedProject}" />
<Button Content="Löschen" Padding="10,6" Foreground="Red"
Command="{Binding DeleteCommand}"
CommandParameter="{Binding SelectedProject}" />
</StackPanel>
</Grid>
<!-- Rechte Spalte: Editierformular -->
<Border Grid.Column="1"
Background="White"
BorderBrush="#DDDDDD"
BorderThickness="1"
CornerRadius="4"
Padding="16"
Visibility="{Binding IsEditing, Converter={StaticResource BoolToVisConverter}}">
<StackPanel>
<TextBlock Text="{Binding IsNewProject, Converter={StaticResource NewEditHeaderConverter}}"
FontWeight="Bold" FontSize="14" Margin="0,0,0,16"/>
<TextBlock Text="Name:" Margin="0,0,0,4" FontWeight="SemiBold"/>
<TextBox Text="{Binding EditName, UpdateSourceTrigger=PropertyChanged}"
Margin="0,0,0,12" Padding="6"/>
<TextBlock Text="Engineering-Pfad:" Margin="0,0,0,4" FontWeight="SemiBold"/>
<DockPanel Margin="0,0,0,12">
<Button DockPanel.Dock="Right" Content="..." Width="32" Margin="4,0,0,0"
Click="BrowseEngineering_Click"/>
<TextBox Text="{Binding EditEngineeringPath, UpdateSourceTrigger=PropertyChanged}" Padding="6"/>
</DockPanel>
<TextBlock Text="Simulations-Pfad:" Margin="0,0,0,4" FontWeight="SemiBold"/>
<DockPanel Margin="0,0,0,12">
<Button DockPanel.Dock="Right" Content="..." Width="32" Margin="4,0,0,0"
Click="BrowseSimulation_Click"/>
<TextBox Text="{Binding EditSimulationPath, UpdateSourceTrigger=PropertyChanged}" Padding="6"/>
</DockPanel>
<TextBlock Text="Dateiendungen (komma-getrennt):" Margin="0,0,0,4" FontWeight="SemiBold"/>
<TextBox Text="{Binding EditFileExtensions, UpdateSourceTrigger=PropertyChanged}"
Margin="0,0,0,12" Padding="6">
<TextBox.Style>
<Style TargetType="TextBox">
<Style.Triggers>
<Trigger Property="Text" Value="">
<Setter Property="Background">
<Setter.Value>
<VisualBrush AlignmentX="Left" AlignmentY="Center" Stretch="None">
<VisualBrush.Visual>
<TextBlock Text="z.B. .jt,.cojt (leer = alle Dateien)" Foreground="#999999" Margin="6,0" FontSize="11"/>
</VisualBrush.Visual>
</VisualBrush>
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
<CheckBox Content="Aktiv (Watcher läuft)"
IsChecked="{Binding EditIsActive}"
Margin="0,0,0,16"/>
<!-- ORDNER-PRÜFUNG -->
<Border Background="#E3F2FD" BorderBrush="#90CAF9" BorderThickness="1"
CornerRadius="4" Padding="12" Margin="0,0,0,16">
<StackPanel>
<TextBlock Text="Unterschiede prüfen" FontWeight="SemiBold" Margin="0,0,0,6"/>
<TextBlock Text="Prüft beide Ordner und zeigt Unterschiede vor dem Speichern."
FontSize="11" Foreground="#555555" Margin="0,0,0,8" TextWrapping="Wrap"/>
<Button Content="Ordner prüfen..." Padding="12,6"
Command="{Binding ScanFoldersCommand}"
HorizontalAlignment="Left"/>
<TextBlock Text="{Binding ScanStatus}" FontSize="11" Foreground="#1976D2"
Margin="0,4,0,0" TextWrapping="Wrap"
Visibility="{Binding ScanStatus, Converter={StaticResource StringToVisConverter}}"/>
</StackPanel>
</Border>
<!-- Scan-Ergebnisse -->
<ItemsControl ItemsSource="{Binding ScanResults}" Margin="0,0,0,16"
Visibility="{Binding HasScanResults, Converter={StaticResource BoolToVisConverter}}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Background="#FFF8E1" BorderBrush="#FFD54F" BorderThickness="1"
CornerRadius="4" Padding="8" Margin="0,4">
<StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding ChangeType}" FontWeight="SemiBold"
Foreground="#F57C00" Width="70"/>
<TextBlock Text="{Binding RelativePath}" FontWeight="SemiBold"/>
</StackPanel>
<TextBlock FontSize="11" Foreground="#666666">
<Run Text="{Binding Size, StringFormat='{}{0:N0} Bytes'}"/>
<Run Text=" • "/>
<Run Text="{Binding LastModified, StringFormat='yyyy-MM-dd HH:mm'}"/>
</TextBlock>
</StackPanel>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<!-- BACKUP-Sektion -->
<Separator Margin="0,8,0,12"/>
<TextBlock Text="BACKUP" FontSize="10" FontWeight="Bold" Foreground="#888888"
Margin="0,0,0,8"/>
<CheckBox Content="Backups vor dem Überschreiben erstellen"
IsChecked="{Binding EditBackupEnabled}"
Margin="0,0,0,10"/>
<StackPanel Visibility="{Binding EditBackupEnabled,
Converter={StaticResource BoolToVisConverter}}">
<TextBlock Text="Speicherort:" FontWeight="SemiBold" Margin="0,0,0,6"/>
<!-- Mode=OneWay: gegenseitiger Ausschluss läuft über GroupName + TwoWay am zweiten Radio -->
<RadioButton GroupName="BackupLocation" Content="Gleicher Ordner wie Simulationsdatei"
IsChecked="{Binding EditBackupUseCustomPath,
Converter={StaticResource InverseBoolToVisConverter},
Mode=OneWay}"
Margin="0,0,0,4"/>
<RadioButton GroupName="BackupLocation" Content="Eigener Backup-Ordner"
IsChecked="{Binding EditBackupUseCustomPath, Mode=TwoWay}"
Margin="0,0,0,6"/>
<DockPanel Margin="0,0,0,10"
Visibility="{Binding EditBackupUseCustomPath,
Converter={StaticResource BoolToVisConverter}}">
<Button DockPanel.Dock="Right" Content="..." Width="32" Margin="4,0,0,0"
Click="BrowseBackup_Click"/>
<TextBox Text="{Binding EditBackupCustomPath, UpdateSourceTrigger=PropertyChanged}"
Padding="6"/>
</DockPanel>
<StackPanel Orientation="Horizontal" Margin="0,0,0,4">
<TextBlock Text="Max. Backups pro Datei:" FontWeight="SemiBold"
VerticalAlignment="Center" Margin="0,0,8,0"/>
<TextBox Width="50" Padding="4,2"
Text="{Binding EditMaxBackupsPerFile, UpdateSourceTrigger=PropertyChanged}"
VerticalContentAlignment="Center"/>
<TextBlock Text="(0 = unbegrenzt)" FontSize="11" Foreground="Gray"
VerticalAlignment="Center" Margin="6,0,0,0"/>
</StackPanel>
</StackPanel>
<TextBlock Text="{Binding StatusMessage}" Foreground="OrangeRed"
TextWrapping="Wrap" Margin="0,0,0,8"
Visibility="{Binding StatusMessage, Converter={StaticResource StringToVisConverter}}"/>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
<Button Content="Abbrechen" Padding="10,6" Margin="0,0,8,0"
Command="{Binding CancelCommand}" />
<Button Content="Speichern" Padding="10,6"
Background="#0078D4" Foreground="White" BorderThickness="0"
Command="{Binding SaveCommand}" />
</StackPanel>
</StackPanel>
</Border>
</Grid>
</Window>