stuff
This commit is contained in:
@@ -1,32 +1,104 @@
|
||||
from typing import Literal
|
||||
from nicegui import ui
|
||||
from components.circular_progress import MetricCircle, LargeMetricCircle, ColorfulMetricCard
|
||||
from utils import data_manager
|
||||
from utils import SystemMonitor, GPUMonitor
|
||||
|
||||
"""
|
||||
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:
|
||||
def __init__(self):
|
||||
# Get real-time data
|
||||
dashboard_data = data_manager.get_dashboard_data()
|
||||
system_info = data_manager.get_system_info()
|
||||
class DashboardPage(ui.column):
|
||||
|
||||
def __init__(self, system_monitor: SystemMonitor, gpu_monitor: GPUMonitor, *, 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 ui.element('div').classes('main-content w-full'):
|
||||
with self:
|
||||
with ui.column().classes('w-full max-w-6xl mx-auto p-6 gap-6'):
|
||||
# Top stats grid
|
||||
with ui.grid(columns=4).classes('w-full gap-4'):
|
||||
MetricCircle('CPU', f"{dashboard_data['cpu']['percent']}%",
|
||||
dashboard_data['cpu']['percent'] / 100, '#e879f9', 'memory')
|
||||
MetricCircle('Memory', f"{dashboard_data['memory']['used_gb']}GB",
|
||||
dashboard_data['memory']['percent'] / 100, '#10b981', 'storage')
|
||||
# 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}%')
|
||||
|
||||
if dashboard_data['gpu']['available']:
|
||||
MetricCircle('GPU', f"{dashboard_data['gpu']['percent']}%",
|
||||
dashboard_data['gpu']['percent'] / 100, '#f97316', 'gpu_on')
|
||||
MetricCircle('Temp', f"{dashboard_data['gpu']['temperature']}°C",
|
||||
dashboard_data['gpu']['temperature'] / 100, '#06b6d4', 'thermostat')
|
||||
else:
|
||||
MetricCircle('GPU', 'N/A', 0, '#f97316', 'gpu_on')
|
||||
MetricCircle('Temp', 'N/A', 0, '#06b6d4', 'thermostat')
|
||||
# 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'):
|
||||
@@ -76,32 +148,47 @@ class DashboardPage:
|
||||
|
||||
# Right column - system info and GPU details
|
||||
with ui.column().classes('w-80 gap-4'):
|
||||
# Large GPU usage circle
|
||||
if dashboard_data['gpu']['available']:
|
||||
gpu_info = data_manager.get_gpu_info()
|
||||
gpu_name = 'Unknown GPU'
|
||||
if gpu_info.get('cards') and len(gpu_info['cards']) > 0:
|
||||
gpu_name = gpu_info['cards'][0].get('name', 'Unknown GPU')
|
||||
LargeMetricCircle('GPU Usage', gpu_name,
|
||||
dashboard_data['gpu']['percent'] / 100, '#f97316')
|
||||
else:
|
||||
LargeMetricCircle('GPU Usage', 'No GPU Detected', 0, '#f97316')
|
||||
# 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
|
||||
# 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'):
|
||||
self._info_row('OS', system_info.get('os', 'Unknown'))
|
||||
self._info_row('Kernel', system_info.get('kernel', 'Unknown'))
|
||||
self._info_row('CPU', system_info.get('cpu', 'Unknown'))
|
||||
# Get first GPU name for display
|
||||
gpu_info = data_manager.get_gpu_info()
|
||||
gpu_display = 'No GPU'
|
||||
if gpu_info.get('cards') and len(gpu_info['cards']) > 0:
|
||||
gpu_display = gpu_info['cards'][0].get('name', 'Unknown GPU')
|
||||
self._info_row('GPU', gpu_display)
|
||||
self._info_row('Uptime', system_info.get('uptime', 'Unknown'))
|
||||
# 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'):
|
||||
@@ -115,34 +202,53 @@ class DashboardPage:
|
||||
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
|
||||
# Bottom metrics row with bindings
|
||||
with ui.grid(columns=5).classes('w-full gap-4 mt-4'):
|
||||
self._bottom_metric(str(dashboard_data['processes']['count']), 'Processes', 'dashboard')
|
||||
# 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')
|
||||
|
||||
# Format network data (bytes to human readable)
|
||||
network_mb = (dashboard_data['network']['bytes_recv'] + dashboard_data['network']['bytes_sent']) / (1024 * 1024)
|
||||
if network_mb > 1024:
|
||||
network_display = f"{network_mb/1024:.1f}GB"
|
||||
else:
|
||||
network_display = f"{network_mb:.0f}MB"
|
||||
self._bottom_metric(network_display, 'Network', 'wifi')
|
||||
# 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')
|
||||
|
||||
self._bottom_metric(f"{dashboard_data['disk']['percent']:.0f}%", 'Disk', 'storage')
|
||||
# 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 core count as services
|
||||
self._bottom_metric(str(dashboard_data['cpu']['count']), 'CPU Cores', 'settings')
|
||||
# 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')
|
||||
|
||||
# Memory total
|
||||
self._bottom_metric(f"{dashboard_data['memory']['total_gb']:.0f}GB", 'Total RAM', 'memory')
|
||||
# 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 _info_row(self, label: str, value: str):
|
||||
with ui.row().classes('w-full justify-between'):
|
||||
ui.label(label).classes('text-xs text-grey-5')
|
||||
ui.label(value).classes('text-xs text-white font-medium')
|
||||
|
||||
def _bottom_metric(self, value: str, label: str, icon: str):
|
||||
with ui.card().classes('metric-card p-3 text-center'):
|
||||
with ui.column().classes('items-center gap-1'):
|
||||
ui.icon(icon, size='sm', color='grey-5')
|
||||
ui.label(value).classes('text-lg font-bold text-white')
|
||||
ui.label(label).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"
|
||||
|
||||
Reference in New Issue
Block a user