Dashboard-Performance, CI-Workflow, Settings-Aufteilung, Backup-Härtung

- Dashboard: Fehlzeiten-Warnung lädt Mitarbeitssitzungen einmal vorab
  statt pro Schüler/Eintrag einzeln nachzuschlagen (N+1 vermieden);
  neues IParticipationSessionRepository.GetAll() dafür.
- CI: .gitea/workflows/ci.yml baut und testet bei jedem Push/PR.
  Dabei fehlende Release|Any CPU-Konfiguration für 6 Projekte in der
  .sln behoben (LehrerApp.Data.Tests wurde bei Release-Builds der
  Solution bislang stillschweigend übersprungen). TreatWarningsAsErrors
  jetzt aktiv.
- SettingsViewModel (1986 Zeilen) als partial class auf 20 Themen-
  dateien aufgeteilt, Verhalten unverändert.
- Backup: optionaler zweiter Sicherungsordner (USB-Stick/Netzlaufwerk,
  best-effort) und Integritätsprüfung nach jedem Backup
  (DatabaseEncryptionService.CanOpenAndRead).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-30 00:52:44 +02:00
co-authored by Claude Sonnet 5
parent dbd8777e64
commit 2b299fc940
37 changed files with 2547 additions and 1823 deletions
@@ -619,6 +619,19 @@
<TextBlock Text="{Binding BackupStatus}" Foreground="Green" FontSize="12"
IsVisible="{Binding BackupStatus, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>
<StackPanel Spacing="6" Margin="0,6,0,0">
<TextBlock Text="Zweiter Sicherungsordner (optional)" FontSize="13" FontWeight="SemiBold"/>
<TextBlock Text="Zusätzliches Ziel für jedes Backup, z. B. ein USB-Stick oder Netzlaufwerk — ist es beim Sichern nicht erreichbar, bleibt das Hauptbackup davon unberührt."
FontSize="12" Opacity="0.6" TextWrapping="Wrap"/>
<TextBlock Text="{Binding SecondaryBackupDirectory}" FontSize="12"
IsVisible="{Binding SecondaryBackupDirectory, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>
<StackPanel Orientation="Horizontal" Spacing="8">
<Button Content="Ordner wählen…" Command="{Binding PickBackupDirectoryCommand}"/>
<Button Content="Entfernen" Command="{Binding ClearBackupDirectoryCommand}"
IsVisible="{Binding SecondaryBackupDirectory, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>
</StackPanel>
</StackPanel>
<ItemsControl ItemsSource="{Binding Backups}">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="vm:BackupListItem">
@@ -34,6 +34,7 @@ public partial class SettingsView : UserControl
vm.OnThemeChanged = App.ApplyTheme;
vm.OnReviewUntisMapping = ShowUntisMappingReviewDialog;
vm.OnPickHomeroomClass = PickHomeroomClassAsync;
vm.OnPickBackupDirectory = PickBackupDirectoryAsync;
_ = vm.LoadSchoolLocationCommand.ExecuteAsync(null);
}
}
@@ -84,6 +85,19 @@ public partial class SettingsView : UserControl
return true;
}
private async Task<string?> PickBackupDirectoryAsync()
{
var topLevel = TopLevel.GetTopLevel(this);
if (topLevel is null) return null;
var folders = await topLevel.StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions
{
Title = "Zweiten Sicherungsordner wählen",
AllowMultiple = false,
});
return folders.Count == 0 ? null : folders[0].Path.LocalPath;
}
private async Task<string?> PickRecoveryFile()
{
var topLevel = TopLevel.GetTopLevel(this);