from typing import Literal from nicegui import ui from components.circular_progress import MetricCircle, LargeMetricCircle, ColorfulMetricCard, MetricCircleAdv from utils import SystemMonitor, GPUMonitor, OllamaMonitor """ with ui.element('div').classes('main-content w-full'): with ui.column().classes('w-full max-w-4xl mx-auto p-6 gap-6'): ui.label('Ollama Manager').classes('text-2xl font-bold text-white mb-4') # Status cards with ui.row().classes('w-full gap-4 mb-6'): with ui.card().classes('metric-card flex-grow p-4'): with ui.row().classes('items-center gap-2'): ui.icon('check_circle', color='green') ui.label('Status: Online').classes('font-medium text-white') with ui.card().classes('metric-card flex-grow p-4'): ui.label('Version: 0.11.11').classes('font-medium text-white') # Models list with ui.card().classes('metric-card p-6'): ui.label('Installed Models').classes('text-lg font-bold text-white mb-4') models = [ ('llama3.2:3b', '2.0 GB', 'Q4_0'), ('mistral:7b', '4.1 GB', 'Q4_0'), ('codellama:13b', '7.4 GB', 'Q4_K_M'), ('phi3:mini', '2.3 GB', 'Q4_0'), ] for name, size, quant in models: with ui.card().classes('metric-card p-4 mb-2'): with ui.row().classes('w-full items-center'): with ui.column().classes('gap-1'): ui.label(name).classes('font-bold text-white') with ui.row().classes('gap-2'): ui.chip(size, icon='storage').props('outline dense color=cyan') ui.chip(quant, icon='memory').props('outline dense color=orange') ui.space() with ui.row().classes('gap-2'): ui.button(icon='play_arrow').props('round flat color=green').tooltip('Run') ui.button(icon='info').props('round flat color=blue').tooltip('Info') ui.button(icon='delete').props('round flat color=red').tooltip('Delete') """ class DashboardPage(ui.column): def __init__(self, system_monitor: SystemMonitor, gpu_monitor: GPUMonitor, ollama_monitor: OllamaMonitor, *, wrap: bool = False, align_items: None | Literal['start'] | Literal['end'] | Literal['center'] | Literal['baseline'] | Literal['stretch'] = None) -> None: super().__init__(wrap=wrap, align_items=align_items) self.system_monitor = system_monitor self.gpu_monitor = gpu_monitor self.classes('main-content w-full') # Main content area with proper viewport handling with self: with ui.column().classes('w-full max-w-6xl mx-auto p-6 gap-6'): with ui.grid(columns=4).classes('w-full gap-4'): MetricCircleAdv('CPU', system_monitor, 'cpu_percent', '', icon='memory', formatting='percent', color='#e879f9') # Top stats grid with ui.grid(columns=4).classes('w-full gap-4'): # CPU metric with binding with ui.card().classes('metric-card p-4 text-center'): with ui.column().classes('items-center gap-2'): ui.icon('memory', size='md', color='#e879f9') ui.label('CPU').classes('text-sm text-grey-5 font-medium') ui.circular_progress(size='60px', color='#e879f9').bind_value_from( system_monitor, 'cpu_percent', lambda x: x / 100) ui.label().classes('text-lg font-bold text-white').bind_text_from( system_monitor, 'cpu_percent', lambda x: f'{x:.1f}%') # Memory metric with binding with ui.card().classes('metric-card p-4 text-center'): with ui.column().classes('items-center gap-2'): ui.icon('storage', size='md', color='#10b981') ui.label('Memory').classes('text-sm text-grey-5 font-medium') ui.circular_progress(size='60px', color='#10b981').bind_value_from( system_monitor, 'memory_percent', lambda x: x / 100) ui.label().classes('text-lg font-bold text-white').bind_text_from( system_monitor, 'memory_used', lambda x: f'{x / (1024**3):.1f}GB') # GPU metric with conditional rendering with ui.card().classes('metric-card p-4 text-center'): with ui.column().classes('items-center gap-2'): ui.icon('gpu_on', size='md', color='#f97316') ui.label('GPU').classes('text-sm text-grey-5 font-medium') ui.circular_progress(size='60px', color='#f97316').bind_value_from( gpu_monitor, 'usage', lambda x: x / 100 if gpu_monitor.available else 0) ui.label().classes('text-lg font-bold text-white').bind_text_from( gpu_monitor, 'usage', lambda x: f'{x:.1f}%' if gpu_monitor.available else 'N/A') # Temperature metric with ui.card().classes('metric-card p-4 text-center'): with ui.column().classes('items-center gap-2'): ui.icon('thermostat', size='md', color='#06b6d4') ui.label('Temp').classes('text-sm text-grey-5 font-medium') ui.circular_progress(size='60px', color='#06b6d4').bind_value_from( gpu_monitor, 'temperature', lambda x: x / 100 if gpu_monitor.available else 0) ui.label().classes('text-lg font-bold text-white').bind_text_from( gpu_monitor, 'temperature', lambda x: f'{x:.1f}°C' if gpu_monitor.available else 'N/A') # Main dashboard content with ui.row().classes('w-full gap-6'): # Left column - charts and graphs with ui.column().classes('flex-grow gap-4'): # Performance chart card with ui.card().classes('chart-area p-6'): ui.label('System Performance').classes('text-lg font-bold text-white mb-4') # Simulated chart area with ui.element('div').classes('h-48 w-full relative').style('background: linear-gradient(45deg, #1a1d2e 0%, #2a2d3e 100%); border-radius: 8px'): # Chart lines simulation with ui.element('svg').classes('absolute inset-0 w-full h-full'): ui.html(''' ''') # Chart legend with ui.row().classes('gap-6 mt-4'): with ui.row().classes('items-center gap-2'): ui.element('div').classes('w-3 h-3 rounded-full').style('background: #e879f9') ui.label('CPU Usage').classes('text-sm text-grey-5') with ui.row().classes('items-center gap-2'): ui.element('div').classes('w-3 h-3 rounded-full').style('background: #10b981') ui.label('Memory Usage').classes('text-sm text-grey-5') # Quick actions with ui.row().classes('w-full gap-4'): ColorfulMetricCard('Process Manager', 'terminal', '#e879f9') ColorfulMetricCard('Network Monitor', 'router', '#10b981') ColorfulMetricCard('Log Viewer', 'description', '#f97316') # Right column - system info and GPU details with ui.column().classes('w-80 gap-4'): # Large GPU usage circle with binding with ui.card().classes('metric-card p-6 text-center'): with ui.column().classes('items-center gap-3'): ui.label('GPU Usage').classes('text-sm text-grey-5 font-medium uppercase tracking-wide') ui.circular_progress(size='120px', color='#f97316').bind_value_from( gpu_monitor, 'usage', lambda x: x / 100 if gpu_monitor.available else 0) ui.label().classes('text-2xl font-bold text-white').bind_text_from( gpu_monitor, 'usage', lambda x: f'{int(x)}%' if gpu_monitor.available else '0%') ui.label().classes('text-xs text-grey-5').bind_text_from( gpu_monitor, 'gpu_name', lambda x: x if gpu_monitor.available else 'No GPU Detected') # System info card with bindings with ui.card().classes('metric-card p-4'): ui.label('System Info').classes('text-sm font-bold text-white mb-3') with ui.column().classes('gap-2'): # OS with ui.row().classes('w-full justify-between'): ui.label('OS').classes('text-xs text-grey-5') ui.label().classes('text-xs text-white font-medium').bind_text_from( system_monitor, 'os_name') # Kernel with ui.row().classes('w-full justify-between'): ui.label('Kernel').classes('text-xs text-grey-5') ui.label().classes('text-xs text-white font-medium').bind_text_from( system_monitor, 'kernel') # CPU with ui.row().classes('w-full justify-between'): ui.label('CPU').classes('text-xs text-grey-5') ui.label().classes('text-xs text-white font-medium').bind_text_from( system_monitor, 'cpu_model') # GPU with ui.row().classes('w-full justify-between'): ui.label('GPU').classes('text-xs text-grey-5') ui.label().classes('text-xs text-white font-medium').bind_text_from( gpu_monitor, 'gpu_name', lambda x: x if gpu_monitor.available else 'No GPU') # Uptime with ui.row().classes('w-full justify-between'): ui.label('Uptime').classes('text-xs text-grey-5') ui.label().classes('text-xs text-white font-medium').bind_text_from( system_monitor, 'uptime') # Ollama status card with ui.card().classes('metric-card p-4'): ui.label('Ollama Status').classes('text-sm font-bold text-white mb-3') with ui.row().classes('items-center gap-2 mb-2'): ui.icon('check_circle', color='green', size='sm') ui.label('Online').classes('text-sm text-white') with ui.column().classes('gap-1'): ui.label('4 models active').classes('text-xs text-grey-5') ui.label('llama3.2:3b, mistral:7b...').classes('text-xs text-grey-6') # Bottom metrics row with bindings with ui.grid(columns=5).classes('w-full gap-4 mt-4'): # Processes with ui.card().classes('metric-card p-3 text-center'): with ui.column().classes('items-center gap-1'): ui.icon('dashboard', size='sm', color='grey-5') ui.label().classes('text-lg font-bold text-white').bind_text_from( system_monitor, 'process_count', lambda x: str(x)) ui.label('Processes').classes('text-xs text-grey-5') # Network with ui.card().classes('metric-card p-3 text-center'): with ui.column().classes('items-center gap-1'): ui.icon('wifi', size='sm', color='grey-5') ui.label().classes('text-lg font-bold text-white').bind_text_from( system_monitor, 'network_bytes_recv', lambda x: self._format_network(system_monitor.network_bytes_recv + system_monitor.network_bytes_sent)) ui.label('Network').classes('text-xs text-grey-5') # Disk with ui.card().classes('metric-card p-3 text-center'): with ui.column().classes('items-center gap-1'): ui.icon('storage', size='sm', color='grey-5') ui.label().classes('text-lg font-bold text-white').bind_text_from( system_monitor, 'disk_percent', lambda x: f'{x:.0f}%') ui.label('Disk').classes('text-xs text-grey-5') # CPU Cores with ui.card().classes('metric-card p-3 text-center'): with ui.column().classes('items-center gap-1'): ui.icon('settings', size='sm', color='grey-5') ui.label().classes('text-lg font-bold text-white').bind_text_from( system_monitor, 'cpu_count', lambda x: str(x)) ui.label('CPU Cores').classes('text-xs text-grey-5') # Total RAM with ui.card().classes('metric-card p-3 text-center'): with ui.column().classes('items-center gap-1'): ui.icon('memory', size='sm', color='grey-5') ui.label().classes('text-lg font-bold text-white').bind_text_from( system_monitor, 'memory_total', lambda x: f'{x / (1024**3):.0f}GB') ui.label('Total RAM').classes('text-xs text-grey-5') def _format_network(self, total_bytes: int) -> str: """Format network bytes to human readable format""" mb = total_bytes / (1024 * 1024) if mb > 1024: return f"{mb/1024:.1f}GB" else: return f"{mb:.0f}MB"