diff --git a/app/main.py b/app/main.py index c8d4e42..a3d0bfc 100644 --- a/app/main.py +++ b/app/main.py @@ -59,7 +59,7 @@ from app.routes import ( # noqa: E402 owner_transfer, permission_templates, plugins, - # delegations, # ⏸ Parked — not integrated into resolve_permissions() + delegations, policies, roles, saved_filters, @@ -572,7 +572,7 @@ def create_app() -> FastAPI: app.include_router(saved_views.router) app.include_router(webhooks.router) app.include_router(permission_templates.router) - # app.include_router(delegations.router) # ⏸ Parked — not integrated into resolve_permissions() + app.include_router(delegations.router) app.include_router(policies.router) app.include_router(errors.router) app.include_router(guests.router) # ⚠️ Guest-System umgebaut — Guests sind jetzt reguläre User mit role=guest diff --git a/app/workflows/engine.py b/app/workflows/engine.py index 256a518..128778e 100644 --- a/app/workflows/engine.py +++ b/app/workflows/engine.py @@ -154,16 +154,38 @@ class WorkflowEngine: action=action_name, ) if not guard_result["allowed"]: - # Guard blocks — pause workflow and create approval request - instance.status = "in_progress" - instance.resume_reason = "decision_guard" - await self.db.flush() - return { - "status": "waiting_for_approval", - "guard": guard_result, - "step_index": instance.current_step_index, - "message": guard_result.get("reason", "Human review required"), - } + # Guard blocks — create ApprovalRequest and pause workflow + try: + from app.core.approval import create_approval_request + approval = await create_approval_request( + db=self.db, + tenant_id=self.tenant_id, + entity_type="workflow_instance", + entity_id=instance.id, + action=f"decision_guard:{action_name}", + requested_by=instance.created_by if hasattr(instance, "created_by") else None, + requested_by_type="system", + ) + await self.db.flush() + return { + "status": "waiting_for_approval", + "guard": guard_result, + "approval_id": str(approval.id), + "step_index": instance.current_step_index, + "message": guard_result.get("reason", "Human review required"), + } + except Exception as e: + logger.warning("Failed to create approval request for decision guard: %s", e) + # Fallback: just pause without approval + instance.status = "in_progress" + instance.resume_reason = "decision_guard" + await self.db.flush() + return { + "status": "waiting_for_approval", + "guard": guard_result, + "step_index": instance.current_step_index, + "message": guard_result.get("reason", "Human review required"), + } try: result: StepResult = await handler( diff --git a/frontend/src/api/aiUIControl.ts b/frontend/src/api/aiUIControl.ts deleted file mode 100644 index c64c3d4..0000000 --- a/frontend/src/api/aiUIControl.ts +++ /dev/null @@ -1,70 +0,0 @@ -/** - * API client for AI UI Control endpoints. - * - * Task 4.2: REST endpoints for AI agents to send commands and poll status. - */ - -import { apiClient } from './client'; - -export interface UICommandCreate { - action: string; - path?: string | null; - entity?: string | null; - filter?: Record | null; - contact_id?: string | null; - modal?: string | null; - tab?: string | null; - section?: string | null; - key?: string | null; - value?: unknown | null; - description?: string | null; -} - -export interface UICommandResponse { - command_id: string; - status: string; - action: string; - message?: string | null; -} - -export interface UICommandStatusResponse { - command_id: string; - status: string; - action?: string | null; - feedback?: { - command_id: string; - status: string; - action?: string | null; - current_path?: string | null; - current_tab?: string | null; - message?: string | null; - error?: string | null; - data?: Record | null; - } | null; -} - -export async function sendUICommand( - body: UICommandCreate, -): Promise { - const res = await apiClient.post( - '/ai-ui-control/command', - body, - ); - return res.data; -} - -export async function getCommandStatus( - commandId: string, -): Promise { - const res = await apiClient.get( - `/ai-ui-control/command/${commandId}/status`, - ); - return res.data; -} - -export async function getOnlineUsers(): Promise<{ online_users: string[] }> { - const res = await apiClient.get<{ online_users: string[] }>( - '/ai-ui-control/online-users', - ); - return res.data; -} diff --git a/frontend/src/api/searchHooks.ts b/frontend/src/api/searchHooks.ts deleted file mode 100644 index c7a7469..0000000 --- a/frontend/src/api/searchHooks.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { useQuery } from '@tanstack/react-query'; -import type { FacetsResponse } from './search'; - -export function useSearchFacets() { - return useQuery({ - queryKey: ['searchFacets'], - queryFn: async (): Promise => { - const { fetchFacets } = await import('@/api/search'); - return fetchFacets(); - }, - staleTime: 5 * 60 * 1000, - }); -} diff --git a/frontend/src/pages/StartPage.tsx b/frontend/src/pages/StartPage.tsx index 218c517..f1f7d4c 100644 --- a/frontend/src/pages/StartPage.tsx +++ b/frontend/src/pages/StartPage.tsx @@ -1,6 +1,6 @@ /** * Start page — shown after login. - * 2-column layout: left menu, right grid with workspace tiles. + * 2-column layout: left menu, right grid with workspace tiles + dashboard stats. */ import React from 'react'; @@ -8,7 +8,9 @@ import { useNavigate } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { useAuthStore } from '@/store/authStore'; import { useUIStore } from '@/store/uiStore'; -import { Settings, HelpCircle, LayoutGrid, ArrowRight, Plus, Bot, Zap, ArrowLeft, ScrollText } from 'lucide-react'; +import { useUnifiedContacts, useAuditLog } from '@/api/hooks'; +import { StatCard } from '@/components/shared/StatCard'; +import { Settings, HelpCircle, LayoutGrid, ArrowRight, Plus, Bot, Zap, ArrowLeft, ScrollText, Users, Building2, Activity } from 'lucide-react'; // Workspace tile definition interface WorkspaceTile { @@ -37,6 +39,14 @@ export function StartPage() { const user = useAuthStore((state) => state.user); const sidebarOpen = useUIStore((state) => state.sidebarOpen); + const { data: companiesData } = useUnifiedContacts(1, 1, undefined, 'company'); + const { data: contactsData } = useUnifiedContacts(1, 1, undefined, 'person'); + const { data: auditData } = useAuditLog(1, 5); + + const totalCompanies = companiesData?.total ?? 0; + const totalContacts = contactsData?.total ?? 0; + const recentActivities = auditData?.items?.length ?? 0; + const menuItems = [ { id: 'workspaces', label: 'Workspaces', icon: , active: true }, { id: 'settings', label: 'Einstellungen', icon: , onClick: () => navigate('/settings') }, @@ -94,6 +104,28 @@ export function StartPage() {

Wähle einen Workspace aus

+ {/* Dashboard stats */} +
+ } + testId="start-stat-companies" + /> + } + testId="start-stat-contacts" + /> + } + testId="start-stat-activity" + /> +
+ {/* Masonry-style grid */}
{DEFAULT_WORKSPACES.map((ws) => ( diff --git a/frontend/src/pages/agents/AgentsOverview.tsx b/frontend/src/pages/agents/AgentsOverview.tsx index c6db5b5..34f16aa 100644 --- a/frontend/src/pages/agents/AgentsOverview.tsx +++ b/frontend/src/pages/agents/AgentsOverview.tsx @@ -1,35 +1,99 @@ import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { useAgents } from '@/api/automation'; +import { Card } from '@/components/ui/Card'; +import { Badge } from '@/components/ui/Badge'; +import { Skeleton } from '@/components/ui/Skeleton'; +import { EmptyState } from '@/components/ui/EmptyState'; +import { Bot, Brain, Activity, AlertCircle } from 'lucide-react'; +import type { AgentDefinition } from '@/types/automation'; + +function statusBadgeVariant(status: string): 'success' | 'warning' | 'secondary' { + if (status === 'active') return 'success'; + if (status === 'inactive') return 'secondary'; + return 'warning'; +} export function AgentsOverviewPage() { + const { t } = useTranslation(); + const { data: agents, isLoading, isError, refetch } = useAgents(); + return (

Agenten Übersicht

Verwalten Sie KI-Agenten, führen Sie diese aus und überwachen Sie deren Ausführungen.

-
-
-
- -
-

Agenten

-

KI-Agenten erstellen, konfigurieren und ausführen

+ + {/* Loading */} + {isLoading && ( +
+ {[1, 2, 3].map((i) => ( + + ))}
-
-
- + )} + + {/* Error */} + {isError && ( + +
+ + {t('common.errorLoading')} +
-

Automation

-

Automatisierte Workflows und Trigger

+
+ )} + + {/* Empty State */} + {!isLoading && !isError && (!agents || agents.length === 0) && ( + } + /> + )} + + {/* Agent List */} + {!isLoading && !isError && agents && agents.length > 0 && ( +
+ {agents.map((agent: AgentDefinition) => ( + +
+
+
+

{agent.name}

+ + {agent.active ? t('agent.active', 'Aktiv') : t('agent.inactive', 'Inaktiv')} + + + {agent.mode} + +
+ {agent.description && ( +

{agent.description}

+ )} +
+ + + {agent.model} + + + + {agent.tools?.length || 0} tools + +
+
+
+
+ ))}
-
-
- -
-

KI

-

KI-Assistent und Proactive AI

-
-
+ )}
); } diff --git a/tests/conftest.py b/tests/conftest.py index 6e36578..2755cc8 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -70,6 +70,16 @@ for _plugin_name in _registry.list_discovered(): importlib.import_module(f"app.plugins.builtins.{_plugin_name}") except Exception: pass + # Also directly import models.py to ensure all tables are registered + try: + importlib.import_module(f"app.plugins.builtins.{_plugin_name}.models") + except Exception: + pass + +# Also import core models that may be missing +from app.models.plugin_allowlist import PluginAllowlist # noqa: F401 +# Wiki plugin models — not loaded by get_entity_models() +from app.plugins.builtins.wiki.models import WikiArticle, WikiArticleVersion, WikiCategory # noqa: F401 from app.plugins.registry import reset_registry_for_testing # noqa: F401 from app.core.permission_registry import init_permission_registry # noqa: F401