more cleanup

This commit is contained in:
2025-08-26 11:12:43 +02:00
parent 2b633cbc0d
commit 75636cff97
5 changed files with 1 additions and 813 deletions

View File

@@ -164,196 +164,6 @@ def get_zammad_configuration_schema() -> Dict[str, Any]:
} }
def get_signal_configuration_schema() -> Dict[str, Any]:
"""
Returns the configuration schema for the Signal Bot plugin.
Based on the existing Signal module implementation.
"""
return {
"type": "object",
"title": "Signal Bot Configuration",
"description": "Configure AI-powered Signal messaging bot with role-based permissions",
"properties": {
# Basic Settings
"enable_signal_bot": {
"type": "boolean",
"title": "Enable Signal Bot",
"description": "Turn the Signal bot on/off",
"default": False,
"required": True
},
"signal_service_url": {
"type": "url",
"title": "Signal Service URL",
"description": "Signal service endpoint (e.g., signal-cli-rest-api)",
"required": True,
"placeholder": "http://localhost:8080",
"depends_on": {
"field": "enable_signal_bot",
"value": True
}
},
"bot_phone_number": {
"type": "string",
"title": "Bot Phone Number",
"description": "Registered Signal phone number for the bot",
"required": True,
"placeholder": "+1234567890",
"pattern": "^\\+[1-9]\\d{1,14}$",
"depends_on": {
"field": "enable_signal_bot",
"value": True
}
},
# AI Settings
"model": {
"type": "select",
"title": "AI Model",
"description": "Choose the AI model for responses",
"required": False,
"default": "privatemode-llama-3-70b",
"options": [], # Will be populated from available models
"depends_on": {
"field": "enable_signal_bot",
"value": True
}
},
"temperature": {
"type": "number",
"title": "Response Creativity",
"description": "Control response creativity (0.0-1.0)",
"required": False,
"default": 0.7,
"minimum": 0.0,
"maximum": 1.0,
"depends_on": {
"field": "enable_signal_bot",
"value": True
}
},
"max_tokens": {
"type": "integer",
"title": "Max Response Length",
"description": "Maximum tokens in AI responses",
"required": False,
"default": 500,
"minimum": 50,
"maximum": 2000,
"depends_on": {
"field": "enable_signal_bot",
"value": True
}
},
"memory_length": {
"type": "integer",
"title": "Conversation Memory",
"description": "Number of message pairs to remember per user",
"required": False,
"default": 10,
"minimum": 1,
"maximum": 50,
"depends_on": {
"field": "enable_signal_bot",
"value": True
}
},
# Permission Settings
"default_role": {
"type": "select",
"title": "Default User Role",
"description": "Role assigned to new Signal users",
"required": False,
"default": "user",
"options": [
{"value": "admin", "label": "Admin"},
{"value": "user", "label": "User"},
{"value": "disabled", "label": "Disabled"}
],
"depends_on": {
"field": "enable_signal_bot",
"value": True
}
},
"auto_register": {
"type": "boolean",
"title": "Auto-Register New Users",
"description": "Automatically register new Signal users",
"default": True,
"required": False,
"depends_on": {
"field": "enable_signal_bot",
"value": True
}
},
"admin_phone_numbers": {
"type": "textarea",
"title": "Admin Phone Numbers",
"description": "Phone numbers with admin privileges (one per line)",
"required": False,
"placeholder": "+1234567890\n+0987654321",
"rows": 3,
"depends_on": {
"field": "enable_signal_bot",
"value": True
}
},
# Bot Behavior
"command_prefix": {
"type": "string",
"title": "Command Prefix",
"description": "Prefix for bot commands",
"required": False,
"default": "!",
"placeholder": "!",
"depends_on": {
"field": "enable_signal_bot",
"value": True
}
},
"log_conversations": {
"type": "boolean",
"title": "Log Conversations",
"description": "Enable conversation logging for analytics",
"default": False,
"required": False,
"depends_on": {
"field": "enable_signal_bot",
"value": True
}
}
},
"required": ["enable_signal_bot"],
"field_groups": [
{
"title": "Basic Settings",
"fields": ["enable_signal_bot", "signal_service_url", "bot_phone_number"]
},
{
"title": "AI Configuration",
"fields": ["model", "temperature", "max_tokens", "memory_length"]
},
{
"title": "Permission Settings",
"fields": ["default_role", "auto_register", "admin_phone_numbers"]
},
{
"title": "Bot Behavior",
"fields": ["command_prefix", "log_conversations"]
}
],
"validation": {
"signal_test": {
"endpoint": "/api/v1/signal/test-connection",
"method": "POST",
"fields": ["signal_service_url", "bot_phone_number"],
"success_message": "Signal service connection successful",
"error_field": "Signal connection failed"
}
}
}
def get_email_assistant_configuration_schema() -> Dict[str, Any]: def get_email_assistant_configuration_schema() -> Dict[str, Any]:
@@ -471,7 +281,6 @@ def get_plugin_configuration_schema(plugin_id: str) -> Optional[Dict[str, Any]]:
""" """
schemas = { schemas = {
"zammad": get_zammad_configuration_schema, "zammad": get_zammad_configuration_schema,
"signal": get_signal_configuration_schema,
"email-assistant": get_email_assistant_configuration_schema "email-assistant": get_email_assistant_configuration_schema
} }

View File

@@ -1,137 +0,0 @@
"""
Test TEE API endpoints.
"""
import pytest
from httpx import AsyncClient
from unittest.mock import patch, AsyncMock
class TestTEEEndpoints:
"""Test TEE API endpoints."""
@pytest.mark.asyncio
async def test_tee_health_check(self, client: AsyncClient):
"""Test TEE health check endpoint."""
mock_health = {
"status": "healthy",
"timestamp": "2024-01-01T00:00:00Z",
"version": "1.0.0"
}
with patch("app.services.tee_service.TEEService.health_check") as mock_check:
mock_check.return_value = mock_health
response = await client.get(
"/api/v1/tee/health",
headers={"Authorization": "Bearer test-api-key"}
)
assert response.status_code == 200
data = response.json()
assert data["status"] == "healthy"
@pytest.mark.asyncio
async def test_tee_capabilities(self, client: AsyncClient):
"""Test TEE capabilities endpoint."""
mock_capabilities = {
"hardware_security": True,
"encryption_at_rest": True,
"memory_protection": True,
"supported_models": ["gpt-3.5-turbo", "claude-3-haiku"]
}
with patch("app.services.tee_service.TEEService.get_tee_capabilities") as mock_caps:
mock_caps.return_value = mock_capabilities
response = await client.get(
"/api/v1/tee/capabilities",
headers={"Authorization": "Bearer test-api-key"}
)
assert response.status_code == 200
data = response.json()
assert data["hardware_security"] is True
assert "supported_models" in data
@pytest.mark.asyncio
async def test_tee_attestation(self, client: AsyncClient):
"""Test TEE attestation endpoint."""
mock_attestation = {
"attestation_document": "base64_encoded_document",
"signature": "signature_data",
"timestamp": "2024-01-01T00:00:00Z",
"valid": True
}
with patch("app.services.tee_service.TEEService.get_attestation") as mock_att:
mock_att.return_value = mock_attestation
response = await client.get(
"/api/v1/tee/attestation",
headers={"Authorization": "Bearer test-api-key"}
)
assert response.status_code == 200
data = response.json()
assert data["valid"] is True
assert "attestation_document" in data
@pytest.mark.asyncio
async def test_tee_session_creation(self, client: AsyncClient):
"""Test TEE secure session creation."""
mock_session = {
"session_id": "secure-session-123",
"public_key": "public_key_data",
"expires_at": "2024-01-01T01:00:00Z"
}
with patch("app.services.tee_service.TEEService.create_secure_session") as mock_session_create:
mock_session_create.return_value = mock_session
response = await client.post(
"/api/v1/tee/session",
json={"model": "gpt-3.5-turbo"},
headers={"Authorization": "Bearer test-api-key"}
)
assert response.status_code == 200
data = response.json()
assert "session_id" in data
assert "public_key" in data
@pytest.mark.asyncio
async def test_tee_metrics(self, client: AsyncClient):
"""Test TEE metrics endpoint."""
mock_metrics = {
"total_requests": 1000,
"successful_requests": 995,
"failed_requests": 5,
"avg_response_time": 0.125,
"privacy_score": 95.8,
"security_level": "high"
}
with patch("app.services.tee_service.TEEService.get_privacy_metrics") as mock_metrics_get:
mock_metrics_get.return_value = mock_metrics
response = await client.get(
"/api/v1/tee/metrics",
headers={"Authorization": "Bearer test-api-key"}
)
assert response.status_code == 200
data = response.json()
assert data["privacy_score"] == 95.8
assert data["security_level"] == "high"
@pytest.mark.asyncio
async def test_tee_unauthorized(self, client: AsyncClient):
"""Test TEE endpoints without authentication."""
response = await client.get("/api/v1/tee/health")
assert response.status_code == 401
response = await client.get("/api/v1/tee/capabilities")
assert response.status_code == 401
response = await client.get("/api/v1/tee/attestation")
assert response.status_code == 401

View File

@@ -29,7 +29,6 @@ import {
} from "lucide-react"; } from "lucide-react";
import { useToast } from "@/hooks/use-toast"; import { useToast } from "@/hooks/use-toast";
import { apiClient } from "@/lib/api-client"; import { apiClient } from "@/lib/api-client";
import ConfidentialityDashboard from "@/components/settings/ConfidentialityDashboard";
interface SystemSettings { interface SystemSettings {
// Security Settings // Security Settings
@@ -262,11 +261,10 @@ function SettingsPageContent() {
)} )}
<Tabs defaultValue="security" className="space-y-6"> <Tabs defaultValue="security" className="space-y-6">
<TabsList className="grid w-full grid-cols-4"> <TabsList className="grid w-full grid-cols-3">
<TabsTrigger value="security">Security</TabsTrigger> <TabsTrigger value="security">Security</TabsTrigger>
<TabsTrigger value="api">API</TabsTrigger> <TabsTrigger value="api">API</TabsTrigger>
<TabsTrigger value="notifications">Notifications</TabsTrigger> <TabsTrigger value="notifications">Notifications</TabsTrigger>
<TabsTrigger value="confidentiality">Confidentiality</TabsTrigger>
</TabsList> </TabsList>
<TabsContent value="security" className="space-y-6"> <TabsContent value="security" className="space-y-6">
@@ -852,9 +850,6 @@ function SettingsPageContent() {
</TabsContent> </TabsContent>
<TabsContent value="confidentiality" className="space-y-6">
<ConfidentialityDashboard />
</TabsContent>
</Tabs> </Tabs>
</div> </div>
); );

View File

@@ -1,14 +0,0 @@
"use client"
import { ProtectedRoute } from "@/components/auth/ProtectedRoute"
import { SignalConfig } from "@/components/modules/SignalConfig"
export default function SignalPage() {
return (
<ProtectedRoute>
<div className="container mx-auto px-4 py-8">
<SignalConfig />
</div>
</ProtectedRoute>
)
}

View File

@@ -1,465 +0,0 @@
"use client"
import { useState, useEffect } from "react"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { Switch } from "@/components/ui/switch"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
import { Textarea } from "@/components/ui/textarea"
import { Slider } from "@/components/ui/slider"
import { Badge } from "@/components/ui/badge"
import { Alert, AlertDescription } from "@/components/ui/alert"
import { useToast } from "@/hooks/use-toast"
import { Settings, Save, RefreshCw, Phone, Bot, Zap } from "lucide-react"
import { config } from "@/lib/config"
import { apiClient } from "@/lib/api-client"
interface SignalConfig {
enabled: boolean
signal_service: string
signal_phone_number: string
model: string
temperature: number
max_tokens: number
memory_length: number
command_prefix: string
default_role: string
auto_register: boolean
admin_phone_numbers: string[]
fallback_responses: string[]
log_conversations: boolean
connection_timeout: number
}
interface ConfigSchema {
title: string
properties: Record<string, any>
required: string[]
}
export function SignalConfig() {
const { toast } = useToast()
// Get default Signal service URL from environment or use localhost fallback
const getDefaultSignalService = () => {
return process.env.NEXT_PUBLIC_DEFAULT_SIGNAL_SERVICE || "localhost:8080"
}
const [config, setConfig] = useState<SignalConfig | null>(null)
const [schema, setSchema] = useState<ConfigSchema | null>(null)
const [loading, setLoading] = useState(true)
const [saving, setSaving] = useState(false)
const [newAdminPhone, setNewAdminPhone] = useState("")
const [newFallbackResponse, setNewFallbackResponse] = useState("")
useEffect(() => {
fetchConfig()
}, [])
const fetchConfig = async () => {
try {
setLoading(true)
const data = await apiClient.get("/api-internal/v1/modules/signal/config")
setSchema(data.schema)
// Set default config if none exists
const defaultConfig: SignalConfig = {
enabled: false,
signal_service: getDefaultSignalService(),
signal_phone_number: "",
model: "gpt-3.5-turbo",
temperature: 0.7,
max_tokens: 1000,
memory_length: 10,
command_prefix: "!",
default_role: "disabled",
auto_register: false,
admin_phone_numbers: [],
fallback_responses: [
"I'm not sure how to help with that. Could you please rephrase your question?",
"I don't have enough information to answer that question accurately.",
"That's outside my knowledge area. Is there something else I can help you with?"
],
log_conversations: true,
connection_timeout: 30
}
setConfig({ ...defaultConfig, ...data.current_config })
} catch (error) {
console.error("Error fetching Signal config:", error)
toast({
title: "Error",
description: "Failed to load Signal bot configuration",
variant: "destructive"
})
} finally {
setLoading(false)
}
}
const saveConfig = async () => {
if (!config) return
try {
setSaving(true)
await apiClient.post("/api-internal/v1/modules/signal/config", config)
toast({
title: "Success",
description: "Signal bot configuration saved successfully"
})
} catch (error) {
console.error("Error saving config:", error)
toast({
title: "Error",
description: error instanceof Error ? error.message : "Failed to save configuration",
variant: "destructive"
})
} finally {
setSaving(false)
}
}
const updateConfig = (key: keyof SignalConfig, value: any) => {
if (!config) return
setConfig({ ...config, [key]: value })
}
const addAdminPhone = () => {
if (!config || !newAdminPhone.trim()) return
if (!newAdminPhone.match(/^\+[1-9]\d{1,14}$/)) {
toast({
title: "Invalid Format",
description: "Phone number must be in international format (e.g., +1234567890)",
variant: "destructive"
})
return
}
updateConfig("admin_phone_numbers", [...config.admin_phone_numbers, newAdminPhone.trim()])
setNewAdminPhone("")
}
const removeAdminPhone = (index: number) => {
if (!config) return
const phones = [...config.admin_phone_numbers]
phones.splice(index, 1)
updateConfig("admin_phone_numbers", phones)
}
const addFallbackResponse = () => {
if (!config || !newFallbackResponse.trim()) return
updateConfig("fallback_responses", [...config.fallback_responses, newFallbackResponse.trim()])
setNewFallbackResponse("")
}
const removeFallbackResponse = (index: number) => {
if (!config) return
const responses = [...config.fallback_responses]
responses.splice(index, 1)
updateConfig("fallback_responses", responses)
}
if (loading) {
return (
<Card>
<CardContent className="flex items-center justify-center py-8">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary"></div>
</CardContent>
</Card>
)
}
if (!config || !schema) {
return (
<Alert>
<AlertDescription>
Failed to load Signal bot configuration. Please try refreshing the page.
</AlertDescription>
</Alert>
)
}
return (
<div className="space-y-6">
{/* Header */}
<div className="flex items-center justify-between">
<div>
<h2 className="text-2xl font-bold">Signal Bot Configuration</h2>
<p className="text-muted-foreground">Configure your AI-powered Signal messaging bot</p>
</div>
<div className="flex gap-2">
<Button onClick={fetchConfig} variant="outline" disabled={loading}>
<RefreshCw className="h-4 w-4 mr-2" />
Refresh
</Button>
<Button onClick={saveConfig} disabled={saving}>
<Save className="h-4 w-4 mr-2" />
{saving ? "Saving..." : "Save"}
</Button>
</div>
</div>
{/* Enable/Disable Switch */}
<Card>
<CardHeader>
<div className="flex items-center justify-between">
<div>
<CardTitle className="flex items-center">
<Bot className="h-5 w-5 mr-2" />
Signal Bot Status
</CardTitle>
<CardDescription>Enable or disable the Signal bot</CardDescription>
</div>
<Switch
checked={config.enabled}
onCheckedChange={(enabled) => updateConfig("enabled", enabled)}
/>
</div>
</CardHeader>
</Card>
{/* Basic Configuration */}
<Card>
<CardHeader>
<CardTitle className="flex items-center">
<Settings className="h-5 w-5 mr-2" />
Basic Configuration
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="signal_service">Signal Service URL *</Label>
<Input
id="signal_service"
value={config.signal_service}
onChange={(e) => updateConfig("signal_service", e.target.value)}
placeholder={getDefaultSignalService()}
/>
</div>
<div className="space-y-2">
<Label htmlFor="signal_phone_number">Bot Phone Number *</Label>
<Input
id="signal_phone_number"
value={config.signal_phone_number}
onChange={(e) => updateConfig("signal_phone_number", e.target.value)}
placeholder="+1234567890"
/>
</div>
</div>
</CardContent>
</Card>
{/* AI Configuration */}
<Card>
<CardHeader>
<CardTitle className="flex items-center">
<Zap className="h-5 w-5 mr-2" />
AI Configuration
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="model">AI Model</Label>
<Select value={config.model} onValueChange={(value) => updateConfig("model", value)}>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
{schema?.properties?.model?.enum ? (
schema.properties.model.enum.map((modelId: string) => (
<SelectItem key={modelId} value={modelId}>
{modelId}
</SelectItem>
))
) : (
// Fallback if schema not loaded yet
<>
<SelectItem value="gpt-3.5-turbo">GPT-3.5 Turbo</SelectItem>
<SelectItem value="gpt-4">GPT-4</SelectItem>
</>
)}
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label htmlFor="command_prefix">Command Prefix</Label>
<Input
id="command_prefix"
value={config.command_prefix}
onChange={(e) => updateConfig("command_prefix", e.target.value)}
placeholder="!"
maxLength={5}
/>
</div>
</div>
<div className="space-y-4">
<div className="space-y-2">
<Label>Response Creativity: {config.temperature}</Label>
<Slider
value={[config.temperature]}
onValueChange={(value) => updateConfig("temperature", value[0])}
max={1}
min={0}
step={0.1}
className="w-full"
/>
<p className="text-xs text-muted-foreground">
0.0 = focused and deterministic, 1.0 = creative and diverse
</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="space-y-2">
<Label>Max Response Length: {config.max_tokens}</Label>
<Slider
value={[config.max_tokens]}
onValueChange={(value) => updateConfig("max_tokens", value[0])}
max={4000}
min={50}
step={50}
className="w-full"
/>
</div>
<div className="space-y-2">
<Label>Conversation Memory: {config.memory_length}</Label>
<Slider
value={[config.memory_length]}
onValueChange={(value) => updateConfig("memory_length", value[0])}
max={50}
min={1}
step={1}
className="w-full"
/>
</div>
</div>
</div>
</CardContent>
</Card>
{/* User Management */}
<Card>
<CardHeader>
<CardTitle className="flex items-center">
<Phone className="h-5 w-5 mr-2" />
User Management
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="flex items-center justify-between">
<div>
<Label>Auto-Register New Users</Label>
<p className="text-sm text-muted-foreground">
Automatically register new Signal contacts
</p>
</div>
<Switch
checked={config.auto_register}
onCheckedChange={(enabled) => updateConfig("auto_register", enabled)}
/>
</div>
<div className="space-y-2">
<Label htmlFor="default_role">Default Role for New Users</Label>
<Select value={config.default_role} onValueChange={(value) => updateConfig("default_role", value)}>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="user">User</SelectItem>
<SelectItem value="admin">Admin</SelectItem>
<SelectItem value="disabled">Disabled</SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label>Admin Phone Numbers</Label>
<div className="flex gap-2">
<Input
value={newAdminPhone}
onChange={(e) => setNewAdminPhone(e.target.value)}
placeholder="+1234567890"
/>
<Button onClick={addAdminPhone} variant="outline">
Add
</Button>
</div>
<div className="flex flex-wrap gap-2">
{config.admin_phone_numbers.map((phone, index) => (
<Badge key={index} variant="secondary" className="cursor-pointer" onClick={() => removeAdminPhone(index)}>
{phone} ×
</Badge>
))}
</div>
</div>
</CardContent>
</Card>
{/* Advanced Settings */}
<Card>
<CardHeader>
<CardTitle>Advanced Settings</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="space-y-2">
<Label>Fallback Responses</Label>
<div className="flex gap-2">
<Textarea
value={newFallbackResponse}
onChange={(e) => setNewFallbackResponse(e.target.value)}
placeholder="Add a fallback response..."
rows={2}
/>
<Button onClick={addFallbackResponse} variant="outline">
Add
</Button>
</div>
<div className="space-y-2">
{config.fallback_responses.map((response, index) => (
<div key={index} className="flex items-start gap-2 p-2 bg-muted rounded">
<span className="flex-1 text-sm">{response}</span>
<Button
size="sm"
variant="ghost"
onClick={() => removeFallbackResponse(index)}
>
×
</Button>
</div>
))}
</div>
</div>
<div className="flex items-center justify-between">
<div>
<Label>Log Conversations</Label>
<p className="text-sm text-muted-foreground">
Save conversations for debugging and analytics
</p>
</div>
<Switch
checked={config.log_conversations}
onCheckedChange={(enabled) => updateConfig("log_conversations", enabled)}
/>
</div>
<div className="space-y-2">
<Label>Connection Timeout: {config.connection_timeout}s</Label>
<Slider
value={[config.connection_timeout]}
onValueChange={(value) => updateConfig("connection_timeout", value[0])}
max={120}
min={5}
step={5}
className="w-full"
/>
</div>
</CardContent>
</Card>
</div>
)
}