Major Features Added: • Cross-character memory sharing with trust-based permissions (Basic 30%, Personal 50%, Intimate 70%, Full 90%) • Complete collaborative creative projects system with MCP integration • Database persistence for all creative project data with proper migrations • Trust evolution system based on interaction quality and relationship development • Memory sharing MCP server with 6 autonomous tools for character decision-making • Creative projects MCP server with 8 tools for autonomous project management • Enhanced character integration with all RAG and MCP capabilities • Demo scripts showcasing memory sharing and creative collaboration workflows System Integration: • Main application now initializes memory sharing and creative managers • Conversation engine upgraded to use EnhancedCharacter objects with full RAG access • Database models added for creative projects, collaborators, contributions, and invitations • Complete prompt construction pipeline enriched with RAG insights and trust data • Characters can now autonomously propose projects, share memories, and collaborate creatively
671 lines
25 KiB
TypeScript
671 lines
25 KiB
TypeScript
import React, { useState, useEffect } from 'react';
|
||
import {
|
||
Monitor,
|
||
Cpu,
|
||
HardDrive,
|
||
Database,
|
||
Wifi,
|
||
Play,
|
||
Pause,
|
||
Settings,
|
||
RefreshCw,
|
||
AlertTriangle,
|
||
CheckCircle,
|
||
XCircle,
|
||
Clock,
|
||
Activity,
|
||
Server,
|
||
BarChart3,
|
||
Sliders
|
||
} from 'lucide-react';
|
||
import { apiClient } from '../services/api';
|
||
import { useWebSocket } from '../contexts/WebSocketContext';
|
||
import LoadingSpinner from '../components/Common/LoadingSpinner';
|
||
import toast from 'react-hot-toast';
|
||
|
||
interface SystemStatus {
|
||
status: string;
|
||
uptime: string;
|
||
version: string;
|
||
database_status: string;
|
||
redis_status: string;
|
||
llm_service_status: string;
|
||
discord_bot_status: string;
|
||
active_processes: string[];
|
||
error_count: number;
|
||
warnings_count: number;
|
||
performance_metrics: {
|
||
avg_response_time: number;
|
||
requests_per_minute: number;
|
||
database_query_time: number;
|
||
};
|
||
resource_usage: {
|
||
cpu_percent: number;
|
||
memory_total_mb: number;
|
||
memory_used_mb: number;
|
||
memory_percent: number;
|
||
};
|
||
}
|
||
|
||
interface SystemConfig {
|
||
conversation_frequency: number;
|
||
response_delay_min: number;
|
||
response_delay_max: number;
|
||
personality_change_rate: number;
|
||
memory_retention_days: number;
|
||
max_conversation_length: number;
|
||
creativity_boost: boolean;
|
||
conflict_resolution_enabled: boolean;
|
||
safety_monitoring: boolean;
|
||
auto_moderation: boolean;
|
||
backup_frequency_hours: number;
|
||
}
|
||
|
||
interface LogEntry {
|
||
timestamp: string;
|
||
level: string;
|
||
component: string;
|
||
message: string;
|
||
metadata?: any;
|
||
}
|
||
|
||
const SystemStatus: React.FC = () => {
|
||
const [systemStatus, setSystemStatus] = useState<SystemStatus | null>(null);
|
||
const [systemConfig, setSystemConfig] = useState<SystemConfig | null>(null);
|
||
const [logs, setLogs] = useState<LogEntry[]>([]);
|
||
const [loading, setLoading] = useState(true);
|
||
const [configLoading, setConfigLoading] = useState(false);
|
||
const [showConfig, setShowConfig] = useState(false);
|
||
const [showLogs, setShowLogs] = useState(false);
|
||
const { connected } = useWebSocket();
|
||
|
||
useEffect(() => {
|
||
loadSystemData();
|
||
const interval = setInterval(loadSystemStatus, 30000); // Refresh every 30s
|
||
return () => clearInterval(interval);
|
||
}, []);
|
||
|
||
const loadSystemData = async () => {
|
||
await Promise.all([
|
||
loadSystemStatus(),
|
||
loadSystemConfig(),
|
||
loadSystemLogs()
|
||
]);
|
||
setLoading(false);
|
||
};
|
||
|
||
const loadSystemStatus = async () => {
|
||
try {
|
||
const response = await apiClient.getSystemStatus();
|
||
setSystemStatus(response.data);
|
||
} catch (error) {
|
||
console.error('Failed to load system status:', error);
|
||
// Fallback demo data
|
||
setSystemStatus({
|
||
status: 'running',
|
||
uptime: '2d 14h 32m',
|
||
version: '1.0.0',
|
||
database_status: 'healthy',
|
||
redis_status: 'healthy',
|
||
llm_service_status: 'healthy',
|
||
discord_bot_status: 'connected',
|
||
active_processes: ['main', 'conversation_engine', 'scheduler', 'admin_interface'],
|
||
error_count: 0,
|
||
warnings_count: 2,
|
||
performance_metrics: {
|
||
avg_response_time: 2.5,
|
||
requests_per_minute: 30,
|
||
database_query_time: 0.05
|
||
},
|
||
resource_usage: {
|
||
cpu_percent: 15.3,
|
||
memory_total_mb: 8192,
|
||
memory_used_mb: 3420,
|
||
memory_percent: 41.7
|
||
}
|
||
});
|
||
}
|
||
};
|
||
|
||
const loadSystemConfig = async () => {
|
||
try {
|
||
const response = await apiClient.getSystemConfig();
|
||
setSystemConfig(response.data);
|
||
} catch (error) {
|
||
console.error('Failed to load system config:', error);
|
||
// Fallback demo data
|
||
setSystemConfig({
|
||
conversation_frequency: 0.5,
|
||
response_delay_min: 1.0,
|
||
response_delay_max: 5.0,
|
||
personality_change_rate: 0.1,
|
||
memory_retention_days: 90,
|
||
max_conversation_length: 50,
|
||
creativity_boost: true,
|
||
conflict_resolution_enabled: true,
|
||
safety_monitoring: true,
|
||
auto_moderation: false,
|
||
backup_frequency_hours: 24
|
||
});
|
||
}
|
||
};
|
||
|
||
const loadSystemLogs = async () => {
|
||
try {
|
||
const response = await apiClient.getSystemLogs(50);
|
||
setLogs(response.data);
|
||
} catch (error) {
|
||
console.error('Failed to load system logs:', error);
|
||
// Fallback demo data
|
||
setLogs([
|
||
{
|
||
timestamp: new Date().toISOString(),
|
||
level: 'INFO',
|
||
component: 'conversation_engine',
|
||
message: 'Character Alex initiated conversation with Sage'
|
||
},
|
||
{
|
||
timestamp: new Date(Date.now() - 60000).toISOString(),
|
||
level: 'DEBUG',
|
||
component: 'memory_system',
|
||
message: 'Memory consolidation completed for Luna'
|
||
},
|
||
{
|
||
timestamp: new Date(Date.now() - 120000).toISOString(),
|
||
level: 'WARN',
|
||
component: 'scheduler',
|
||
message: 'High memory usage detected'
|
||
}
|
||
]);
|
||
}
|
||
};
|
||
|
||
const handleSystemAction = async (action: 'pause' | 'resume') => {
|
||
try {
|
||
if (action === 'pause') {
|
||
await apiClient.pauseSystem();
|
||
toast.success('System paused successfully');
|
||
} else {
|
||
await apiClient.resumeSystem();
|
||
toast.success('System resumed successfully');
|
||
}
|
||
await loadSystemStatus();
|
||
} catch (error) {
|
||
toast.error(`Failed to ${action} system`);
|
||
}
|
||
};
|
||
|
||
const handleConfigUpdate = async (updatedConfig: Partial<SystemConfig>) => {
|
||
try {
|
||
setConfigLoading(true);
|
||
await apiClient.updateSystemConfig(updatedConfig);
|
||
setSystemConfig(prev => prev ? { ...prev, ...updatedConfig } : null);
|
||
toast.success('Configuration updated successfully');
|
||
} catch (error) {
|
||
toast.error('Failed to update configuration');
|
||
} finally {
|
||
setConfigLoading(false);
|
||
}
|
||
};
|
||
|
||
const getStatusIcon = (status: string) => {
|
||
switch (status.toLowerCase()) {
|
||
case 'healthy':
|
||
case 'connected':
|
||
case 'running':
|
||
return <CheckCircle className="w-5 h-5 text-green-500" />;
|
||
case 'warning':
|
||
case 'paused':
|
||
return <AlertTriangle className="w-5 h-5 text-yellow-500" />;
|
||
case 'error':
|
||
case 'disconnected':
|
||
return <XCircle className="w-5 h-5 text-red-500" />;
|
||
default:
|
||
return <Monitor className="w-5 h-5 text-gray-500" />;
|
||
}
|
||
};
|
||
|
||
const getStatusColor = (status: string) => {
|
||
switch (status.toLowerCase()) {
|
||
case 'healthy':
|
||
case 'connected':
|
||
case 'running':
|
||
return 'text-green-600';
|
||
case 'warning':
|
||
case 'paused':
|
||
return 'text-yellow-600';
|
||
case 'error':
|
||
case 'disconnected':
|
||
return 'text-red-600';
|
||
default:
|
||
return 'text-gray-600';
|
||
}
|
||
};
|
||
|
||
const getLevelColor = (level: string) => {
|
||
switch (level.toUpperCase()) {
|
||
case 'ERROR':
|
||
return 'text-red-600 bg-red-50';
|
||
case 'WARN':
|
||
return 'text-yellow-600 bg-yellow-50';
|
||
case 'INFO':
|
||
return 'text-blue-600 bg-blue-50';
|
||
case 'DEBUG':
|
||
return 'text-gray-600 bg-gray-50';
|
||
default:
|
||
return 'text-gray-600 bg-gray-50';
|
||
}
|
||
};
|
||
|
||
if (loading) {
|
||
return (
|
||
<div className="flex items-center justify-center h-64">
|
||
<LoadingSpinner size="lg" text="Loading system status..." />
|
||
</div>
|
||
);
|
||
}
|
||
|
||
return (
|
||
<div className="space-y-6">
|
||
{/* Header */}
|
||
<div className="flex items-center justify-between">
|
||
<div>
|
||
<h1 className="text-2xl font-bold text-gray-900">System Status</h1>
|
||
<p className="text-gray-600">Monitor system health and performance</p>
|
||
</div>
|
||
<div className="flex items-center space-x-2">
|
||
<div className={`flex items-center space-x-2 px-3 py-1 rounded-full ${connected ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'}`}>
|
||
<div className={`w-2 h-2 rounded-full ${connected ? 'bg-green-500' : 'bg-red-500'}`}></div>
|
||
<span className="text-sm font-medium">
|
||
{connected ? 'Real-time Connected' : 'Disconnected'}
|
||
</span>
|
||
</div>
|
||
<button
|
||
onClick={loadSystemStatus}
|
||
className="btn-secondary"
|
||
title="Refresh status"
|
||
>
|
||
<RefreshCw className="w-4 h-4" />
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
{/* System Overview */}
|
||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||
<div className="metric-card">
|
||
<div className="flex items-center justify-between">
|
||
<div>
|
||
<p className="text-sm font-medium text-gray-600">System Status</p>
|
||
<div className="flex items-center space-x-2 mt-1">
|
||
{getStatusIcon(systemStatus?.status || 'unknown')}
|
||
<p className={`text-lg font-bold capitalize ${getStatusColor(systemStatus?.status || 'unknown')}`}>
|
||
{systemStatus?.status || 'Unknown'}
|
||
</p>
|
||
</div>
|
||
</div>
|
||
<Monitor className="w-8 h-8 text-blue-500" />
|
||
</div>
|
||
</div>
|
||
|
||
<div className="metric-card">
|
||
<div className="flex items-center justify-between">
|
||
<div>
|
||
<p className="text-sm font-medium text-gray-600">Uptime</p>
|
||
<p className="text-lg font-bold text-gray-900">{systemStatus?.uptime || 'Unknown'}</p>
|
||
</div>
|
||
<Clock className="w-8 h-8 text-green-500" />
|
||
</div>
|
||
</div>
|
||
|
||
<div className="metric-card">
|
||
<div className="flex items-center justify-between">
|
||
<div>
|
||
<p className="text-sm font-medium text-gray-600">Errors</p>
|
||
<p className="text-lg font-bold text-red-600">{systemStatus?.error_count || 0}</p>
|
||
</div>
|
||
<XCircle className="w-8 h-8 text-red-500" />
|
||
</div>
|
||
</div>
|
||
|
||
<div className="metric-card">
|
||
<div className="flex items-center justify-between">
|
||
<div>
|
||
<p className="text-sm font-medium text-gray-600">Warnings</p>
|
||
<p className="text-lg font-bold text-yellow-600">{systemStatus?.warnings_count || 0}</p>
|
||
</div>
|
||
<AlertTriangle className="w-8 h-8 text-yellow-500" />
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* System Controls */}
|
||
<div className="card">
|
||
<div className="flex items-center justify-between mb-4">
|
||
<h3 className="text-lg font-semibold text-gray-900">System Controls</h3>
|
||
<div className="flex space-x-2">
|
||
<button
|
||
onClick={() => setShowConfig(!showConfig)}
|
||
className="btn-secondary"
|
||
>
|
||
<Settings className="w-4 h-4 mr-2" />
|
||
Configuration
|
||
</button>
|
||
<button
|
||
onClick={() => handleSystemAction(systemStatus?.status === 'paused' ? 'resume' : 'pause')}
|
||
className={systemStatus?.status === 'paused' ? 'btn-primary' : 'btn-secondary'}
|
||
>
|
||
{systemStatus?.status === 'paused' ? (
|
||
<>
|
||
<Play className="w-4 h-4 mr-2" />
|
||
Resume System
|
||
</>
|
||
) : (
|
||
<>
|
||
<Pause className="w-4 h-4 mr-2" />
|
||
Pause System
|
||
</>
|
||
)}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Service Status */}
|
||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||
<div className="flex items-center justify-between p-3 bg-gray-50 rounded-lg">
|
||
<div className="flex items-center space-x-2">
|
||
<Database className="w-4 h-4 text-gray-600" />
|
||
<span className="text-sm font-medium">Database</span>
|
||
</div>
|
||
<div className="flex items-center space-x-1">
|
||
{getStatusIcon(systemStatus?.database_status || 'unknown')}
|
||
<span className={`text-sm capitalize ${getStatusColor(systemStatus?.database_status || 'unknown')}`}>
|
||
{systemStatus?.database_status || 'Unknown'}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="flex items-center justify-between p-3 bg-gray-50 rounded-lg">
|
||
<div className="flex items-center space-x-2">
|
||
<Server className="w-4 h-4 text-gray-600" />
|
||
<span className="text-sm font-medium">Redis</span>
|
||
</div>
|
||
<div className="flex items-center space-x-1">
|
||
{getStatusIcon(systemStatus?.redis_status || 'unknown')}
|
||
<span className={`text-sm capitalize ${getStatusColor(systemStatus?.redis_status || 'unknown')}`}>
|
||
{systemStatus?.redis_status || 'Unknown'}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="flex items-center justify-between p-3 bg-gray-50 rounded-lg">
|
||
<div className="flex items-center space-x-2">
|
||
<Activity className="w-4 h-4 text-gray-600" />
|
||
<span className="text-sm font-medium">LLM Service</span>
|
||
</div>
|
||
<div className="flex items-center space-x-1">
|
||
{getStatusIcon(systemStatus?.llm_service_status || 'unknown')}
|
||
<span className={`text-sm capitalize ${getStatusColor(systemStatus?.llm_service_status || 'unknown')}`}>
|
||
{systemStatus?.llm_service_status || 'Unknown'}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="flex items-center justify-between p-3 bg-gray-50 rounded-lg">
|
||
<div className="flex items-center space-x-2">
|
||
<Wifi className="w-4 h-4 text-gray-600" />
|
||
<span className="text-sm font-medium">Discord Bot</span>
|
||
</div>
|
||
<div className="flex items-center space-x-1">
|
||
{getStatusIcon(systemStatus?.discord_bot_status || 'unknown')}
|
||
<span className={`text-sm capitalize ${getStatusColor(systemStatus?.discord_bot_status || 'unknown')}`}>
|
||
{systemStatus?.discord_bot_status || 'Unknown'}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Resource Usage */}
|
||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||
<div className="card">
|
||
<h3 className="text-lg font-semibold text-gray-900 mb-4">Resource Usage</h3>
|
||
<div className="space-y-4">
|
||
<div>
|
||
<div className="flex items-center justify-between text-sm mb-2">
|
||
<div className="flex items-center space-x-2">
|
||
<Cpu className="w-4 h-4 text-gray-600" />
|
||
<span className="text-gray-600">CPU Usage</span>
|
||
</div>
|
||
<span className="font-medium">{systemStatus?.resource_usage?.cpu_percent?.toFixed(1) || 0}%</span>
|
||
</div>
|
||
<div className="w-full bg-gray-200 rounded-full h-2">
|
||
<div
|
||
className="bg-blue-500 h-2 rounded-full"
|
||
style={{ width: `${systemStatus?.resource_usage?.cpu_percent || 0}%` }}
|
||
></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div>
|
||
<div className="flex items-center justify-between text-sm mb-2">
|
||
<div className="flex items-center space-x-2">
|
||
<HardDrive className="w-4 h-4 text-gray-600" />
|
||
<span className="text-gray-600">Memory Usage</span>
|
||
</div>
|
||
<span className="font-medium">
|
||
{systemStatus?.resource_usage?.memory_used_mb || 0}MB / {systemStatus?.resource_usage?.memory_total_mb || 0}MB
|
||
({systemStatus?.resource_usage?.memory_percent?.toFixed(1) || 0}%)
|
||
</span>
|
||
</div>
|
||
<div className="w-full bg-gray-200 rounded-full h-2">
|
||
<div
|
||
className="bg-green-500 h-2 rounded-full"
|
||
style={{ width: `${systemStatus?.resource_usage?.memory_percent || 0}%` }}
|
||
></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="card">
|
||
<h3 className="text-lg font-semibold text-gray-900 mb-4">Performance Metrics</h3>
|
||
<div className="space-y-3">
|
||
<div className="flex items-center justify-between">
|
||
<span className="text-sm text-gray-600">Avg Response Time</span>
|
||
<span className="font-medium">{systemStatus?.performance_metrics?.avg_response_time?.toFixed(1) || 0}s</span>
|
||
</div>
|
||
<div className="flex items-center justify-between">
|
||
<span className="text-sm text-gray-600">Requests/Min</span>
|
||
<span className="font-medium">{systemStatus?.performance_metrics?.requests_per_minute || 0}</span>
|
||
</div>
|
||
<div className="flex items-center justify-between">
|
||
<span className="text-sm text-gray-600">DB Query Time</span>
|
||
<span className="font-medium">{systemStatus?.performance_metrics?.database_query_time?.toFixed(3) || 0}s</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Configuration Panel */}
|
||
{showConfig && systemConfig && (
|
||
<div className="card">
|
||
<div className="flex items-center justify-between mb-4">
|
||
<h3 className="text-lg font-semibold text-gray-900">System Configuration</h3>
|
||
<button
|
||
onClick={() => setShowConfig(false)}
|
||
className="text-gray-400 hover:text-gray-600"
|
||
>
|
||
×
|
||
</button>
|
||
</div>
|
||
|
||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||
<div className="space-y-4">
|
||
<div>
|
||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||
Conversation Frequency
|
||
</label>
|
||
<input
|
||
type="range"
|
||
min="0.1"
|
||
max="1.0"
|
||
step="0.1"
|
||
value={systemConfig.conversation_frequency}
|
||
onChange={(e) => handleConfigUpdate({ conversation_frequency: parseFloat(e.target.value) })}
|
||
className="w-full"
|
||
disabled={configLoading}
|
||
/>
|
||
<span className="text-xs text-gray-500">{systemConfig.conversation_frequency}</span>
|
||
</div>
|
||
|
||
<div>
|
||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||
Memory Retention (Days)
|
||
</label>
|
||
<input
|
||
type="number"
|
||
min="1"
|
||
max="365"
|
||
value={systemConfig.memory_retention_days}
|
||
onChange={(e) => handleConfigUpdate({ memory_retention_days: parseInt(e.target.value) })}
|
||
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-1 focus:ring-primary-500"
|
||
disabled={configLoading}
|
||
/>
|
||
</div>
|
||
|
||
<div>
|
||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||
Max Conversation Length
|
||
</label>
|
||
<input
|
||
type="number"
|
||
min="10"
|
||
max="200"
|
||
value={systemConfig.max_conversation_length}
|
||
onChange={(e) => handleConfigUpdate({ max_conversation_length: parseInt(e.target.value) })}
|
||
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-1 focus:ring-primary-500"
|
||
disabled={configLoading}
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="space-y-4">
|
||
<div className="flex items-center justify-between">
|
||
<label className="text-sm font-medium text-gray-700">Creativity Boost</label>
|
||
<button
|
||
onClick={() => handleConfigUpdate({ creativity_boost: !systemConfig.creativity_boost })}
|
||
className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors ${
|
||
systemConfig.creativity_boost ? 'bg-primary-600' : 'bg-gray-200'
|
||
}`}
|
||
disabled={configLoading}
|
||
>
|
||
<span
|
||
className={`inline-block h-4 w-4 transform rounded-full bg-white transition-transform ${
|
||
systemConfig.creativity_boost ? 'translate-x-6' : 'translate-x-1'
|
||
}`}
|
||
/>
|
||
</button>
|
||
</div>
|
||
|
||
<div className="flex items-center justify-between">
|
||
<label className="text-sm font-medium text-gray-700">Conflict Resolution</label>
|
||
<button
|
||
onClick={() => handleConfigUpdate({ conflict_resolution_enabled: !systemConfig.conflict_resolution_enabled })}
|
||
className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors ${
|
||
systemConfig.conflict_resolution_enabled ? 'bg-primary-600' : 'bg-gray-200'
|
||
}`}
|
||
disabled={configLoading}
|
||
>
|
||
<span
|
||
className={`inline-block h-4 w-4 transform rounded-full bg-white transition-transform ${
|
||
systemConfig.conflict_resolution_enabled ? 'translate-x-6' : 'translate-x-1'
|
||
}`}
|
||
/>
|
||
</button>
|
||
</div>
|
||
|
||
<div className="flex items-center justify-between">
|
||
<label className="text-sm font-medium text-gray-700">Safety Monitoring</label>
|
||
<button
|
||
onClick={() => handleConfigUpdate({ safety_monitoring: !systemConfig.safety_monitoring })}
|
||
className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors ${
|
||
systemConfig.safety_monitoring ? 'bg-primary-600' : 'bg-gray-200'
|
||
}`}
|
||
disabled={configLoading}
|
||
>
|
||
<span
|
||
className={`inline-block h-4 w-4 transform rounded-full bg-white transition-transform ${
|
||
systemConfig.safety_monitoring ? 'translate-x-6' : 'translate-x-1'
|
||
}`}
|
||
/>
|
||
</button>
|
||
</div>
|
||
|
||
<div className="flex items-center justify-between">
|
||
<label className="text-sm font-medium text-gray-700">Auto Moderation</label>
|
||
<button
|
||
onClick={() => handleConfigUpdate({ auto_moderation: !systemConfig.auto_moderation })}
|
||
className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors ${
|
||
systemConfig.auto_moderation ? 'bg-primary-600' : 'bg-gray-200'
|
||
}`}
|
||
disabled={configLoading}
|
||
>
|
||
<span
|
||
className={`inline-block h-4 w-4 transform rounded-full bg-white transition-transform ${
|
||
systemConfig.auto_moderation ? 'translate-x-6' : 'translate-x-1'
|
||
}`}
|
||
/>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* System Logs */}
|
||
<div className="card">
|
||
<div className="flex items-center justify-between mb-4">
|
||
<h3 className="text-lg font-semibold text-gray-900">System Logs</h3>
|
||
<div className="flex space-x-2">
|
||
<button
|
||
onClick={() => setShowLogs(!showLogs)}
|
||
className="btn-secondary"
|
||
>
|
||
<BarChart3 className="w-4 h-4 mr-2" />
|
||
{showLogs ? 'Hide Logs' : 'Show Logs'}
|
||
</button>
|
||
<button
|
||
onClick={loadSystemLogs}
|
||
className="btn-secondary"
|
||
>
|
||
<RefreshCw className="w-4 h-4" />
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
{showLogs && (
|
||
<div className="space-y-2 max-h-64 overflow-y-auto">
|
||
{logs.map((log, index) => (
|
||
<div key={index} className="flex items-start space-x-3 p-2 hover:bg-gray-50 rounded">
|
||
<span className={`px-2 py-1 text-xs font-medium rounded ${getLevelColor(log.level)}`}>
|
||
{log.level}
|
||
</span>
|
||
<div className="flex-1 min-w-0">
|
||
<div className="flex items-center space-x-2 text-sm">
|
||
<span className="font-medium text-gray-900">{log.component}</span>
|
||
<span className="text-gray-500">
|
||
{new Date(log.timestamp).toLocaleTimeString()}
|
||
</span>
|
||
</div>
|
||
<p className="text-sm text-gray-600 mt-1">{log.message}</p>
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export default SystemStatus; |