# admin-dashboard - Index Dashboard administrativ pentru platforma DIDI. Aplicatie React SPA (Single Page Application) cu Material UI, montata la `/admin`. Ofera interfata grafica pentru managementul complet al platformei: monitorizare servicii, configurare framework analiza, management utilizatori, configurare modele LLM, vizualizare si executie pipeline-uri, si istoric analize. **Stack**: React 19 + TypeScript + Material UI 7 + React Router 7 **Container**: didi-admin-local (nginx) -- porturi host `3081` -> 80 (HTTP) si `3001` -> 443 (HTTPS) **Port intern**: 80/443 (nginx) **Basename**: /admin (toate rutele prefixate cu /admin) **Autentificare**: Keycloak (client `admin-dashboard`, PKCE S256, realm `didi-admins`), Keycloak LOCAL `didi-keycloak` servit sub `/auth` prin nginx-ul dashboard-ului **Build**: node:20-alpine (build) -> nginx:alpine (serve) **Network**: `didi-network` (single unified network for all DIDI + AI platform stacks since 2026-05-04) ## URL-uri de acces | URL | Cum funcționează | |---|---| | `https://:3001/admin/` | direct la containerul `didi-admin-local` (nginx HTTPS, port host 3001) | | `http://:3081/admin/` | direct la containerul `didi-admin-local` (nginx HTTP, port host 3081) | | `https://didi365.eu/admin/` | public via Cloudflare tunnel + frontend nginx (când Cloudflare permite path) | | `https://didi365.eu/admin-backend/` | public alias 301 → `/admin/` | `` = mașina de deployment (host-ul local). Containerul `didi-admin-local` are propria nginx (porturi host 3081/3001); frontend `didi-frontend` adaugă proxy `/admin/*`. Detalii: `frontend/INDEX.md` secțiunea 21. --- ## Ce face 1. **Monitorizare servicii** -- health check pentru toate 14 serviciile backend (polling 30s) 2. **Configurare framework analiza** -- CRUD complet pe 20+ tabele de parametri (dimensiuni, tehnici, indicatori, verdicte, ponderi, surse, claims) 3. **Management utilizatori** -- lista, cautare, editare, stergere, sincronizare Keycloak, management planuri abonament 4. **Configurare modele LLM** -- provideri, modele, assignments pe componente/etape, chei API, teste conectivitate 5. **Istoric analize** -- vizualizare si stergere sesiuni analiza din agent-v3 (filtre: risk, status, date) 6. **Pipeline builder** -- editor vizual DAG (ReactFlow) pentru pipeline-uri de analiza 7. **Executie si monitorizare** -- lansare pipeline-uri, tracking progres real-time (WebSocket + polling) 8. **Catalog componente** -- registry pentru functii, resurse AI, transformatoare, modele LLM 9. **Sincronizare Redis** -- trigger manual sync parametri framework -> Redis (pentru agent-v3) --- ## Rute (App.tsx) Toate rutele sunt protejate cu ProtectedRoute (verifica autentificare Keycloak). | Path (relativ la /admin) | Componenta | Scop | |---------------------------|-----------|------| | / | ServicesDashboard | Landing page: monitorizare health servicii | | /dashboard | ServicesDashboard | Alias pentru / | | /login | LoginRedirect | Trigger Keycloak login | | /orchestrator | OrchestratorDashboard | Pipeline builder + catalog + executie + istoric | | /orchestrator/runs/:runId | RunDetailsPage | Detalii run individual (timeline, I/O, raw) | | /pipelines | PipelinesPage | **NEW** — CRUD + lifecycle pipeline-uri reale (`input_type_profile`) + dry-run/cancel (Modul 1); admin-only | | /analysis | UniversalAnalysisDashboard | Executie analiza multi-modal (text/image/video/audio/URL) | | /analysis/results/:runId | UniversalResultsPage | Rezultate live cu polling | | /framework | FrameworkDashboard | CRUD complet parametri framework DIDI | | /framework/progressive | ProgressiveAnalysisTree | Vizualizare arbore analiza progresiva | | /users | UserManagement | Management utilizatori + planuri abonament | | /history | AnalysisHistory | Istoric sesiuni analiza admin | | /providers | ProvidersManagement | CRUD provideri LLM, modele, assignments, chei API | | /techniques-v3 | TechniquesV3Config | Configurare modele pe etape techniques | | /llm-components | LLMComponentsConfig | Configurare unificata LLM (Techniques + AI-Tampered + Claims + Verdict) | | /text-analysis | UniversalAnalysisDashboard | Legacy (randeaza direct, nu redirect) | | /image-analysis | UniversalAnalysisDashboard | Legacy (randeaza direct, nu redirect) | | /video-analysis | UniversalAnalysisDashboard | Legacy (randeaza direct, nu redirect) | | /audio-analysis | UniversalAnalysisDashboard | Legacy (randeaza direct, nu redirect) | | /moderation | ModerationQueue | Lista pending HIL review queue cu filtre + paginatie | | /moderation/:queueId | ModerationDetail | Detalii sesiune + Resolve dialog (action + corrections JSON + notes) | | /moderation/stats | ModerationStats | Cards count (pending, in_review, resolved_24h) + by-priority + avg time | | /unauthorized | Unauthorized | 403 page pentru useri fara rol staff | --- ## Structura fisiere ``` admin-dashboard/ Dockerfile -- Multi-stage: node:20-alpine build -> nginx:alpine Dockerfile.dev -- Dev build nginx.conf -- Nginx dev (port 80) nginx-ssl.conf -- Nginx productie (HTTPS 443 + HTTP 80) package.json -- React 19, MUI 7, ReactFlow, Recharts, Keycloak JS .env -- REACT_APP_KEYCLOAK_URL, STAGING_MODE=false src/ App.tsx -- Router principal (toate rutele) + AuthProvider + ThemeProvider index.tsx -- Entry point (randeaza App) services/ api.ts -- Client API principal (axios, 100+ endpoint-uri) analysisService.ts -- Serviciu analiza legacy (submit/status/results) keycloak.ts -- Configurare Keycloak JS adapter contexts/ AuthContext.tsx -- Provider autentificare (token refresh 30s, logout pe 401) ThemeContext.tsx -- Provider tema dark/light (persistat in localStorage) hooks/ useAnalysisResults.ts -- Rezultate analiza cu auto-refresh useRunsHistory.ts -- Istoric run-uri paginat cu filtre usePipelineProgress.ts -- WebSocket progres pipeline real-time useDeleteDialog.ts -- Dialog confirmare stergere generic useExpandedItems.ts -- Stare accordion expandat useTabState.ts -- Stare tab per item pages/ LandingPage.tsx -- Pagina marketing (nefolosita in rute) FrameworkDashboard.tsx -- CRUD framework (20+ tabele) OrchestratorDashboard.tsx -- Pipeline builder + catalog RunDetailsPage.tsx -- Detalii run (3 tab-uri) UniversalAnalysisDashboard.tsx -- Executie analiza multi-modal UniversalResultsPage.tsx -- Rezultate live polling AudioAnalysisDashboard.tsx -- Legacy ImageAnalysisDashboard.tsx -- Legacy TextAnalysisDashboard.tsx -- Legacy VideoAnalysisDashboard.tsx -- Legacy CostDashboard/ -- Dashboard costuri LLM (orfan, nerutuit) components/ auth/ LoginRedirect.tsx -- Redirect la Keycloak login ProtectedRoute.tsx -- Guard autentificare pe rute (suporta requiredAnyRole?: string[] any-of) Unauthorized.tsx -- NEW: 403 page cu lista roluri + "Go to Public App" + "Log out" dashboard/ ServicesDashboard.tsx -- Grid servicii cu health check services/ ServiceCard.tsx -- Card individual serviciu framework/ CrudDataTable.tsx -- Tabel CRUD generic (GET/POST/PUT/DELETE) TechniquesTreeView.tsx -- Arbore tehnici ProgressiveAnalysisTree.tsx -- Arbore analiza progresiva AnalysisFlowVisualization/ -- Vizualizare ReactFlow DAG framework orchestrator/ PipelineBuilder.tsx -- Editor vizual DAG (ReactFlow) PipelineManager.tsx -- Lista + CRUD pipeline-uri PipelineList.tsx -- Lista pipeline-uri PipelineExecutor.tsx -- Executor pipeline cu polling 500ms ComponentRegistry.tsx -- Catalog componente (functii, resurse, transformatoare) benchmark/ PipelineBenchmark.tsx -- Executie si monitorizare benchmark useBenchmarkRunner.ts -- Hook benchmark (execute + poll) runs-history/ RunsHistoryPage.tsx -- Istoric run-uri paginat RunsHistoryTable.tsx -- Tabel run-uri RunDetailsDrawer.tsx -- Drawer detalii run NodeWaterfallTimeline.tsx -- Timeline executie noduri nodes/, edges/, dialogs/, hooks/ -- Sub-componente pipeline builder Pipelines/ -- NEW (Modul 1) PipelinesPage.tsx -- Tabel pipeline-uri reale (input_type_profile) + lifecycle (clone/activate/versions/restore/import/export) DryRunDialog.tsx -- Run Console-lite: dry-run pe agent-v3 (flow + noduri + verdict profile, fara executie reala) pipelinesApi.ts -- Client catre /framework/api/pipelines + /agent-v3/api/v3/pipeline/dry-run|cancel UserManagement/ UserManagement.tsx -- Tab utilizatori + planuri UserEditModal.tsx -- Dialog editare utilizator AnalysisHistory/ AnalysisHistory.tsx -- Tabel istoric sesiuni analiza AnalysisDetailModal.tsx -- Modal detalii sesiune ProvidersManagement/ ProvidersManagement.tsx -- CRUD 4 tabele: provideri, modele, assignments, chei TechniquesV3/ TechniquesV3Config.tsx -- Configurare modele techniques LLMComponentsConfig/ index.tsx -- Config unificata 3 componente + verdict + provideri VerdictConfig.tsx -- Configurare verdict (ponderi, categorii, severitate) Moderation/ -- NEW api.ts -- fetch helpers + types + UI helpers (priorityColor, statusColor) ModerationQueue.tsx -- paginated table + filters (status, priority), auto-refresh 30s pe pending tab ModerationDetail.tsx -- back btn + action buttons (Claim/Approve/Resolve corrected/Reject) + side-by-side cards (Input + AI Verdict) + Resolve dialog cu JSON corrections editor ModerationStats.tsx -- 3 stat cards + by-priority chips + avg time ModerationSettings/ -- NEW index.tsx -- main panel cu 4 cards + auto-sync after save TriageCard.tsx -- toggle, slider confidence_low, TextFields risk_grey + queue thresholds BrainClientCard.tsx -- toggle, URL, timeouts, sliders confidence_min_silver + semantic_threshold, per-component checkboxes (techniques/ai_tampered/claims). Include alert "CLIENT settings only -- server config in AI platform" SensitiveTopicsCard.tsx -- chip list cu delete + add form (code regex validation) RolesCard.tsx -- table cu toggles (can_resolve, can_escalate, can_force_gold_brain, is_active) api.ts -- fetch helpers pentru /api/moderation-config, /api/sensitive-topics, /api/moderation-roles + sync-redis trigger text-analysis/ -- Componente analiza text legacy image-analysis/ -- Componente analiza imagini legacy audio-analysis/ -- Componente analiza audio legacy video-analysis/ -- Componente analiza video legacy config/ serviceGroups.ts -- Definitie grupuri servicii sidebar providerConfigs.ts -- Configurare provideri LLM theme/ index.ts -- Tema MUI (light/dark, culori, tipografie) types/ catalog.ts -- Tipuri catalog componente keycloak.d.ts -- Tipuri Keycloak ``` --- ## Servicii backend consumate Aplicatia nu are backend propriu. Toate datele vin prin nginx reverse proxy de la 3 servicii + health checks directe. ### 1. didiFramework (proxiat la /framework) Nginx: `/framework/` -> `http://didi-framework:3005` Folosit de: FrameworkDashboard, UserManagement, ProvidersManagement, LLMComponentsConfig, VerdictConfig ### 2. agent-v3 (proxiat la /agent-v3) Nginx: `/agent-v3/` -> `http://didi-agent-v3:24803` Folosit de: AnalysisHistory, TechniquesV3Config, LLMComponentsConfig, ServicesDashboard (health) ### 3. Orchestrator API (proxiat la /api/v1 prin Kong) Nginx: `/api/` -> `http://didi-kong:8000` (Kong LOCAL) Folosit de: OrchestratorDashboard, UniversalAnalysisDashboard, ComponentRegistry, PipelineManager, RunsHistory, CostDashboard ### 4. Health checks directe Nginx proxiaza catre containere individuale pentru verificare disponibilitate. --- ## Endpoint-uri API consumate ### Health checks (ServicesDashboard, polling 30s) | Metoda | Endpoint | Serviciu verificat | |--------|----------|--------------------| | GET | /framework/ | didiFramework | | GET | /agent-v3/api/v3/health | agent-v3 | | GET | /api/status | Kong | | GET | /auth/realms/didi-admins | Keycloak (local didi-keycloak) | | GET | /health-check/redis-commander | Redis Commander | | GET | /health-check/rabbitmq | RabbitMQ Management | | GET | /health-check/minio | MinIO | | GET | /health-check/pgadmin | pgAdmin | ### Framework CRUD (FrameworkDashboard, baza /framework) Fiecare endpoint suporta GET (lista), POST (creare), PUT /{id} (update), DELETE /{id} (stergere). Implementat prin componenta generica CrudDataTable. | Categorie | Endpoint-uri | |-----------|-------------| | Analysis Core | /api/dimensions, /api/subdimensions | | Tehnici | /api/techniques, /api/indicators, /api/validation-rules | | Evaluare Sursa | /api/platforms, /api/platform-modifiers, /api/source-credibility, /api/domain-age-scores, /api/domain-risk-levels, /api/domain-red-flags, /api/author-classifications, /api/author-credibility | | Claims | /api/claims/status, /api/claims/types, /api/claims/confidence, /api/claims/interpretation | | Verdicte | /api/verdicts/categories, /api/verdicts/risk, /api/verdicts/severity | | Ponderi | /api/weights/components, /api/weights/scenarios, /api/weights/multipliers | | Provideri | /api/providers/configs, /api/providers/models, /api/providers/assignments, /api/providers/keys | Extra: | Metoda | Endpoint | Scop | |--------|----------|------| | GET | /api/overview/stats | Statistici framework | | GET | /api/sync-redis/status | Status ultima sincronizare | | POST | /api/sync-redis | Trigger sincronizare Redis | ### Management utilizatori (UserManagement, baza /framework) | Metoda | Endpoint | Scop | |--------|----------|------| | GET | /api/admin/users?page=&limit=&search=&planId= | Lista utilizatori paginata cu filtre | | GET | /api/admin/plans | Lista planuri abonament | | DELETE | /api/admin/users/:id | Dezactivare utilizator | | PUT | /api/admin/users/:id/email-verified | Toggle email verificat | | POST | /api/admin/users/sync | Sincronizare utilizatori Keycloak -> PostgreSQL | | PUT | /api/admin/plans/:id | Editare plan (nume, descriere, credite, pret) | ### Istoric analize admin (AnalysisHistory, baza /agent-v3/api/v3) | Metoda | Endpoint | Scop | |--------|----------|------| | GET | /pipeline/history/admin?page=&limit=&search=&risk_level=&status=&from_date=&to_date= | Lista sesiuni cu filtre | | GET | /pipeline/history/admin/:sessionId | Detalii sesiune | | DELETE | /pipeline/history/admin/:sessionId | Stergere sesiune | ### Configurare modele LLM (TechniquesV3Config + LLMComponentsConfig, baza /agent-v3/api/v3) | Metoda | Endpoint | Scop | |--------|----------|------| | GET | /{component}/config | Configurare componenta | | GET | /{component}/models | Modele disponibile | | GET | /{component}/stage-assignments | Assignments pe etape | | PUT | /{component}/stage-assignments | Salvare assignments | | POST | /{component}/test-model | Test conectivitate model | | POST | /{component}/analyze | Test analiza (doar techniques) | | GET | /{component}/prompts | Lista prompturi componenta | | PUT | /{component}/prompts/:id | Update prompt | Unde {component} = techniques, ai-tampered, claims. ### Moderation queue (ModerationQueue + Detail + Stats, baza /agent-v3/api/v3/moderation) | Metoda | Endpoint | Scop | |--------|----------|------| | GET | /queue?status=&priority=&assigned_to=me&limit=20&offset=0 | Lista paginata cu filtre | | GET | /queue/:queueId | Detail (queue entry + full session JOIN) | | POST | /queue/:queueId/claim | Atomic claim by current moderator | | PUT | /queue/:queueId/resolve | body: {action, corrections?, notes?, user_id?} | | POST | /flag | User report (browser extension) | | GET | /stats | Counts + averages | ### Moderation Settings (ModerationSettings, baza /framework/api) | Metoda | Endpoint | Scop | |--------|----------|------| | GET | /moderation-config | Single-row config | | PUT | /moderation-config | Update whitelisted fields | | GET | /sensitive-topics?active=all | List topics | | POST | /sensitive-topics | Create topic | | PUT | /sensitive-topics/:id | Update label/is_active | | DELETE | /sensitive-topics/:id | Soft delete | | GET | /moderation-roles | List roles | | PUT | /moderation-roles/:code | Update permission toggles | ### Provideri LLM (ProvidersManagement, baza /framework/api/providers) | Metoda | Endpoint | Scop | |--------|----------|------| | GET | /all | Toti providerii cu modele | | POST | /configs | Creare provider | | PUT | /configs/:id | Update provider | | DELETE | /configs/:id | Stergere provider | | POST | /models | Creare model | | PUT | /models/:id | Update model | | DELETE | /models/:id | Stergere model | | POST | /assignments | Creare assignment | | PUT | /assignments/:id | Update assignment | | DELETE | /assignments/:id | Stergere assignment | | POST | /keys | Creare cheie API | | PUT | /keys/:id | Update cheie | | DELETE | /keys/:id | Stergere cheie | | POST | /test/:providerId | Test conexiune provider | | GET | /prompts?component={comp} | Lista prompturi componenta | | PUT | /prompts/:id | Update prompt | ### Orchestrator - Catalog componente (ComponentRegistry, baza /api/v1/catalog) | Metoda | Endpoint | Scop | |--------|----------|------| | GET | /functions/ | Lista feature extractors | | GET | /resources/ | Lista resurse AI | | GET | /transformers/ | Lista transformatoare | | GET | /models/ | Lista modele LLM | | GET | /models/filters/options | Optiuni filtre modele | | POST | /{type}/ | Creare componenta | | PUT | /{type}/:id | Update componenta | | DELETE | /{type}/:id | Stergere componenta | | PATCH | /{type}/:id | Toggle enabled/disabled | ### Orchestrator - Pipeline-uri (PipelineManager, baza /api/v1/pipelines) | Metoda | Endpoint | Scop | |--------|----------|------| | GET | / | Lista pipeline-uri | | GET | /:id | Detalii pipeline | | POST | / | Creare pipeline | | PUT | /:id | Update pipeline | | DELETE | /:id | Stergere pipeline | | GET | /:id/versions | Lista versiuni | | POST | /:id/versions | Creare versiune | | GET | /:id/preview | Preview executie | | POST | /:id/execute | Executie pipeline | | POST | /:id/versions/:ver/execute | Executie versiune specifica | ### Orchestrator - Run-uri (RunsHistory + RunDetailsPage, baza /api/v1/runs) | Metoda | Endpoint | Scop | |--------|----------|------| | GET | ?limit=&offset=&status=&date_from= | Lista run-uri cu filtre | | GET | /:runId | Detalii run | | GET | /:runId?include_live_status=true | Detalii cu status live | | GET | /:runId/results?include_intermediate=true | Rezultate complete | | GET | /status/:runId | Status polling | | GET | /list?limit= | Lista run-uri (analysisResults) | | GET | /history/enriched?skip=&limit=&pipeline_slug=&verdict= | Istoric imbogatit cu filtre | | DELETE | /:runId | Stergere run | ### Orchestrator - Template-uri si API-uri externe | Metoda | Endpoint | Scop | |--------|----------|------| | GET/POST/PUT/DELETE | /api/v1/dynamic-templates/templates | CRUD template-uri | | GET | /api/v1/dynamic-templates/templates/tasks/list | Lista task-uri | | GET | /api/v1/dynamic-templates/templates/external-apis/list | Lista API-uri externe | | GET | /api/v1/dynamic-templates/templates/feature-extractors/list | Lista extractori | | GET/POST/PUT/DELETE | /api/v1/task-templates | CRUD task template-uri | | POST | /api/v1/task-templates/:id/validate | Validare template | | POST | /api/v1/task-templates/:id/test | Test template | | POST | /api/v1/task-templates/:id/execute | Executie template | | GET | /api/v1/external-apis/providers | Lista provideri externi | | GET/POST/PUT/DELETE | /api/v1/external-apis/apis | CRUD API-uri externe | | POST | /api/v1/sync/openapi/discover | Descoperire OpenAPI | | POST | /api/v1/sync/openapi/import | Import servicii descoperite | | GET | /api/v1/catalog/litellm-catalogue | Catalog LiteLLM | ### Costuri LLM (CostDashboard -- pagina orfana, nerutata) | Metoda | Endpoint | Scop | |--------|----------|------| | GET | /api/v1/costs/summary?start_date=&end_date=&period= | Sumar costuri | | GET | /api/v1/costs/by-model?start_date=&end_date= | Costuri per model | | GET | /api/v1/costs/by-job?start_date=&end_date= | Costuri per job | | GET | /api/v1/costs/thresholds | Praguri configurate | | GET | /api/v1/costs/alerts | Alerte active | | POST | /api/v1/costs/thresholds | Creare prag | | DELETE | /api/v1/costs/thresholds/:id | Stergere prag | | PUT | /api/v1/costs/alerts/:id/acknowledge | Confirmare alerta | ### WebSocket (progres real-time) | Endpoint | Scop | |----------|------| | wss://{host}/ws/runs/:runId/progress?include_outputs=true | Progres executie pipeline | Mesaje WebSocket: connected, node_status (update per nod), run_complete, error. Reconnect cu exponential backoff: 1s -> 2s -> 4s -> ... -> 30s max. --- ## Pagini -- ce face fiecare ### ServicesDashboard (/, /dashboard) Grid cu carduri pentru toate serviciile grupate pe layere (Data, Gateway/Auth, Orchestration, Monitoring). Fiecare card face health check la 30s si afiseaza status (verde/rosu). Sidebar cu navigare catre paginile admin (Providers, Users, History -- vizibile doar cu rol admin). Logica: src/components/dashboard/ServicesDashboard.tsx + src/components/services/ServiceCard.tsx Configurare servicii: src/config/serviceGroups.ts (14 servicii in 4 grupuri) ### FrameworkDashboard (/framework) Management CRUD complet pentru parametrii framework-ului de analiza. 7 categorii cu sub-tab-uri, fiecare cu tabel editabil (CrudDataTable). Include vizualizare ReactFlow a fluxului de analiza si buton "Sync to Redis" pentru a trimite parametrii catre Redis (agent-v3 ii citeste de acolo). Logica: src/pages/FrameworkDashboard.tsx (orchestrare tab-uri) Tabel generic: src/components/framework/CrudDataTable.tsx (GET/POST/PUT/DELETE pe orice endpoint) Vizualizare: src/components/framework/AnalysisFlowVisualization/ ### OrchestratorDashboard (/orchestrator) Sidebar cu 2 sectiuni: Component Registry (Extractori, Componente AI, Transformatoare) si Pipelines (Lista, Creare, Executie, Istoric). Fiecare sectiune randeaza componenta corespunzatoare. Logica: src/pages/OrchestratorDashboard.tsx (layout sidebar) Pipeline builder: src/components/orchestrator/PipelineBuilder.tsx (editor ReactFlow DAG) Pipeline manager: src/components/orchestrator/PipelineManager.tsx (lista + CRUD) Catalog: src/components/orchestrator/ComponentRegistry.tsx (tabele + dialoguri CRUD) Benchmark: src/components/orchestrator/benchmark/PipelineBenchmark.tsx (executie + monitorizare) Istoric: src/components/orchestrator/runs-history/RunsHistoryPage.tsx (tabel paginat + filtre) ### RunDetailsPage (/orchestrator/runs/:runId) Detalii complete pentru un run individual. 3 tab-uri: Timeline (waterfall executie noduri), Input/Output (date intrare/iesire), Raw Data (JSON complet). Auto-refresh la 3s cand run-ul e activ. Foloseste fetch() in loc de axios (workaround pentru extensie browser). Logica: src/pages/RunDetailsPage.tsx Timeline: src/components/orchestrator/runs-history/NodeWaterfallTimeline.tsx ### PipelinesPage (/pipelines) -- NEW (Modul 1) Livrabil Modul 1 caiet ("Workflow Builder: CRUD/versionare pipeline; Run Console"). Opereaza pe **definitiile reale de pipeline** (`input_type_profile` din didiFramework), NU pe `pipelineApi` mort (`/api/v1/pipelines`, fara backend). Tabel cu profilurile + actiuni de lifecycle: dry-run, clone, activate/deactivate, versiuni (istoric + restore), import/export. - **Dry-run** (`DryRunDialog.tsx`): apeleaza `POST /agent-v3/api/v3/pipeline/dry-run` -> arata flow-ul, nodurile (component/queue/priority/stages/prompt keys), componentele skipped si verdict profile-ul rezolvat, fara a rula analiza reala. **Cancel** disponibil pe run-uri active. - Backing endpoints: `/framework/api/pipelines/*` (list/get/clone/activate/deactivate/PUT cu versionare/versions/restore/import) + `/agent-v3/api/v3/pipeline/dry-run|:id/cancel`. Logica: src/components/Pipelines/PipelinesPage.tsx + DryRunDialog.tsx + pipelinesApi.ts Acces: admin-only (`ProtectedRoute requiredRole="admin"`). ### UniversalAnalysisDashboard (/analysis) Executie analiza multi-modal: text, imagine, video, audio, URL. Sidebar cu 3 sectiuni: Analysis (formular + executor), Results (run-uri active, polling 10s), History (paginat cu filtre). Suporta upload fisiere in MinIO si tracking progres executie. Logica: src/pages/UniversalAnalysisDashboard.tsx Polling executie: 2s (GET /api/v1/runs/:runId?include_live_status=true) Polling run-uri active: 10s (GET /api/v1/runs?limit=50) ### UniversalResultsPage (/analysis/results/:runId) Pagina rezultate live. Afiseaza progress bar, status per nod (accordion), rezultate finale JSON. Polling la 2s. Logica: src/pages/UniversalResultsPage.tsx ### UserManagement (/users) 2 tab-uri: Utilizatori (tabel paginat cu cautare, filtrare plan, editare, stergere, sync Keycloak, toggle email-verified) si Planuri Abonament (editare inline: nume, descriere, credite, pret). Logica: src/components/UserManagement/UserManagement.tsx Dialog editare: src/components/UserManagement/UserEditModal.tsx ### AnalysisHistory (/history) Tabel paginat cu toate sesiunile de analiza din agent-v3. Filtre: cautare text, nivel risc (LOW/MODERATE/HIGH/VERY_HIGH/CRITICAL), status, interval date. Coloane: data, email, input preview, risk score, tehnici, AI %, claims, durata. View deschide AnalysisDetailModal cu toate detaliile sesiunii. **Cost display per analiza (USD)** — in AnalysisDetailModal, sectiunea "LLM Usage" afiseaza: - Badge total cost langa titlul accordionului (ex: `$0.0321`) - Grid cu 5 celule: Calls / Prompt Tokens / Completion Tokens / Total Tokens / **Est. Cost** - Per Component: fiecare card contine cost chip individual (ex: Techniques: `$0.0054`) Costul se calculeaza client-side dintr-o combinatie de: 1. `llm_usage.by_component[comp]` — prompt_tokens, completion_tokens, models_used[] din agent-v3 2. Fetch parallel la `/framework/api/providers/models` — care returneaza `input_cost_per_1m` + `output_cost_per_1m` per model 3. Formula: `cost = (prompt_tokens / 1M) * input_cost + (completion_tokens / 1M) * output_cost` 4. Cost-ul e charged pe **primul model** din `models_used[]` (aproximare: daca primary a picat si s-a dus pe fallback, cost-ul ar putea fi putin subestimat pe free chain, supra-estimat pe premium — dar in happy path e 100% corect) Free tier afiseaza tot `$0.00` (Qwen local nu are cost input/output). Premium arata cost real per analiza — tipic $0.02-0.05 pentru text simplu, pana la $0.10-0.15 pentru media cu claims multiple. Logica: src/components/AnalysisHistory/AnalysisHistory.tsx Modal detalii: src/components/AnalysisHistory/AnalysisDetailModal.tsx (helpers: `normalizeModelKey`, `computeTokenCost`, `formatUsd`) ### ProvidersManagement (/providers) CRUD pe 4 tab-uri: Providers (configurare provideri LLM), Models (modele per provider), Assignments (assignment componenta -> model pe etapa), API Keys (chei de acces). Fiecare tab cu dialoguri add/edit/delete. Buton test conexiune. Logica: src/components/ProvidersManagement/ProvidersManagement.tsx ### TechniquesV3Config (/techniques-v3) 3 tab-uri: Stage Assignments (model primar + 3 fallback-uri per etapa screening/deep), Available Models (catalog cu info speed/quality/cost), Test Analysis (textarea + buton executie test). Comunicare cu agent-v3. Logica: src/components/TechniquesV3/TechniquesV3Config.tsx ### LLMComponentsConfig (/llm-components) Pagina unificata pentru configurare LLM si scoring pe toate componentele. Top-level tab-uri: Techniques, AI Tampered, Claims, Source Assessment, Verdict, **Moderation** (NEW), Providers. **Moderation** (NEW) — al 6-lea buton (alaturi de Techniques, AI Tampered, Claims, Source Assessment, Verdict). Randeaza `` cu 4 cards pentru HIL triage + brain client + sensitive topics + roles. Auto-syncs to Redis dupa fiecare save (snackbar feedback). **Toggle FREE | PREMIUM** (Etapa 6) — peste Stage Assignments, un `ToggleButtonGroup` permite switch-ul intre chain-ul `free` si `premium` pentru componenta curenta: - Cand selectezi **FREE**, tabelul arata chain-ul primary+fallbacks din rows cu `tier='free'` (Qwen local primary + cloud fallbacks) - Cand selectezi **PREMIUM**, tabelul arata chain-ul din rows cu `tier='premium'` (Gemini 3 Flash / Claude Sonnet primary + Qwen local safety) - Badge-ul stage (`[FREE]` verde / `[PREMIUM]` galben) reflecta tier-ul curent selectat - Daca un stage NU are rows premium configurate, apare un `Alert` warning si fallback automat la chain-ul `free` - Editare: cand userul schimba un model in tabel, se aplica doar pe tier-ul curent selectat; salvarea itereaza ambele tiers si trimite PUT la `/api/providers/assignments/:id` State-ul `selectedTier` e stored in componenta main LLMComponentsConfig; `StageAssignmentsPanel` primeste `selectedTier` ca prop si filtreaza `stage.modelsByTier[selectedTier]` la randare. Structura `modelsByTier: Record<'free'|'premium', ModelConfig[]>` e construita in `fetchStageAssignments()` prin grouping pe `tier` din response-ul GET /api/providers/assignments (care include acum campul `tier`). Per componenta analiza (Techniques, AI Tampered, Claims, Source Assessment): - **Stage Assignments**: modele LLM pe fiecare etapa (primary + 3 fallback-uri) — **cu toggle Free/Premium** - **Vision Models**: cascade viziune (doar AI Tampered) - **Available Models**: catalog modele din PG - **Parameters**: scoring_config editabil — toti parametrii de calcul scor (din PG via didiFramework) - Techniques: count_scaler, severe_threshold, intensity bonus, dimension/count bonuses - AI Tampered: blend_weights, disclosure_impact, undisclosed_threshold, verdict thresholds - Claims: status_weights (VT/LT/UV/OP/LF/VF), all_unverified behavior/credibility - Source Assessment: axis_weights (publication/domain/author/platform), verdict_thresholds - **Prompts**: system prompt + user template editabile - **Test Analysis**: test cu text sample Verdict (VerdictConfig) — 8 tab-uri: - Component Weights (ponderile globale, fallback daca profilurile nu exista) - Verdict Categories (RELIABLE..DISINFORMATION ranges, editabile) - Risk Levels (VERY_LOW..CRITICAL ranges, editabile) - Severity & Actions (LOW..CRITICAL + actiuni recomandate) - Overrides & Synergy (configurare override-uri globale) - Confidence (bonusuri/penalitati) - Topic Multipliers (elections, health, climate — editabile) - **Input Profiles** (NOU): ponderi per input type (text/image/audio/video/url), override-uri per profil, AI disclosure multipliers, reguli INCONCLUSIVE. Editabil cu save + sync to Redis. Logica: src/components/LLMComponentsConfig/index.tsx Verdict: src/components/LLMComponentsConfig/VerdictConfig.tsx ### ModerationQueue (/moderation) Paginated table cu filtre (status: pending/in_review/resolved/all + priority CSV). Auto-refresh 30s pe pending tab. Click row -> detail. Top-right: link "View Stats". Logica: src/components/Moderation/ModerationQueue.tsx Polling: 30s (auto-refresh pe pending tab) ### ModerationDetail (/moderation/:queueId) Side-by-side: Input (text/url + meta) | AI Verdict (risk_score, confidence, severity, components run/skipped). Card metadata queue cu enqueue_reason + meta JSONB. Action buttons: - Claim for Review (pe pending) - Approve as is / Resolve with Corrections / Reject (pe in_review) Resolve dialog: action toggle + JSON corrections editor (validated client-side) + notes. Logica: src/components/Moderation/ModerationDetail.tsx ### ModerationStats (/moderation/stats) 3 stat cards (pending, in_review, resolved_24h) cu colored borders. Open by Priority chips. Avg time in queue. Auto-refresh 30s. Logica: src/components/Moderation/ModerationStats.tsx --- ## Autentificare (Keycloak) ### Configurare Fisier: src/services/keycloak.ts ``` Client ID: admin-dashboard (din REACT_APP_KEYCLOAK_CLIENT_ID, obligatoriu) Realm: didi-admins (din REACT_APP_KEYCLOAK_REALM, obligatoriu) PKCE: S256 (obligatoriu) URL: din REACT_APP_KEYCLOAK_URL (requireEnv) — pointeaza spre /auth pe nginx-ul dashboard-ului, care proxiaza catre Keycloak LOCAL didi-keycloak ``` ### Flow Fisier: src/contexts/AuthContext.tsx 1. La pornire: `keycloak.init({ onLoad: 'login-required' })` 2. Token stocat in localStorage.keycloak_token 3. Refresh token la fiecare 30s (updateToken(70) -- refresh daca expira in < 70s) 4. La 401 de la axios: event auth:unauthorized -> logout automat 5. REACT_APP_STAGING_MODE=true sare peste toata autentificarea ### Protectie rute Fisier: src/components/auth/ProtectedRoute.tsx - Verifica isAuthenticated inainte de a randa componenta - Redirect la /login daca neautentificat - Suporta requiredRole (verificare rol Keycloak) - Suporta `requiredAnyRole?: string[]` (any-of) pentru rute partajate intre mai multe roluri staff Fisier: src/components/auth/LoginRedirect.tsx -- apeleaza keycloak.login() imediat Fisier: src/components/auth/Unauthorized.tsx -- 403 page (lista roluri necesare + "Go to Public App" + "Log out") ### Role gating (admin / moderator / senior_moderator) Roluri definite pe Keycloak LOCAL `didi-keycloak`, realm `didi-admins`: `moderator`, `senior_moderator` (langa `admin` existent). - `App.tsx`: AdminLayout wrapped in `ProtectedRoute requiredAnyRole=[admin, moderator, senior_moderator]` (end-users tip `viewer` primesc 403 -> Unauthorized page) - Per-route guards: - /framework, /users, /providers, /llm-components -> admin-only - /history + /moderation/* -> admin OR moderator - AdminLayout sidebar: sectiunea Configuration admin-only; sectiunea Management split (Users=admin, History=staff, Moderation=moderator-aware) - Staging mode (`REACT_APP_STAGING_MODE=true`) face `hasRole()` sa returneze mereu true -> toate gates bypass pentru testare locala. In prod, Keycloak JWT roles enforcate strict. ### Context expus AuthContext expune: isAuthenticated, initialized, user (tokenParsed), token, login(), logout(), hasRole(role) --- ## Configurare Nginx ### Productie (nginx-ssl.conf) HTTPS pe port 443 (TLS 1.2/1.3) + HTTP pe port 80. | Location | Proxy catre | Scop | |----------|------------|------| | / | SPA fallback /admin/index.html | React app | | /api/ | http://didi-kong:8000 | Orchestrator API prin Kong LOCAL | | /auth/ | http://keycloak:8080 | Keycloak LOCAL (container `didi-keycloak`); NU strip — Keycloak servit sub `/auth` (KC_HTTP_RELATIVE_PATH) | | /agent-v3/ | http://didi-agent-v3:24803 | agent-v3 direct (strip prefix) | | /framework/ | http://didi-framework:3005 | didiFramework direct (strip prefix) | | /health-check/redis-commander | http://staging-dataLayer-redis-commander:8081 | Health check Redis Commander | | /health-check/rabbitmq | http://staging-dataLayer-rabbitmq:15672 | Health check RabbitMQ | | /health-check/minio | http://staging-dataLayer-minio:9000 | Health check MinIO | | /health-check/pgadmin | http://staging-dataLayer-pgadmin:80 | Health check pgAdmin | | /health-check/postgres | http://staging-dataLayer-postgres:5432 | Health check PostgreSQL | Headere securitate: X-Frame-Options SAMEORIGIN, X-Content-Type-Options nosniff, X-XSS-Protection. Cache static: 1 an + immutable pentru .js, .css, .png, .svg, .ico. ### Dev (nginx.conf) Acelasi layout fara SSL, porturi 3000 si 80. Nu are proxy pentru /api/, /auth/, sau health checks (doar /agent-v3/ si /framework/). --- ## Comunicare real-time ### WebSocket (usePipelineProgress) Fisier: src/hooks/usePipelineProgress.ts Conectare la `wss://{host}/ws/runs/:runId/progress?include_outputs=true` pentru tracking progres pipeline. Mesaje primite: - connected -- confirmare subscriptie - node_status -- update executie nod (key, status, label, type, timestamp, duration_ms) - run_complete -- pipeline terminat (status: success/error/stopped, total_duration_ms) - error -- mesaj eroare Reconnect exponential backoff: 1s -> 2s -> 4s -> ... -> 30s max. ### Polling HTTP | Componenta | Interval | Endpoint | |-----------|----------|----------| | ServicesDashboard | 30s | Health checks (8 endpoint-uri) | | UniversalAnalysisDashboard | 2s | GET /api/v1/runs/:runId?include_live_status=true | | UniversalAnalysisDashboard | 10s | GET /api/v1/runs?limit=50 | | PipelineExecutor | 500ms | GET /api/v1/runs/:runId?include_live_status=true | | UniversalResultsPage | 2s | GET /api/v1/runs/status/:runId | | RunDetailsPage | 3s | GET /api/v1/runs/:runId/results | | RunsHistory | 10s | GET /api/v1/runs (optional, toggle) | | useAnalysisResults | 5s | GET /api/v1/runs/:id?include_live_status=true | --- ## Hooks custom | Hook | Fisier | Scop | |------|--------|------| | useAnalysisResults | src/hooks/useAnalysisResults.ts | Rezultate analiza cu auto-refresh 5s, filtrare pe tags | | useRunsHistory | src/hooks/useRunsHistory.ts | Istoric run-uri paginat (fetch(), nu axios), filtre status/date/search | | usePipelineProgress | src/hooks/usePipelineProgress.ts | WebSocket progres pipeline, reconnect backoff | | useDeleteDialog | src/hooks/useDeleteDialog.ts | State generic dialog confirmare stergere | | useExpandedItems | src/hooks/useExpandedItems.ts | State accordion expandat (Set) | | useTabState | src/hooks/useTabState.ts | State tab per item (Record) | | useBenchmarkRunner | src/components/orchestrator/benchmark/useBenchmarkRunner.ts | Executie benchmark pipeline + polling status | | useAutoLayout | src/components/orchestrator/hooks/useAutoLayout.ts | Auto-layout noduri ReactFlow | | useDagValidation | src/components/orchestrator/hooks/useDagValidation.ts | Validare DAG (detectie cicluri) | | useEdgeSuggestions | src/components/orchestrator/hooks/useEdgeSuggestions.ts | Sugestii edge-uri data-flow | | useProviderModels | src/components/orchestrator/hooks/useProviderModels.ts | Fetch modele si resurse catalog | | useResourceEndpoints | src/components/orchestrator/hooks/useResourceEndpoints.ts | Fetch resurse catalog | --- ## Tema (light/dark) Fisier: src/theme/index.ts Persistenta: localStorage['didi-admin-theme'] Toggle: src/components/ThemeToggle.tsx (buton in sidebar ServicesDashboard) ### Culori | Rol | Valoare | Nume | |-----|---------|------| | Primary | #0052CC | Deep Trust Blue | | Secondary | #00BFA6 | Honest Teal | | Success | #28A745 | Truth Green | | Error | #E63946 | Caution Red | | Warning | #FF8C42 | Insight Orange | | Background (light) | #F5F7FA | -- | | Background (dark) | #121212 | -- | | Text (light) | #1F2933 | Slate Charcoal | ### Tipografie Font: Inter, Roboto, Helvetica, Arial - h1: 32px/700, h2: 24px/600, h3: 20px/500, body1: 16px/400, button: 14px/500/uppercase ### Overrides MUI - Button: radius 8, padding 12px 24px - Card: radius 12, hover shadow - TextField: primary border on focus - Chip: rounded 16px --- ## Dependente principale (package.json) | Pachet | Versiune | Scop | |--------|----------|------| | react | ^19.1.1 | UI framework | | react-router-dom | ^7.7.1 | Routing | | @mui/material | ^7.3.1 | Component library | | axios | ^1.11.0 | HTTP client | | keycloak-js | ^26.2.0 | Autentificare Keycloak | | @react-keycloak/web | ^3.4.0 | React bindings Keycloak | | reactflow | ^11.11.4 | Vizualizare DAG pipeline | | recharts | ^3.1.0 | Grafice costuri | | react-dnd | ^16.0.1 | Drag-and-drop | | react-dropzone | ^14.3.8 | Upload fisiere | | zod | ^3.25.76 | Validare schema | | neverthrow | ^8.1.1 | Result types (Ok/Err) | | ts-pattern | ^5.6.0 | Pattern matching | --- ## Configurare Docker ### Dockerfile (productie) ``` Stage 1: node:20-alpine - npm install --legacy-peer-deps - react-scripts build (20+ ARG-uri pentru REACT_APP_*) - ARG REACT_APP_KEYCLOAK_REALM default = didi-admins (= realm-ul real); .env confirma aceeasi valoare - ARG REACT_APP_STAGING_MODE=false (+ ENV explicit — CRA altfel bake-uia ARG default in loc sa citeasca .env) Stage 2: nginx:alpine - Copie build in /usr/share/nginx/html/admin si /usr/share/nginx/html - Copie nginx-ssl.conf + certificate SSL - Expose 443, 80 ``` ### Variabile de mediu (.env) `` = mașina de deployment (host-ul local). Valorile reale din `.env` (bake-uite in build la build time): ``` # General REACT_APP_SERVER_HOST= REACT_APP_HOST= REACT_APP_API_BASE_URL=https://:3001 # prin Kong LOCAL (admin nginx /api/ → didi-kong:8000) REACT_APP_STAGING_MODE=false # Keycloak — LOCAL, proxiat prin admin nginx la /auth/ REACT_APP_KEYCLOAK_URL=https://:3001/auth REACT_APP_KEYCLOAK_REALM=didi-admins REACT_APP_KEYCLOAK_CLIENT_ID=admin-dashboard # Kong Gateway (LOCAL) REACT_APP_KONG_PROXY_PORT=443 REACT_APP_KONG_ADMIN_PORT=18101 REACT_APP_KONG_MANAGER_PORT=18102 # Porturi servicii (afisate pe service cards) REACT_APP_KEYCLOAK_PORT=28000 REACT_APP_POSTGRES_PORT=5000 REACT_APP_PGADMIN_PORT=5050 REACT_APP_REDIS_PORT=6379 REACT_APP_COMMANDER_PORT=8081 REACT_APP_RABBITMQ_MGMT_PORT=15672 REACT_APP_MINIO_CONSOLE_PORT=9001 ``` Nota: `REACT_APP_KEYCLOAK_URL` **este** folosit de `keycloak.ts` (`requireEnv`), pointand spre `/auth` pe nginx-ul dashboard-ului, care proxiaza catre Keycloak LOCAL `didi-keycloak`. `REACT_APP_STAGING_MODE=false` (real) → autentificarea Keycloak e ACTIVA; cand ar fi `true`, s-ar sari peste toata autentificarea (doar pentru testare locala). --- ## Ce NU face - Nu are backend propriu (doar nginx reverse proxy + SPA static) - Nu scrie direct in baza de date (toate operatiile prin didiFramework sau orchestrator API) - Nu proceseaza analize (doar le lanseaza si monitorizeaza) - Nu gestioneaza fisiere (upload-urile merg prin orchestrator API -> MinIO) - Nu are SSR (Server Side Rendering) -- e un SPA clasic - Nu are teste unitare (doar un e2e dark-mode.spec.ts cu Playwright) - CostDashboard exista dar nu e rutata (pagina orfana) ## Functionalitati suplimentare (in afara celor 9 module de caiet) | Functionalitate | Unde in cod | Note | |-----------------|-------------|------| | CostDashboard (costuri LLM) | `src/pages/CostDashboard/` | Pagina orfana — NU e rutata in App.tsx; consuma `/api/v1/costs/*` de pe orchestrator | (HIL Moderation din dashboard — ModerationQueue/Detail/Stats + ModerationSettings — este integrarea UI a Modulului de moderare din backend; ramane in sectiunile de mai sus.) --- ## Recent Changes (2026-05-05) - **Keycloak login fix CRITICAL**: `src/services/keycloak.ts` ignora `REACT_APP_KEYCLOAK_URL` (folosea `${window.location.origin}/auth` hardcoded) — cauza "Cookie not found" cross-domain. Acum foloseste `requireEnv('REACT_APP_KEYCLOAK_URL')` direct. - **`.env`**: `REACT_APP_STAGING_MODE=false` (era `true` -> bypass auth complet), `REACT_APP_KEYCLOAK_REALM=didi-admins` (era gresit `didi-clients`), `REACT_APP_KEYCLOAK_URL=https://:3001/auth` (Keycloak LOCAL `didi-keycloak` prin admin nginx `/auth`; nu SSO cluster). - **`Dockerfile`**: `ARG REACT_APP_STAGING_MODE=false` (era `true`) + `ENV REACT_APP_STAGING_MODE=$REACT_APP_STAGING_MODE` adaugat (CRA bake-uia ARG default in loc sa citeasca .env). - **`Moderation/api.ts` + `ModerationSettings/api.ts`**: helper nou `authedFetch()` care injecteaza `Authorization: Bearer ${localStorage.keycloak_token}`. Inainte faceau `fetch()` fara auth -> 401 pe Kong. - **Role-based UI guard `AdminLayout.tsx`**: Dashboard + service groups (Data Layer, Gateway Layer, etc.) acum vizibile doar pentru `admin`. Moderator/senior_moderator vad doar Management section (Analysis History + Moderation). - **`App.tsx` `DefaultLanding`**: redirect automat la `/moderation` pentru non-admin (in loc de ServicesDashboard). Plus route `/dashboard` cere rol `admin`. - **UI cosmetics**: `ModerationQueue.tsx` Status/Priority labels cu `display: block, mb: 0.5` (erau lipite de butoane); priority 1,2,3,4,5 (erau doar 1,3,4); `AdminLayout.tsx` sidebar logo + main navbar header ambele la `height: 64px` (linii divider aliniate); divider deasupra "Management" ascuns pentru non-admin. --- ## User Management overhaul (2026-05-05) `UserManagement.tsx` complet rescris (3 tabs) + 2 componente noi + `UserEditModal.tsx` extins. Toate hook-uite la endpoint-urile noi din didiFramework `/api/admin/*`. ### Tab #1 — Users (extins) Coloane: ☑ Checkbox (bulk), Email (+ keycloak_id snippet), Name, **Sync** chip (Synced/KC only — warning bg), Plan, **Group** chip, **Roles** chips (top-3 + "+N more"), Email Verified Switch, Active, Credits (tooltip "Spent: N"), **Storage** progress bar (>90% red / >70% warning), Actions (Edit modal / 🛡️ RolesDialog / 📜 UsageHistory / 🗑️ Delete hard). Filter row: search + Plan + **Sync filter** (All/Synced/Keycloak only). Bulk toolbar (apare pe selectare): activate / deactivate / mark email verified / set credits to N / delete. Sequential per id (evită hammer Keycloak). ### Tab #2 — Subscription Plans (nemodificat) ### Tab #3 — Audit Log (NOU) Citește `/api/admin/audit-log` paginat. Filtre Action preset + Actor email (ILIKE) + Since (datetime-local). Coloane: When / Action chip color-coded / Target / Actor / Payload (truncat + tooltip JSON pretty) / IP. ### Componente noi | File | Rol | |------|-----| | `RolesDialog.tsx` | Multi-select check-list pe realm roles, diff summary (add/remove), save → `PUT /:id/roles`. | | `UsageHistoryModal.tsx` | Last 100 rows din `bos_sysadmin.ai_credit_usage`. Defensive render (input_type chip + credits + session_id + JSON fallback pe coloane necunoscute). | ### `UserEditModal.tsx` extins În plus față de existent: **Group select** (dropdown grupuri Keycloak, "(no group)" = remove) + **"Send password reset email" button** (apelează `POST /:id/reset-password` lifespan 86400s, Snackbar succes). ### `SensitiveTopicsCard.tsx` extins (Phase D1) Rânduri rich cu volatility chip color-coded (volatile=red, evolving=warning, stable=success) + TTL/recency chips + iconul ⚙️ Tune → `EditVolatilityDialog` (Select tier auto-seed defaults + override fine pe ttl/recency/half_life). Add-form extins cu Volatility select. ### Live state 20 users live. Reset-password fail cunoscut: cluster Keycloak SMTP not configured (502 cu detail în audit_log) — operațional, nu cod.