Files
clei-flow/app/ui/dashboard.py

154 lines
7.4 KiB
Python
Executable File

from nicegui import ui
from database import AppConfig
from core.state import state
def show():
semi_auto_initial = AppConfig.get_val('semi_auto', 'false') == 'true'
with ui.grid(columns=2).classes('w-full gap-4'):
# --- COLUNA DA ESQUERDA: CONTROLES ---
with ui.column().classes('w-full gap-4'):
# Painel de Controle
with ui.card().classes('w-full p-4 border-l-4 border-indigo-500'):
ui.label('🎛️ Painel de Controle').classes('text-xl font-bold mb-4 text-gray-700')
# Switches
is_running = state.watcher.is_running if state.watcher else False
def toggle_watcher(e):
if state.watcher:
state.watcher.is_running = e.value
state.log(f"Comando: {'INICIAR' if e.value else 'PAUSAR'}")
switch_run = ui.switch('Monitoramento Ativo', value=is_running, on_change=toggle_watcher).props('color=green size=lg')
def toggle_semi_auto(e):
AppConfig.set_val('semi_auto', str(e.value).lower())
state.log(f"⚠️ Semi-Auto: {e.value}")
switch_auto = ui.switch('Modo Semi-Automático', value=semi_auto_initial, on_change=toggle_semi_auto).props('color=amber size=lg')
# Botão Cancelar Global
def cancel_task():
if state.watcher:
state.watcher.abort_current_task()
ui.notify('Cancelando...', type='warning')
btn_cancel = ui.button('CANCELAR ATUAL', on_click=cancel_task, icon='cancel').props('color=red').classes('w-full mt-4 hidden')
# Terminal de Logs
with ui.card().classes('w-full h-64 bg-black text-green-400 font-mono text-xs p-2 overflow-hidden flex flex-col'):
ui.label('>_ Logs').classes('text-gray-500 mb-1 border-b border-gray-800 w-full')
log_container = ui.scroll_area().classes('flex-grow w-full')
log_content = ui.label().style('white-space: pre-wrap; font-family: monospace;')
with log_container: log_content.move(log_container)
# --- COLUNA DA DIREITA: LISTA DE TAREFAS ---
with ui.card().classes('w-full h-[80vh] bg-gray-50 flex flex-col p-0'):
# Cabeçalho da Lista
with ui.row().classes('w-full p-4 bg-white border-b items-center justify-between'):
ui.label('📋 Fila de Processamento').classes('text-lg font-bold text-gray-700')
lbl_status_top = ui.label('Ocioso').classes('text-sm text-gray-400')
# Container da Lista
tasks_container = ui.column().classes('w-full p-2 gap-2 overflow-y-auto flex-grow')
# --- RENDERIZADOR DA LISTA (Com Duas Barras) ---
def render_tasks():
tasks_container.clear()
if not state.tasks:
with tasks_container:
ui.label('Nenhuma atividade recente.').classes('text-gray-400 italic p-4')
return
for fname, data in reversed(state.tasks.items()):
status = data['status']
global_pct = data['progress']
file_pct = data.get('file_progress', 0)
speed = data.get('speed', '')
label = data['label']
icon = 'circle'; color = 'grey'; spin = False
bg_color = 'bg-white'
if status == 'pending':
icon = 'hourglass_empty'; color = 'grey'
elif status == 'running':
icon = 'sync'; color = 'blue'; spin = True
bg_color = 'bg-blue-50 border-blue-200 border'
elif status == 'warning':
icon = 'warning'; color = 'orange'
bg_color = 'bg-orange-50 border-orange-200 border'
elif status == 'done':
icon = 'check_circle'; color = 'green'
elif status == 'error':
icon = 'error'; color = 'red'
elif status == 'skipped':
icon = 'block'; color = 'red'
with tasks_container:
with ui.card().classes(f'w-full p-3 {bg_color} flex-row items-start gap-3'):
# Ícone
if spin: ui.spinner(size='sm').classes('text-blue-500 mt-1')
else: ui.icon(icon, color=color, size='sm').classes('mt-1')
# Conteúdo
with ui.column().classes('flex-grow gap-1 w-full'):
# Título e Nome Arquivo
ui.label(label).classes('font-bold text-sm text-gray-800 break-all')
ui.label(fname).classes('text-xs text-gray-500 break-all mb-1')
# Se estiver rodando, mostra barras
if status == 'running':
# Barra 1: Global
with ui.row().classes('w-full items-center gap-2'):
ui.label('Total').classes('text-xs font-bold text-gray-400 w-12')
ui.linear_progress(value=global_pct/100, show_value=False).classes('h-2 rounded flex-grow').props('color=blue-4')
ui.label(f"{int(global_pct)}%").classes('text-xs font-bold text-blue-400 w-8 text-right')
# Barra 2: Conversão de Arquivo
# Mostra se tivermos algum progresso de arquivo OU velocidade detectada
if file_pct > 0 or (speed and speed != "0x"):
with ui.row().classes('w-full items-center gap-2'):
ui.label('File').classes('text-xs font-bold text-gray-400 w-12')
# Barra Verde
ui.linear_progress(value=file_pct/100, show_value=False).classes('h-3 rounded flex-grow').props('color=green')
# Porcentagem e Velocidade
with ui.row().classes('items-center gap-1'):
ui.label(f"{int(file_pct)}%").classes('text-xs font-bold text-green-600')
if speed:
# Badge de Velocidade
ui.label(speed).classes('text-xs bg-green-100 text-green-800 px-1 rounded font-mono')
# --- LOOP DE ATUALIZAÇÃO ---
def update_ui():
# Logs
logs = state.get_logs()
log_content.set_text("\n".join(logs[-30:]))
log_container.scroll_to(percent=1.0)
# Tarefas
render_tasks()
# Status Global
if state.watcher and state.watcher.is_running:
lbl_status_top.text = "Serviço Rodando"
lbl_status_top.classes(replace='text-green-500')
switch_run.value = True
else:
lbl_status_top.text = "Serviço Pausado"
lbl_status_top.classes(replace='text-red-400')
switch_run.value = False
# Botão Cancelar
if state.current_file:
btn_cancel.classes(remove='hidden')
else:
btn_cancel.classes(add='hidden')
ui.timer(1.0, update_ui)