"""NiceGUI dashboard for AI Software Factory with real-time database data.""" from nicegui import ui from database import get_db, get_engine, init_db, get_db_sync from models import ProjectHistory, ProjectLog, AuditTrail, UserAction, SystemLog, AgentAction from datetime import datetime import logging logger = logging.getLogger(__name__) def create_dashboard(): """Create and configure the NiceGUI dashboard with real-time data from database.""" # Get database session directly for NiceGUI (not a FastAPI dependency) db_session = get_db_sync() if db_session is None: ui.label('Database session could not be created. Check configuration and restart the server.') return try: # Wrap database queries to handle missing tables gracefully try: # Fetch current project current_project = db_session.query(ProjectHistory).order_by(ProjectHistory.created_at.desc()).first() # Fetch recent audit trail entries recent_audits = db_session.query(AuditTrail).order_by(AuditTrail.created_at.desc()).limit(10).all() # Fetch recent project history entries recent_projects = db_session.query(ProjectHistory).order_by(ProjectHistory.created_at.desc()).limit(5).all() # Fetch recent system logs recent_logs = db_session.query(SystemLog).order_by(SystemLog.created_at.desc()).limit(5).all() except Exception as e: # Handle missing tables or other database errors ui.label(f'Database error: {str(e)}. Please run POST /init-db or ensure the database is initialized.') return # Create main card with ui.card().classes('w-full max-w-6xl mx-auto').props('elevated').style('max-width: 1200px; margin: 0 auto;') as main_card: # Header section with ui.row().classes('items-center gap-4 mb-6').style('padding: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 12px; color: white;') as header_row: title = ui.label('AI Software Factory').style('font-size: 28px; font-weight: bold; margin: 0;') subtitle = ui.label('Real-time Dashboard & Audit Trail Display').style('font-size: 14px; opacity: 0.9; margin-top: 5px;') # Stats grid with ui.grid(columns=4, cols=4).props('gutter=1').style('margin-top: 15px;') as stats_grid: # Current Project with ui.column().classes('text-center').style('background: rgba(255, 255, 255, 0.1); padding: 15px; border-radius: 8px;') as card1: ui.label('Current Project').style('font-size: 12px; text-transform: uppercase; opacity: 0.8;') project_name = current_project.project_name if current_project else 'No active project' ui.label(project_name).style('font-size: 20px; font-weight: bold; margin-top: 5px;') # Active Projects count with ui.column().classes('text-center').style('background: rgba(255, 255, 255, 0.1); padding: 15px; border-radius: 8px;') as card2: ui.label('Active Projects').style('font-size: 12px; text-transform: uppercase; opacity: 0.8;') active_count = len(recent_projects) ui.label(str(active_count)).style('font-size: 20px; font-weight: bold; margin-top: 5px; color: #00ff88;') # Code Generated (calculated from history entries) with ui.column().classes('text-center').style('background: rgba(255, 255, 255, 0.1); padding: 15px; border-radius: 8px;') as card3: ui.label('Code Generated').style('font-size: 12px; text-transform: uppercase; opacity: 0.8;') # Count .py files from history code_count = sum(1 for p in recent_projects if 'Generated' in p.message) code_size = sum(p.progress for p in recent_projects) if recent_projects else 0 ui.label(f'{code_count} files ({code_size}% total)').style('font-size: 20px; font-weight: bold; margin-top: 5px; color: #ffd93d;') # Status with ui.column().classes('text-center').style('background: rgba(255, 255, 255, 0.1); padding: 15px; border-radius: 8px;') as card4: ui.label('Status').style('font-size: 12px; text-transform: uppercase; opacity: 0.8;') status = current_project.status if current_project else 'No active project' ui.label(status).style('font-size: 20px; font-weight: bold; margin-top: 5px; color: #00d4ff;') # Separator ui.separator(style='margin: 15px 0; color: rgba(255, 255, 255, 0.3);') # Current Status Panel with ui.column().style('background: rgba(255, 255, 255, 0.08); padding: 20px; border-radius: 12px; margin-bottom: 15px;') as status_panel: ui.label('📊 Current Status').style('font-size: 18px; font-weight: bold; color: #4fc3f7; margin-bottom: 10px;') with ui.row().classes('items-center gap-4').style('margin-top: 10px;') as progress_row: if current_project: ui.label('Progress:').style('color: #bdbdbd;') ui.label(str(current_project.progress) + '%').style('color: #4fc3f7; font-weight: bold;') ui.label('').style('color: #bdbdbd;') else: ui.label('No active project').style('color: #bdbdbd;') if current_project: ui.label(current_project.message).style('color: #888; margin-top: 8px; font-size: 13px;') ui.label('Last update: ' + current_project.updated_at.strftime('%H:%M:%S')).style('color: #bdbdbd; font-size: 12px; margin-top: 5px;') else: ui.label('Waiting for a new project...').style('color: #888; margin-top: 8px; font-size: 13px;') # Separator ui.separator(style='margin: 15px 0; color: rgba(255, 255, 255, 0.3);') # Active Projects Section with ui.column().style('background: rgba(255, 255, 255, 0.08); padding: 20px; border-radius: 12px; margin-bottom: 15px;') as projects_section: ui.label('📁 Active Projects').style('font-size: 18px; font-weight: bold; color: #81c784; margin-bottom: 10px;') with ui.row().style('gap: 10px;') as projects_list: for i, project in enumerate(recent_projects[:3], 1): with ui.card().props('elevated rounded').style('background: rgba(0, 255, 136, 0.15); border: 1px solid rgba(0, 255, 136, 0.4);') as project_item: ui.label(str(i + len(recent_projects)) + '. ' + project.project_name).style('font-size: 16px; font-weight: bold; color: white;') ui.label('• Agent: Orchestrator').style('font-size: 12px; color: #bdbdbd;') ui.label('• Status: ' + project.status).style('font-size: 11px; color: #81c784; margin-top: 3px;') if not recent_projects: ui.label('No active projects yet.').style('font-size: 14px; color: #bdbdbd;') # Separator ui.separator(style='margin: 15px 0; color: rgba(255, 255, 255, 0.3);') # Audit Trail Section with ui.column().style('background: rgba(255, 255, 255, 0.08); padding: 20px; border-radius: 12px; margin-bottom: 15px;') as audit_section: ui.label('📜 Audit Trail').style('font-size: 18px; font-weight: bold; color: #ffe082; margin-bottom: 10px;') with ui.data_table( headers=['Timestamp', 'Component', 'Action', 'Level'], columns=[ {'name': 'Timestamp', 'field': 'created_at', 'width': '180px'}, {'name': 'Component', 'field': 'component', 'width': '150px'}, {'name': 'Action', 'field': 'action', 'width': '250px'}, {'name': 'Level', 'field': 'log_level', 'width': '100px'}, ], row_height=36, ) as table: # Populate table with audit trail data audit_rows = [] for audit in recent_audits: status = 'Success' if audit.log_level.upper() in ['INFO', 'SUCCESS'] else audit.log_level.upper() audit_rows.append({ 'created_at': audit.created_at.strftime('%Y-%m-%d %H:%M:%S'), 'component': audit.component or 'System', 'action': audit.action or audit.message[:50], 'log_level': status[:15], }) table.rows = audit_rows if not recent_audits: ui.label('No audit trail entries yet.').style('font-size: 12px; color: #bdbdbd;') # Separator ui.separator(style='margin: 15px 0; color: rgba(255, 255, 255, 0.3);') # System Logs Section with ui.column().style('background: rgba(255, 255, 255, 0.08); padding: 20px; border-radius: 12px;') as logs_section: ui.label('⚙️ System Logs').style('font-size: 18px; font-weight: bold; color: #ff8a80; margin-bottom: 10px;') with ui.data_table( headers=['Component', 'Level', 'Message'], columns=[ {'name': 'Component', 'field': 'component', 'width': '150px'}, {'name': 'Level', 'field': 'log_level', 'width': '100px'}, {'name': 'Message', 'field': 'log_message', 'width': '450px'}, ], row_height=32, ) as logs_table: logs_table.rows = [ { 'component': log.component, 'log_level': log.log_level, 'log_message': log.log_message[:50] + '...' if len(log.log_message) > 50 else log.log_message } for log in recent_logs ] if not recent_logs: ui.label('No system logs yet.').style('font-size: 12px; color: #bdbdbd;') # Separator ui.separator(style='margin: 15px 0; color: rgba(255, 255, 255, 0.3);') # API Endpoints Section with ui.expansion_group('🔗 Available API Endpoints', default_open=True).props('dense') as api_section: with ui.column().style('font-size: 12px; color: #78909c;') as endpoint_list: endpoints = [ ['/ (root)', 'Dashboard'], ['/generate', 'Generate new software (POST)'], ['/health', 'Health check'], ['/projects', 'List all projects'], ['/status/{project_id}', 'Get project status'], ['/audit/projects', 'Get project audit data'], ['/audit/logs', 'Get system logs'], ['/audit/trail', 'Get audit trail'], ['/audit/actions', 'Get user actions'], ['/audit/history', 'Get project history'], ['/audit/prompts', 'Get prompts'], ['/audit/changes', 'Get code changes'], ['/init-db', 'Initialize database (POST)'], ] for endpoint, desc in endpoints: ui.label(f'• {endpoint:<30} {desc}') finally: db_session.close() def run_app(port=None, reload=False, browser=True, storage_secret=None): """Run the NiceGUI app.""" ui.run(title='AI Software Factory Dashboard', port=port, reload=reload, browser=browser, storage_secret=storage_secret) # Create and run the app if __name__ in {'__main__', '__console__'}: create_dashboard() run_app()