feat(deploy): TrueNAS Custom-App + Auto-Deploy, plus aufgelaufene Arbeit
Some checks failed
CI / Backend Tests (.NET) (push) Successful in 1m11s
CI / Frontend Tests (Node/Vite) (push) Failing after 4m59s
CI / Docker Build & Push (push) Has been skipped
CI / Deploy auf TrueNAS (Custom App) (push) Has been skipped

Deployment:
- custom-app.compose.yaml: self-contained Compose fuer TrueNAS "Custom App"
  (absolute Host-Bind-Pfade, postgres:18, pull_policy always, Port 8090)
- scripts/truenas-deploy.sh: Host-Skript create/redeploy via midclt (App
  bleibt unter Apps sichtbar) inkl. Image-Pull + Health-Check
- ci.yml Deploy-Job: laeuft auf ubuntu-latest-Runner, kopiert Deploy-Dateien
  per SSH auf den NAS-Host und triggert truenas-deploy.sh (statt runs-on goldeye)
- compose.yaml/.env.example: postgres:18 (Locale-Match zur Quell-DB), Port 8090
- .gitignore: .agents/, tools/rag/, deploy/truenas/.env (Secrets/Scratch)

Aufgelaufene Feature-Arbeit (verified/Freeze, Migrationen, Import-Triage):
- GerbilOverride/VerifiedGerbil-Endpoints + GerbilSnapshotService + Tests
- EF-Migrationen (ShowInChronicle, Stillborn, BirthOrder, ManualFlag, DSGVO)
- Frontend VerifizierteTierePage + verified-API + e2e-Spec
- diverse Import-/Triage-Skripte und -Tests

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-19 09:19:11 +02:00
parent 84365bba7f
commit 45b8533f18
93 changed files with 20477 additions and 432 deletions

View File

@@ -133,6 +133,12 @@ export const de = {
parentLittersEmpty: 'Keine Würfe als Elternteil erfasst.',
parentLittersRoleVater: 'Vater',
parentLittersRoleMutter: 'Mutter',
// WURFCHRONIK-FILTER: Wurf ist nicht in der Wurfchronik (showInChronicle=false),
// wird daher nur hier (am Elterntier) gezeigt, nicht in der Wurf-Liste.
parentLittersNotInChronicle: 'Nicht in der Wurfchronik',
offspringInBreedingTitle: 'Nachzucht in der Zucht',
offspringInBreedingEmpty: 'Keine Nachkommen dieses Tiers sind in die Zucht gegangen.',
// Hinweis: Eltern dieses Tiers sind Vollgeschwister (gleicher Wurf).
siblingPairing: 'Geschwisterverpaarung',
siblingPairingTitle:
@@ -141,6 +147,13 @@ export const de = {
// ausgeblendet, da das Tier nie im eigenen Bestand war.
nonResidentNote:
'Externes Tier — nur als Vorfahre erfasst. Würfe und Charakter werden nicht angezeigt, da es nie im eigenen Bestand war.',
// Vorfahren-Nachzucht-Zeile: statt Wurf-Karten nur ein Hinweis auf die Nachzucht.
nonResidentOffspringLabel: 'Nachzucht dieses Vorfahren:',
// Positives „Zuchttier“-Kennzeichen (Tier hatte eigene Würfe in der Zucht).
zuchttierBadge: '★ Zuchttier',
zuchttierBadgeTitle: 'Dieses Tier wurde bei dir in der Zucht eingesetzt (hatte eigene Würfe).',
// Zeile „Zuchtpartner“ in den Stammdaten (bisherige Verpaarungspartner).
zuchtpartner: 'Zuchtpartner',
},
// Formular (anlegen/bearbeiten)
form: {
@@ -151,9 +164,9 @@ export const de = {
ownBreeding: 'Eigene Zucht',
genotypeHint:
'Optional. Format z. B. „Aa CC Dd EE GG Pp Spsp rere“. Unbekannte Allele als „-“.',
// BESTAND-FILTER: Zugehörigkeit zum eigenen Bestand (sonst externe Ahne)
isResidentLabel: 'Gehört zum eigenen Bestand',
isResidentHint: 'Abwählen für externe Ahnen, die nur für den Stammbaum erfasst sind.',
// BESTAND-FILTER: Zuchtanwärter/in (eigenes Zuchttier) vs. reine Ahne
isResidentLabel: 'Zuchtanwärter/innen',
isResidentHint: 'Abwählen für reine Vorfahren, die nur für den Stammbaum erfasst sind und keine eigenen Zuchttiere waren.',
isDeafUnknown: 'Unbekannt',
isDeafYes: 'Ja',
isDeafNo: 'Nein',
@@ -218,6 +231,8 @@ export const de = {
sort: {
dateDesc: 'Wurfdatum (neueste)',
dateAsc: 'Wurfdatum (älteste)',
nameAsc: 'Alphabetisch (A-Z)',
nameDesc: 'Alphabetisch (Z-A)',
},
// Felder
fields: {
@@ -227,7 +242,10 @@ export const de = {
mother: 'Mutter',
totalBorn: 'Wurfstärke',
deathsWithin8Weeks: 'In den ersten 8 Wochen verstorben',
stillborn: 'Totgeburten',
expectedGoHomeDate: 'Voraussichtliches Abgabedatum',
socializeDate: 'Vergesellschaftung (ab 8 Wochen)',
groupGoHomeDate: 'Abgabe als Gruppe (ab 10 Wochen)',
notes: 'Notizen',
},
// Detail / Arbeitsbereich
@@ -1164,6 +1182,92 @@ export const de = {
button: 'Zum RennmausPro-Import',
},
},
// ── GEPRÜFTE TIERE: „vollständig korrekt"-Markierung + Schutz manueller Änderungen ──
// Ziel laienverständlich erklären: eine geprüfte Maus wird vom automatischen Import
// nicht mehr verändert; so wird der Bestand Tier für Tier sauber.
verified: {
// Karte auf der Rennmausakte
sectionTitle: 'Prüfstatus & Schutz',
explainIntro:
'Wenn bei dieser Maus alle Angaben stimmen, markiere sie als „vollständig korrekt". ' +
'Der automatische Import verändert sie dann nicht mehr — deine geprüften Angaben bleiben erhalten. ' +
'So wird dein Bestand nach und nach sauber, ohne dass der Import raten muss.',
explainProtected:
'Diese Maus enthält von dir geänderte Angaben. Diese sind vor dem Import geschützt und bleiben erhalten. ' +
'Felder, die du nicht angefasst hast, werden weiterhin automatisch aus dem Import verbessert.',
verifiedBadge: '✓ Vollständig korrekt',
verifiedBadgeTitle: 'Geprüft — der automatische Import verändert diese Maus nicht mehr.',
protectedBadge: '🔒 Geschützte Änderungen',
protectedBadgeTitle: 'Enthält von dir geänderte Angaben, die der Import nicht überschreibt.',
driftBadge: '⚠ Weicht vom Import ab',
driftBadgeTitle: 'Der automatische Import liefert für einige Felder andere Werte als dein geprüfter Stand.',
markAction: 'Als vollständig korrekt markieren',
unmarkAction: 'Markierung entfernen',
releaseAction: 'Schutz aufheben (wieder aus Import)',
marking: 'Wird gespeichert …',
markSuccess: 'Als vollständig korrekt markiert. Der Import verändert diese Maus nicht mehr.',
unmarkSuccess: 'Markierung entfernt. Beim nächsten Import wird die Maus wieder automatisch aktualisiert.',
error: 'Konnte nicht gespeichert werden.',
unmarkConfirm:
'Markierung wirklich entfernen? Die aktuellen Werte bleiben stehen; beim nächsten Import wird diese Maus wieder automatisch aktualisiert.',
releaseConfirm:
'Schutz wirklich aufheben? Beim nächsten Import wird diese Maus wieder komplett aus dem Import übernommen — deine Änderungen gehen dann verloren.',
statusUnchanged: 'Der Import stimmt mit deinem geprüften Stand überein.',
driftHeading: 'Abweichungen zum automatischen Import',
driftHint:
'So sieht es der Import gerade. Ist dein Stand richtig, kannst du das ignorieren — oder Julian passt den Import an. Deine geprüften Werte bleiben so oder so erhalten.',
colField: 'Feld',
colGolden: 'Dein geprüfter Stand',
colImport: 'Automatischer Import',
// Übersichtsseite (/hilfe/verifizierte-tiere)
pageTitle: 'Geprüfte Tiere',
pageSubtitle: 'Alle Mäuse, die du als „vollständig korrekt" markiert oder von Hand geändert hast.',
pageIntro1:
'Hier siehst du alle Mäuse, deren Angaben du geprüft und markiert hast. Diese werden vom automatischen Import nicht mehr verändert — dein Bestand wird so Tier für Tier sauber.',
pageIntro2:
'„Weicht vom Import ab" heißt: der Import liefert inzwischen andere Werte als dein geprüfter Stand — ein Hinweis, dass am Import noch etwas zu korrigieren ist. Deine Angaben bleiben trotzdem erhalten.',
empty: 'Noch keine Mäuse als vollständig korrekt markiert.',
loadError: 'Die geprüften Tiere konnten nicht geladen werden.',
sectionVerified: 'Vollständig korrekt',
sectionProtected: 'Geschützte Änderungen (von Hand bearbeitet)',
openAkte: 'Zur Akte',
protectedFieldsLabel: 'Geschützte Felder:',
warningsHeading: 'Hinweise',
statusLabels: {
unchanged: 'Unverändert',
drifted: 'Weicht vom Import ab',
missing: 'Im Import nicht mehr vorhanden',
} as Record<string, string>,
// Link auf der Hilfe-Seite
hilfeLink: 'Geprüfte Tiere (vollständig korrekt)',
// Feld-Bezeichnungen für Diff-/Schutz-Anzeige (Schlüssel = Backend-Feldnamen).
fieldLabels: {
name: 'Name',
gender: 'Geschlecht',
dateOfBirth: 'Geburtsdatum',
dateOfDeath: 'Todesdatum',
causeOfDeath: 'Todesursache',
goHomeDate: 'Abgabedatum',
genotype: 'Genotyp',
spottingType: 'Scheckung',
colorName: 'Farbschlag',
colorVarietyId: 'Farbschlag',
isResident: 'Zuchtanwärter/in',
isDeaf: 'Gehörlos',
isCastrated: 'Kastrat',
originBreeder: 'Herkunft (Zucht)',
originContact: 'Herkunft (Kontakt)',
originContactId: 'Herkunft (Kontakt)',
receiverContact: 'Abnehmer',
receiverContactId: 'Abnehmer',
notes: 'Notizen',
characterTraits: 'Charakter-Merkmale',
characterNote: 'Charakter-Notiz',
birthLitter: 'Geburtswurf',
father: 'Vater',
mother: 'Mutter',
} as Record<string, string>,
},
api: {
errors: {
network: 'Verbindung zum Server fehlgeschlagen. Bitte später erneut versuchen.',