uüd
This commit is contained in:
parent
d96deacf9f
commit
cf2e875968
4 changed files with 448 additions and 8 deletions
|
|
@ -1,5 +1,5 @@
|
|||
<!-- status: canonical -->
|
||||
<!-- lastReviewed: 2026-04-10 -->
|
||||
<!-- lastReviewed: 2026-04-13 -->
|
||||
<!-- verifiedAgainst: frontend_nyla (codebase audit 2026-04-07, post Automation Unification) -->
|
||||
|
||||
# Frontend Nyla -- Architektur
|
||||
|
|
@ -32,7 +32,7 @@ Ergänzend typische Root-Dateien und Bereiche im Repo: `main.tsx`, `App.tsx`, `a
|
|||
| Komponente | Zweck |
|
||||
|------------|-------|
|
||||
| `UnifiedDataBar (UDB)` | Multi-Tab-Panel: Chats, Files, Sources — wird in Workspace, CommCoach und Graphical Editor genutzt |
|
||||
| `FormGenerator` | Dynamische Formulare/Tabellen aus Backend-Attribut-Definitionen |
|
||||
| `FormGenerator` | Dynamische Formulare/Tabellen aus Backend-Attribut-Definitionen (siehe [formgenerator.md](formgenerator.md)) |
|
||||
| `FolderTree` | Rekursiver Ordnerbaum mit Drag&Drop, Multi-Selection, Inline-Editing |
|
||||
| `MandateNavigation` | Feature-Baum-Navigation mit Mandanten-Kontext |
|
||||
| `Automation2FlowEditor` | Konsolidierter n8n-style Flow-Builder (Graphical Editor) mit 3-Column-Layout: [UDB + Chat/Tracing] [Canvas + Header] [Nodes] |
|
||||
|
|
|
|||
208
b-reference/frontend-nyla/formgenerator.md
Normal file
208
b-reference/frontend-nyla/formgenerator.md
Normal file
|
|
@ -0,0 +1,208 @@
|
|||
<!-- status: canonical -->
|
||||
<!-- lastReviewed: 2026-04-13 -->
|
||||
<!-- verifiedAgainst: frontend_nyla (FormGeneratorTable.tsx, FormGeneratorControls.tsx) + gateway routeHelpers.py -->
|
||||
|
||||
# FormGenerator -- Referenz
|
||||
|
||||
## Uebersicht
|
||||
|
||||
Der FormGenerator besteht aus mehreren Komponenten:
|
||||
|
||||
| Komponente | Datei | Zweck |
|
||||
|------------|-------|-------|
|
||||
| `FormGeneratorTable` | `components/FormGenerator/FormGeneratorTable/FormGeneratorTable.tsx` | Generische Datentabelle mit Pagination, Sortierung, Filterung, Suche, Selektion, Bulk-Actions |
|
||||
| `FormGeneratorControls` | `components/FormGenerator/FormGeneratorControls/FormGeneratorControls.tsx` | Toolbar: Suche, Pagination, Batch-Action-Buttons, "Select All Filtered"-Banner |
|
||||
| `FormGeneratorForm` | `components/FormGenerator/FormGeneratorForm/FormGeneratorForm.tsx` | Dynamische Formulare aus Backend-Attribut-Definitionen |
|
||||
| `FormGeneratorList` | `components/FormGenerator/FormGeneratorList/FormGeneratorList.tsx` | Listen-Darstellung |
|
||||
|
||||
Diese Referenz fokussiert auf **FormGeneratorTable** und das zugehoerige Backend-API-Pattern.
|
||||
|
||||
---
|
||||
|
||||
## FormGeneratorTable -- Props
|
||||
|
||||
### Datenanbindung
|
||||
|
||||
| Prop | Typ | Beschreibung |
|
||||
|------|-----|-------------|
|
||||
| `data` | `T[]` | Anzuzeigende Datensaetze (aktuelle Seite) |
|
||||
| `columns` | `ColumnConfig[]` | Spaltendefinitionen (key, label, type, sortable, filterable, searchable, filterOptions, width) |
|
||||
| `apiEndpoint` | `string?` | Backend-Endpunkt fuer automatische Filter-/ID-Abfragen |
|
||||
| `hookData` | `{ refetch, pagination, fetchFilterValues? }` | Daten-Hook mit Refetch-Callback und Pagination-Metadata |
|
||||
| `supportsBackendPagination` | `boolean` | Aktiviert serverseitige Pagination, Sortierung und Filterung |
|
||||
|
||||
### Interaktion
|
||||
|
||||
| Prop | Typ | Beschreibung |
|
||||
|------|-----|-------------|
|
||||
| `selectable` | `boolean` | Aktiviert Zeilen-Selektion (Checkboxen) |
|
||||
| `onSelectionChange` | `(selectedIds: Set<string>) => void` | Callback bei Aenderung der Selektion |
|
||||
| `idField` | `string` | Feld fuer eindeutige ID (Default: `"id"`) |
|
||||
| `isRowSelectable` | `(row: T) => boolean` | Bestimmt, ob eine Zeile selektierbar ist |
|
||||
| `batchActions` | `BatchAction[]` | Bulk-Aktionen fuer selektierte Zeilen |
|
||||
| `actionButtons` | `ActionButton[]` | Zeilen-Aktionen (Edit, Delete etc.) |
|
||||
|
||||
### BatchAction Interface
|
||||
|
||||
```typescript
|
||||
interface BatchAction<T> {
|
||||
label: string;
|
||||
icon?: React.ReactNode;
|
||||
loading?: boolean;
|
||||
onClick: (selectedRows: T[]) => void | Promise<void>;
|
||||
isApplicable?: (row: T) => boolean;
|
||||
}
|
||||
```
|
||||
|
||||
`isApplicable` ermoeglicht pro Action zu bestimmen, welche der selektierten Zeilen fuer diese Aktion qualifizieren. Der Button zeigt dann `(applicableCount/totalSelected)` und ist `disabled` wenn `applicableCount === 0`.
|
||||
|
||||
---
|
||||
|
||||
## Unified Filter API
|
||||
|
||||
### Prinzip
|
||||
|
||||
Alle Daten-Endpunkte unterstuetzen drei Modi ueber Query-Parameter auf dem **gleichen** Endpunkt:
|
||||
|
||||
| Modus | Query-Parameter | Response | Zweck |
|
||||
|-------|----------------|----------|-------|
|
||||
| Normal (default) | `pagination={...}` | `{ items: T[], pagination: PaginationMetadata }` | Paginierte Datenliste |
|
||||
| `filterValues` | `mode=filterValues&column=xxx&pagination={crossFilters}` | `string[]` | Distinct-Werte fuer Spaltenfilter-Dropdown |
|
||||
| `ids` | `mode=ids&pagination={filters}` | `string[]` | Alle IDs der gefilterten Datensaetze |
|
||||
|
||||
**Kein separater `/filter-values`-Endpunkt.** Alle alten `/filter-values`-Sub-Pfade wurden entfernt (Stand 2026-04-13).
|
||||
|
||||
### Cross-Filtering
|
||||
|
||||
Bei `mode=filterValues` werden alle aktiven Filter **ausser** dem angeforderten Column-Filter angewendet (Cross-Filtering). So zeigt z.B. der Status-Filter nur Werte an, die bei den aktuell gesetzten Mandant- und Typ-Filtern vorkommen.
|
||||
|
||||
### Beispiele
|
||||
|
||||
```
|
||||
# Normale Liste (Seite 1, 20 Eintraege)
|
||||
GET /api/users/?pagination={"page":1,"pageSize":20,"sort":[{"field":"name","direction":"asc"}]}
|
||||
|
||||
# Filter-Werte fuer Spalte "status" (mit Cross-Filter auf mandateLabel)
|
||||
GET /api/users/?mode=filterValues&column=status&pagination={"filters":{"mandateLabel":"Demo AG"}}
|
||||
|
||||
# Alle IDs der gefilterten Ansicht (fuer "Select All Filtered")
|
||||
GET /api/users/?mode=ids&pagination={"filters":{"status":"active"},"search":"admin"}
|
||||
```
|
||||
|
||||
### Backend-Implementierung
|
||||
|
||||
Zentrale Hilfsfunktionen in `gateway/modules/routes/routeHelpers.py`:
|
||||
|
||||
| Funktion | Zweck |
|
||||
|----------|-------|
|
||||
| `handleFilterValuesMode(db, modelClass, column, ...)` | SQL-basierte Distinct-Werte via DB-Connector |
|
||||
| `handleIdsMode(db, modelClass, ...)` | SQL-basierte ID-Liste via DB-Connector |
|
||||
| `handleFilterValuesInMemory(items, column, ...)` | In-Memory Distinct-Werte (fuer enriched/aggregierte Listen) |
|
||||
| `handleIdsInMemory(items, ...)` | In-Memory ID-Liste |
|
||||
| `parseCrossFilterPagination(column, paginationJson)` | Parsed Pagination-JSON und entfernt den angeforderten Column-Filter |
|
||||
| `_applyFiltersAndSort(items, paginationParams)` | In-Memory Filterung und Sortierung |
|
||||
| `_extractDistinctValues(items, column, ...)` | Distinct-Werte aus Item-Liste extrahieren |
|
||||
| `paginateInMemory(items, paginationParams)` | In-Memory Paginierung |
|
||||
|
||||
### Route-Integration Pattern
|
||||
|
||||
```python
|
||||
@router.get("/", response_model=PaginatedResponse[MyModel])
|
||||
def list_items(
|
||||
request: Request,
|
||||
pagination: Optional[str] = Query(None),
|
||||
mode: Optional[str] = Query(None, description="'filterValues' or 'ids'"),
|
||||
column: Optional[str] = Query(None, description="Column key"),
|
||||
context: RequestContext = Depends(getRequestContext),
|
||||
):
|
||||
if mode in ("filterValues", "ids"):
|
||||
from modules.routes.routeHelpers import handleFilterValuesInMemory, handleIdsInMemory
|
||||
items = _buildFullList(context)
|
||||
if mode == "filterValues":
|
||||
if not column:
|
||||
raise HTTPException(status_code=400, detail="column required")
|
||||
return handleFilterValuesInMemory(items, column, pagination)
|
||||
return handleIdsInMemory(items, pagination)
|
||||
|
||||
# Normal paginated list...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Selektion
|
||||
|
||||
### ID-basierte Selektion
|
||||
|
||||
Die Selektion arbeitet mit **IDs** (nicht Zeilen-Indizes). Das `idField`-Prop bestimmt, welches Feld als eindeutiger Schluessel verwendet wird (Default: `"id"`).
|
||||
|
||||
| State | Typ | Beschreibung |
|
||||
|-------|-----|-------------|
|
||||
| `selectedIds` | `Set<string>` | Menge der selektierten IDs |
|
||||
|
||||
### Selektion-Reset
|
||||
|
||||
Die Selektion wird automatisch zurueckgesetzt bei:
|
||||
- Seitenwechsel (currentPage)
|
||||
- Seitengroesse-Aenderung (pageSize)
|
||||
- Filter-Aenderung
|
||||
- Suchbegriff-Aenderung
|
||||
- Sortier-Aenderung
|
||||
|
||||
### Select All Filtered
|
||||
|
||||
Wenn `apiEndpoint` und `supportsBackendPagination` gesetzt sind, zeigt die Toolbar einen "Alle X Eintraege auswaehlen"-Button. Dieser ruft `GET {apiEndpoint}?mode=ids&pagination={currentFilters}` auf und fuegt alle zurueckgegebenen IDs zur Selektion hinzu.
|
||||
|
||||
Ein "Auswahl aufheben"-Button erscheint, wenn "Select All Filtered" aktiv ist.
|
||||
|
||||
### Row-Level Delete
|
||||
|
||||
Wenn eine Zeile geloescht wird (via `actionButtons` Delete), wird deren ID automatisch aus `selectedIds` entfernt.
|
||||
|
||||
---
|
||||
|
||||
## FilterValuesList (Spaltenfilter-Dropdown)
|
||||
|
||||
Wenn ein filterbarer Spalten-Header geklickt wird, laedt das Dropdown die Distinct-Werte:
|
||||
|
||||
1. `column.filterOptions` (statische Enum) -- wird direkt verwendet, kein Backend-Call
|
||||
2. `hookData.fetchFilterValues(columnKey, crossFilters)` -- falls im Hook bereitgestellt
|
||||
3. `GET {apiEndpoint}?mode=filterValues&column=xxx&pagination={currentFilters}` -- automatisch
|
||||
|
||||
Bei mehr als 10 Werten erscheint ein **Suchfeld** (Client-seitige Filterung der geladenen Werte).
|
||||
|
||||
Boolean-Spalten rendern als "Ja"/"Nein". Datum-Spalten rendern als Range-Picker.
|
||||
|
||||
---
|
||||
|
||||
## Migrierte Endpunkte (Stand 2026-04-13)
|
||||
|
||||
Alle folgenden Endpunkte unterstuetzen `mode=filterValues` und `mode=ids`:
|
||||
|
||||
### Core Routes
|
||||
|
||||
| Route-Datei | Endpunkt | Methode |
|
||||
|-------------|----------|---------|
|
||||
| `routeDataUsers.py` | `GET /api/users/` | SQL + In-Memory (je nach Scope) |
|
||||
| `routeDataConnections.py` | `GET /api/connections/` | In-Memory |
|
||||
| `routeDataPrompts.py` | `GET /api/prompts/` | In-Memory |
|
||||
| `routeInvitations.py` | `GET /api/invitations/` | In-Memory |
|
||||
| `routeSubscription.py` | `GET /api/subscriptions/admin/all` | In-Memory |
|
||||
| `routeAdminFeatures.py` | `GET /api/admin/features/instances` | In-Memory |
|
||||
| `routeAdminFeatures.py` | `GET /api/admin/features/templates/roles` | In-Memory |
|
||||
| `routeAdminFeatures.py` | `GET /api/admin/features/instances/{id}/users` | In-Memory |
|
||||
| `routeAdminRbacRules.py` | `GET /api/admin/rbac/roles` | In-Memory |
|
||||
| `routeDataMandates.py` | `GET /api/mandates/` | SQL + In-Memory (je nach Scope) |
|
||||
| `routeDataMandates.py` | `GET /api/mandates/{id}/users` | In-Memory |
|
||||
| `routeDataFiles.py` | `GET /api/files/list` | SQL + In-Memory Fallback |
|
||||
| `routeWorkflowDashboard.py` | `GET /api/automation/dashboard/` (Runs) | Enriched + SQL |
|
||||
| `routeWorkflowDashboard.py` | `GET /api/automation/dashboard/workflows` | Enriched + SQL |
|
||||
| `routeBilling.py` | `GET /api/billing/view/users/transactions` | Billing-Interface |
|
||||
|
||||
### Feature Routes
|
||||
|
||||
| Route-Datei | Endpunkt | Methode |
|
||||
|-------------|----------|---------|
|
||||
| `routeFeatureTrustee.py` | `GET /api/trustee/{id}/documents` | RBAC SQL + In-Memory Fallback |
|
||||
| `routeFeatureTrustee.py` | `GET /api/trustee/{id}/positions` | RBAC SQL + In-Memory Fallback |
|
||||
| `routeFeatureRealEstate.py` | `GET /api/realestate/{id}/projects` | In-Memory |
|
||||
| `routeFeatureRealEstate.py` | `GET /api/realestate/{id}/parcels` | In-Memory |
|
||||
228
c-work/1-plan/2026-04-database-health-and-data-cleanup.md
Normal file
228
c-work/1-plan/2026-04-database-health-and-data-cleanup.md
Normal file
|
|
@ -0,0 +1,228 @@
|
|||
<!-- status: plan -->
|
||||
<!-- started: 2026-04-13 -->
|
||||
<!-- component: gateway | frontend-nyla -->
|
||||
|
||||
# Database Health and Data Cleanup — Admin Page
|
||||
|
||||
## Beschreibung und Kontext
|
||||
|
||||
SysAdmin-Seite unter Admin > System > "Datenbank-Gesundheit" mit zwei Views:
|
||||
|
||||
1. **Tabellenstatistiken** — Row Count, Size (MB), Index Size, Last Vacuum/Analyze fuer alle Tabellen in allen Datenbanken
|
||||
2. **Orphan Cleanup** — Generische Erkennung verwaister Datensaetze (FK-Referenzen auf nicht-existierende Parent-Records) mit Cleanup-Buttons
|
||||
|
||||
**Business-Treiber:** Beim Entfernen von Demo-Sets (und generell beim Loeschen von Mandanten/Features) bleiben verwaiste Daten im System. Aktuell gibt es keine Sicht auf den Zustand der Datenbanken und keine Moeglichkeit, Orphans systematisch zu erkennen und zu bereinigen.
|
||||
|
||||
**Abhaengigkeiten:**
|
||||
- FormGeneratorTable mit Batch-Handling (wird parallel implementiert)
|
||||
- Bestehende Interface-Architektur (interfaceDb*, interfaceFeature*)
|
||||
|
||||
**Risiko bei Nicht-Umsetzung:** Verwaiste Daten akkumulieren sich ueber die Zeit, belegen Speicher, und koennen zu Inkonsistenzen fuehren (z.B. Workflows ohne Mandate, Feature-Configs ohne Feature-Instanzen).
|
||||
|
||||
## Fokus und kritische Details
|
||||
|
||||
### Dynamische DB-Registry (Selbst-Registrierung)
|
||||
|
||||
Jede Datenbank registriert sich **selbst** in ihrem Interface (`interfaceDb*.py` oder `interfaceFeature*.py`). Es gibt **keine** statische Liste aller Datenbanken. Neue DBs werden automatisch erkannt, entfernte DBs verschwinden.
|
||||
|
||||
**Pattern:** Jedes Interface ruft beim Import oder bei der Initialisierung `_registerDatabase("poweron_xyz")` auf. Die Registry sammelt alle registrierten DBs dynamisch.
|
||||
|
||||
### Deklaratives FK-Registry auf Model-Ebene
|
||||
|
||||
Jedes `*Id`-Feld das eine echte FK-Beziehung darstellt, bekommt ein `fk_target` in `json_schema_extra`:
|
||||
|
||||
```python
|
||||
mandateId: str = Field(
|
||||
...,
|
||||
json_schema_extra={
|
||||
...,
|
||||
"fk_target": {"db": "poweron_app", "table": "Mandate"},
|
||||
},
|
||||
)
|
||||
```
|
||||
|
||||
Felder die **keine** DB-FK sind (Stripe-IDs, logische IDs, Graph-Node-IDs, polymorphe `referenceId`) bekommen **kein** `fk_target`.
|
||||
|
||||
### Cross-DB Orphan Detection
|
||||
|
||||
Viele FK-Beziehungen gehen ueber Datenbank-Grenzen hinweg (z.B. `AutoWorkflow.featureInstanceId` in `poweron_graphicaleditor` referenziert `FeatureInstance` in `poweron_app`). Der Scanner muss Parent-IDs aus der Ziel-DB laden und dann im Child-DB pruefen.
|
||||
|
||||
### Performance
|
||||
|
||||
- **Stats:** `pg_stat_user_tables` + `pg_total_relation_size` sind PostgreSQL-Katalog-Queries (kein Table-Scan). Ueber ~14 DBs mit je 5-15 Tabellen dauert der Call wenige Sekunden.
|
||||
- **Orphans:** Cross-DB-Checks koennen bei grossen Tabellen langsam sein. Ergebnisse werden gecacht (5 Min TTL).
|
||||
- **`n_live_tup`** ist ein Schaetzwert (aktualisiert nach ANALYZE/VACUUM). Fuer ein Health-Dashboard ausreichend.
|
||||
|
||||
## Ziel und Nicht-Ziele
|
||||
|
||||
**Ziel:**
|
||||
- Dynamische DB-Registry die sich aus den Interfaces selbst befuellt
|
||||
- Auto-Registry fuer PowerOnModel-Subklassen (Tabellenname = Klassenname)
|
||||
- `fk_target` Annotationen auf allen echten FK-Feldern
|
||||
- Generischer Orphan-Scanner der FK-Graph aus Model-Metadaten aufbaut
|
||||
- Tabellenstatistiken via PostgreSQL-Katalog
|
||||
- SysAdmin-UI mit zwei Tabs (Stats + Orphans) und Clean-Buttons
|
||||
- Navigation-Eintrag unter Admin > System
|
||||
|
||||
**Explizit NICHT:**
|
||||
- Keine automatische Bereinigung (nur manuell per Button)
|
||||
- Keine Schema-Migration oder Aenderung an bestehenden DB-Constraints
|
||||
- Kein Monitoring/Alerting (nur On-Demand-Scan)
|
||||
- Keine Aenderung an bestehenden Interface-Initialisierungen (nur Registrierungs-Call hinzufuegen)
|
||||
|
||||
## Betroffene Module
|
||||
|
||||
- **Gateway:** `modules/shared/dbRegistry.py` (neu), `modules/shared/fkRegistry.py` (neu), `modules/system/databaseHealth.py` (neu), `modules/routes/routeAdminDatabaseHealth.py` (neu), `modules/datamodels/datamodelBase.py` (Model-Registry), ~20 Datamodel-Dateien (fk_target Annotationen), ~13 Interface-Dateien (DB-Registrierung)
|
||||
- **Frontend:** `AdminDatabaseHealthPage.tsx` (neu), Routing-Eintrag
|
||||
- **DB-Migration:** nein
|
||||
- **Andere Komponenten:** keine
|
||||
|
||||
## Entscheidungen
|
||||
|
||||
| Datum | Entscheidung | Begruendung |
|
||||
|-------|-------------|------------|
|
||||
| 2026-04-13 | Dynamische DB-Registry statt statischer Liste | Interfaces registrieren sich selbst — bei Add/Remove von DBs muss die Registry nicht manuell gepflegt werden |
|
||||
| 2026-04-13 | `fk_target` in `json_schema_extra` statt DB-Constraints als Quelle | Ermoeglicht Cross-DB Orphan Detection (z.B. Greenfield → App DB); DB-Constraints existieren nur in poweron_app |
|
||||
| 2026-04-13 | `__init_subclass__` fuer Model-Registry | Automatisch, kein manuelles Registrieren noetig; jede PowerOnModel-Subklasse ist sofort im Registry |
|
||||
| 2026-04-13 | `pg_stat_user_tables` fuer Row Counts statt `SELECT COUNT(*)` | Schnell (Katalog-Query), ausreichend genau fuer Health-Dashboard |
|
||||
| 2026-04-13 | SysAdmin-only Zugriff | Datenbank-Gesundheit ist eine System-Funktion, nicht mandantenspezifisch |
|
||||
|
||||
## Umsetzungs-Checkliste
|
||||
|
||||
### Phase 1: Infrastruktur (Backend)
|
||||
|
||||
**LLM: Opus 4.6** (Architektur-Entscheidungen, Cross-File-Refactoring)
|
||||
|
||||
- [ ] **DB-Registry** (`modules/shared/dbRegistry.py`)
|
||||
- `registerDatabase(dbName: str, configPrefix: str = "DB")` — oeffentliche API, registriert eine DB
|
||||
- `_getRegisteredDatabases() -> Dict[str, str]` — intern, gibt alle registrierten DBs zurueck
|
||||
- `_getConnectorForDb(dbName: str) -> DatabaseConnector` — intern, Factory fuer Connector
|
||||
- Thread-safe (Lock oder atomare Operationen)
|
||||
- `registerDatabase` ohne `_` Prefix weil explizit als oeffentliche API fuer andere Module gedacht
|
||||
|
||||
- [ ] **Selbst-Registrierung in allen Interfaces** (~13 Dateien)
|
||||
- Jedes `interfaceDb*.py` und `interfaceFeature*.py` ruft `registerDatabase()` auf Modul-Ebene auf
|
||||
- Kein Umbau der bestehenden Connector-Logik — nur ein zusaetzlicher Registrierungs-Call
|
||||
|
||||
- [ ] **Model-Registry** (`modules/datamodels/datamodelBase.py`)
|
||||
- `_MODEL_REGISTRY: Dict[str, Type[PowerOnModel]]` — automatisch via `__init_subclass__`
|
||||
- `_getModelByTableName(tableName: str) -> Optional[Type[PowerOnModel]]` — Lookup
|
||||
|
||||
### Phase 2: FK-Annotationen (Backend)
|
||||
|
||||
**LLM: Composer Fast** (repetitive Annotation-Arbeit, klares Pattern)
|
||||
|
||||
- [ ] **fk_target auf allen FK-Feldern** (~80-90 Felder in ~20 Dateien)
|
||||
- `modules/datamodels/datamodelMembership.py` — UserMandate, FeatureAccess, Junctions
|
||||
- `modules/datamodels/datamodelRbac.py` — Role, AccessRule
|
||||
- `modules/datamodels/datamodelFeatures.py` — FeatureInstance
|
||||
- `modules/datamodels/datamodelChat.py` — ChatWorkflow, ChatMessage, ChatLog, ChatDocument
|
||||
- `modules/datamodels/datamodelFiles.py` — FileItem, FileFolder
|
||||
- `modules/datamodels/datamodelKnowledge.py` — FileContentIndex, ContentChunk, WorkflowMemory
|
||||
- `modules/datamodels/datamodelBilling.py` — BillingAccount, BillingTransaction, BillingSettings
|
||||
- `modules/datamodels/datamodelSubscription.py` — MandateSubscription
|
||||
- `modules/datamodels/datamodelInvitation.py` — Invitation
|
||||
- `modules/datamodels/datamodelDataSource.py` — DataSource
|
||||
- `modules/datamodels/datamodelFeatureDataSource.py` — FeatureDataSource
|
||||
- `modules/datamodels/datamodelUam.py` — Token, AuthEvent, UserConnection, UserVoicePreferences, UserNotification
|
||||
- `modules/features/graphicalEditor/datamodelFeatureGraphicalEditor.py` — AutoWorkflow, AutoVersion, AutoRun, AutoStepLog, AutoTask
|
||||
- `modules/features/trustee/datamodelFeatureTrustee.py` — alle Trustee-Models
|
||||
- `modules/features/workspace/datamodelFeatureWorkspace.py` — WorkspaceUserSettings
|
||||
- `modules/features/neutralization/datamodelFeatureNeutralizer.py` — DataNeutraliserConfig
|
||||
- `modules/features/realEstate/datamodelFeatureRealEstate.py` — Kanton, Parzelle, Projekt
|
||||
- `modules/datamodels/datamodelContent.py` — ContentChunk, ContentObject (falls vorhanden)
|
||||
- `modules/interfaces/interfaceDbManagement.py` — MessagingSubscription, Prompt (falls Models dort)
|
||||
|
||||
### Phase 3: FK-Discovery und Orphan-Scanner (Backend)
|
||||
|
||||
**LLM: Opus 4.6** (komplexe Cross-DB-Logik, SQL-Generierung)
|
||||
|
||||
- [ ] **FK-Discovery** (`modules/shared/fkRegistry.py`)
|
||||
- `_discoverFkRelationships() -> List[FkRelationship]` — scannt Model-Registry, liest `fk_target`
|
||||
- Cached nach erstem Scan (Models aendern sich nicht zur Laufzeit)
|
||||
- `FkRelationship` Dataclass: `sourceDb, sourceTable, sourceColumn, targetDb, targetTable`
|
||||
|
||||
- [ ] **Orphan-Scanner** (`modules/system/databaseHealth.py`)
|
||||
- `_scanOrphans(dbFilter: Optional[str]) -> List[OrphanResult]`
|
||||
- Same-DB: `SELECT COUNT(*) WHERE col NOT IN (SELECT id FROM parent)`
|
||||
- Cross-DB: Parent-IDs laden, dann `WHERE col NOT IN (...)`
|
||||
- `_cleanOrphans(db, table, column) -> int` — loescht Orphans, gibt Count zurueck
|
||||
- `_getTableStats(dbFilter: Optional[str]) -> List[TableStats]` — pg_stat_user_tables Query
|
||||
|
||||
### Phase 4: API-Endpunkte (Backend)
|
||||
|
||||
**LLM: Composer Fast** (Standard-Route-Pattern, klar definiert)
|
||||
|
||||
- [ ] **Route** (`modules/routes/routeAdminDatabaseHealth.py`)
|
||||
- Prefix: `/api/admin/database-health`
|
||||
- `GET /stats` — Tabellenstatistiken (optional `?db=...`)
|
||||
- `GET /orphans` — Orphan-Scan (optional `?db=...`)
|
||||
- `POST /orphans/clean` — Cleanup Body: `{"db": "...", "table": "...", "column": "..."}`
|
||||
- `POST /orphans/clean-all` — Alle Orphans bereinigen
|
||||
- Alle Endpunkte: SysAdmin-only via `requireSysAdminRole`
|
||||
|
||||
- [ ] **Router in app.py registrieren**
|
||||
|
||||
### Phase 5: Navigation (Backend)
|
||||
|
||||
**LLM: Composer Fast** (einzelne Zeile in mainSystem.py)
|
||||
|
||||
- [ ] **Navigation-Eintrag** in `modules/system/mainSystem.py`
|
||||
- Unter `admin-system-group`, order 98, sysAdminOnly
|
||||
- Icon: `FaDatabase`, Path: `/admin/database-health`
|
||||
|
||||
### Phase 6: Frontend
|
||||
|
||||
**LLM: Opus 4.6** (neue Seite mit Tabs, FormGeneratorTable-Integration)
|
||||
|
||||
- [ ] **AdminDatabaseHealthPage.tsx** mit zwei Tabs
|
||||
- Tab 1 "Statistiken": FormGeneratorTable mit DB, Tabelle, Rows, Total Size, Table Size, Index Size, Last Vacuum, Last Analyze. Sortierbar, Summary-Zeile oben.
|
||||
- Tab 2 "Orphan Cleanup": FormGeneratorTable mit DB, Tabelle, FK-Spalte, Referenz, Orphans, Total Rows, Clean-Button. Filter "Nur Probleme". Batch-Clean.
|
||||
- [ ] **Routing** in Frontend PAGE_REGISTRY
|
||||
- [ ] **i18n** — alle Labels mit `t()` taggen
|
||||
|
||||
### Querschnitt-Checks
|
||||
|
||||
- [ ] API-Endpunkte: 4 neue (stats, orphans, clean, clean-all)
|
||||
- [ ] DB-Schema / Migration: nein
|
||||
- [ ] Frontend-Komponenten: 1 neue Seite (2 Tabs)
|
||||
- [ ] RBAC / Permissions: SysAdmin-only
|
||||
- [ ] Neutralisierung betroffen? nein
|
||||
- [ ] Navigation / Routing: 1 neuer Eintrag
|
||||
- [ ] Billing-Impact? nein
|
||||
|
||||
## Akzeptanzkriterien
|
||||
|
||||
| # | Kriterium (Given-When-Then) | Prio |
|
||||
|---|---------------------------|------|
|
||||
| 1 | Given SysAdmin auf /admin/database-health, When Tab "Statistiken" geladen, Then werden alle Tabellen aller registrierten DBs mit Row Count und Size angezeigt | must |
|
||||
| 2 | Given SysAdmin auf Tab "Orphan Cleanup", When Scan ausgefuehrt, Then werden alle FK-Beziehungen mit Orphan-Count angezeigt | must |
|
||||
| 3 | Given Orphans in AutoWorkflow.featureInstanceId (Cross-DB), When "Clean" geklickt, Then werden die verwaisten AutoWorkflow-Zeilen geloescht und Count aktualisiert | must |
|
||||
| 4 | Given neue Feature-DB wird hinzugefuegt (z.B. poweron_newfeature), When Interface sich registriert und Scan laeuft, Then erscheint die neue DB automatisch in Stats und Orphan-Scan | must |
|
||||
| 5 | Given kein SysAdmin-User, When /admin/database-health aufgerufen, Then 403 Forbidden | must |
|
||||
| 6 | Given grosse Tabelle (>100k Rows), When Orphan-Scan laeuft, Then antwortet der Scan innerhalb von 30 Sekunden | should |
|
||||
| 7 | Given "Alle bereinigen" geklickt, When mehrere Tabellen Orphans haben, Then werden alle Orphans in allen Tabellen bereinigt und Summary angezeigt | should |
|
||||
|
||||
## Testplan
|
||||
|
||||
| ID | AC | Art | Automatisiert | Methode | Status |
|
||||
|----|----|-----|--------------|---------|--------|
|
||||
| T1 | 1 | api | nein | Manuell: GET /stats, pruefen ob alle DBs und Tabellen erscheinen | pending |
|
||||
| T2 | 2 | api | nein | Manuell: Demo laden, Demo entfernen (ohne Cascade), GET /orphans pruefen | pending |
|
||||
| T3 | 3 | api | nein | Manuell: POST /orphans/clean fuer AutoWorkflow, dann GET /orphans erneut | pending |
|
||||
| T4 | 4 | unit | nein | Manuell: _registerDatabase() aufrufen, pruefen ob in _getRegisteredDatabases() | pending |
|
||||
| T5 | 5 | api | nein | Manuell: Non-SysAdmin Request, 403 pruefen | pending |
|
||||
| T6 | 7 | e2e | nein | Manuell: Mehrere Orphan-Typen erzeugen, "Alle bereinigen", Stats pruefen | pending |
|
||||
|
||||
## Links
|
||||
|
||||
- Cursor-Plan: `.cursor/plans/data_cleanup_admin_page_0a610562.plan.md`
|
||||
- Demo-Config Bug-Fix: `gateway/modules/demoConfigs/investorDemo2026.py` (remove() Cascade)
|
||||
- deleteMandate Cascade-Fix: `gateway/modules/interfaces/interfaceDbApp.py` (_cascadeDeleteGraphicalEditorData)
|
||||
- Coding-Conventions: `wiki/d-guides/coding-conventions.md`
|
||||
|
||||
## Abschluss
|
||||
|
||||
- [ ] b-reference/ aktualisiert (gateway/architecture.md — neuer Abschnitt "Database Health")
|
||||
- [ ] TOPICS.md aktualisiert (neues Thema "Database Health / Data Cleanup")
|
||||
- [ ] Dieses Dokument → z-archive/ verschoben
|
||||
|
|
@ -57,19 +57,23 @@ Dieser Endpunkt (`GET /user-language-options` in `routeI18n.py`) wurde entfernt.
|
|||
|
||||
---
|
||||
|
||||
### 2.3 Billing filter-values — KEIN Handlungsbedarf
|
||||
### 2.3 Billing filter-values — Erledigt (2026-04-13)
|
||||
|
||||
**Befund:** `BillingDataView.tsx` ruft `/api/billing/view/users/transactions/filter-values` auf. Dieser Endpunkt existiert weiterhin in `routeBilling.py` (Zeile 1805). Es wurde nur der **Admin**-Endpunkt `GET /admin/transactions/{targetMandateId}/filter-values` entfernt, der im Frontend nirgends referenziert war.
|
||||
**Befund (alt):** `BillingDataView.tsx` rief `/api/billing/view/users/transactions/filter-values` auf.
|
||||
|
||||
**Empfehlung:** Kein Handlungsbedarf.
|
||||
**Update:** Im Rahmen der Unified Filter API Migration (2026-04-13) wurde das gesamte `/filter-values`-Pattern eliminiert. `BillingDataView.tsx` nutzt jetzt `GET /api/billing/view/users/transactions?mode=filterValues&column=xxx`. Der separate Endpunkt existiert nicht mehr.
|
||||
|
||||
---
|
||||
|
||||
### 2.4 FormGeneratorTable — Generisches filter-values-Pattern
|
||||
### 2.4 FormGeneratorTable — Unified Filter API (aktualisiert 2026-04-13)
|
||||
|
||||
**Befund:** `FormGeneratorTable.tsx` baut generisch `{apiEndpoint}/filter-values`-URLs zusammen. Das ist ein allgemeines Pattern — jede Route, die als `apiEndpoint` übergeben wird, muss einen `/filter-values`-Subpfad anbieten.
|
||||
**Befund (alt):** `FormGeneratorTable.tsx` baute generisch `{apiEndpoint}/filter-values`-URLs zusammen.
|
||||
|
||||
**Empfehlung:** Kein direkter Handlungsbedarf durch die Cleanup-Aktion. Aber: Falls in Zukunft Endpunkte entfernt werden, die als `apiEndpoint` im FormGenerator genutzt werden, muss der zugehörige `filter-values`-Pfad mitberücksichtigt werden.
|
||||
**Update:** Das `/filter-values`-Sub-Pfad-Pattern wurde komplett durch Query-Parameter auf dem Haupt-Endpunkt ersetzt:
|
||||
- `GET {apiEndpoint}?mode=filterValues&column=xxx` — Distinct-Werte für Spaltenfilter
|
||||
- `GET {apiEndpoint}?mode=ids&pagination={filters}` — Alle gefilterten IDs (für "Select All Filtered")
|
||||
|
||||
Alle separaten `/filter-values`-Endpunkte in Core-Routes und Feature-Routes (Trustee, RealEstate) wurden entfernt. Siehe `wiki/b-reference/frontend-nyla/formgenerator.md` für die aktuelle Dokumentation.
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue