concertado o deploy
This commit is contained in:
@@ -3,11 +3,14 @@ import os
|
||||
import shutil
|
||||
import json
|
||||
import asyncio
|
||||
import datetime
|
||||
from collections import deque # Import necessário para ler as últimas linhas de forma eficiente
|
||||
|
||||
# --- CONFIGURAÇÕES DE DIRETÓRIOS ---
|
||||
SRC_ROOT = "/downloads"
|
||||
DST_ROOT = "/media"
|
||||
CONFIG_PATH = "/app/data/presets.json"
|
||||
LOG_PATH = "/app/data/history.log" # Novo arquivo de log persistente
|
||||
|
||||
class DeployManager:
|
||||
def __init__(self):
|
||||
@@ -16,8 +19,42 @@ class DeployManager:
|
||||
self.selected_items = []
|
||||
self.container = None
|
||||
self.presets = self.load_presets()
|
||||
self.pendencies = [] # {'name':, 'src':, 'dst':}
|
||||
self.logs = []
|
||||
self.pendencies = []
|
||||
# CARREGA OS LOGS DO ARQUIVO AO INICIAR
|
||||
self.logs = self.load_logs_from_file()
|
||||
|
||||
# --- NOVO: GERENCIAMENTO DE LOGS PERSISTENTES ---
|
||||
def load_logs_from_file(self):
|
||||
"""Lê as últimas 50 linhas do arquivo de log"""
|
||||
if not os.path.exists(LOG_PATH):
|
||||
return []
|
||||
try:
|
||||
# Lê as últimas 50 linhas do arquivo
|
||||
with open(LOG_PATH, 'r', encoding='utf-8') as f:
|
||||
# deque(..., maxlen=50) pega automaticamente as últimas 50
|
||||
last_lines = list(deque(f, maxlen=50))
|
||||
|
||||
# Inverte a ordem para mostrar o mais recente no topo da lista visual
|
||||
# remove quebras de linha com .strip()
|
||||
return [line.strip() for line in reversed(last_lines)]
|
||||
except:
|
||||
return []
|
||||
|
||||
def add_log(self, message, type="info"):
|
||||
"""Adiciona log na memória E no arquivo"""
|
||||
timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
full_msg = f"[{timestamp}] {message}"
|
||||
|
||||
# 1. Atualiza a lista da memória (para a UI)
|
||||
self.logs.insert(0, full_msg)
|
||||
if len(self.logs) > 50: self.logs.pop() # Limpa memória excedente
|
||||
|
||||
# 2. Salva no arquivo (Modo 'a' = append/adicionar no fim)
|
||||
try:
|
||||
with open(LOG_PATH, 'a', encoding='utf-8') as f:
|
||||
f.write(full_msg + "\n")
|
||||
except Exception as e:
|
||||
print(f"Erro ao salvar log: {e}")
|
||||
|
||||
# --- 1. PERSISTÊNCIA (JSON) ---
|
||||
def load_presets(self):
|
||||
@@ -43,49 +80,45 @@ class DeployManager:
|
||||
json.dump(self.presets, f)
|
||||
self.refresh()
|
||||
|
||||
# --- 2. DIÁLOGO DE CONFIRMAÇÃO DO PRESET (NOVO) ---
|
||||
# --- 2. DIÁLOGO DE CONFIRMAÇÃO DO PRESET ---
|
||||
def confirm_preset_execution(self, name, paths):
|
||||
"""Abre janela para confirmar antes de rodar o Smart Deploy"""
|
||||
src = paths['src']
|
||||
dst = paths['dst']
|
||||
|
||||
# Verificação básica antes de abrir o diálogo
|
||||
if not os.path.exists(src):
|
||||
ui.notify(f'Erro: Pasta de origem não existe: {src}', type='negative')
|
||||
return
|
||||
|
||||
# Conta itens para mostrar no aviso
|
||||
try:
|
||||
items = [f for f in os.listdir(src) if not f.startswith('.')] # Ignora ocultos
|
||||
items = [f for f in os.listdir(src) if not f.startswith('.')]
|
||||
count = len(items)
|
||||
except: count = 0
|
||||
|
||||
with ui.dialog() as dialog, ui.card():
|
||||
ui.label(f'Executar: {name}?').classes('text-xl font-bold text-blue-900')
|
||||
|
||||
ui.label('Isso moverá TODOS os arquivos de:').classes('text-xs text-gray-500 mt-2')
|
||||
ui.label(src).classes('font-mono text-sm bg-gray-100 p-1 rounded w-full break-all')
|
||||
|
||||
ui.label('Para:').classes('text-xs text-gray-500 mt-2')
|
||||
ui.label(dst).classes('font-mono text-sm bg-gray-100 p-1 rounded w-full break-all')
|
||||
ui.label(f'Origem: {src}').classes('font-mono text-xs bg-gray-100 p-1 rounded w-full break-all')
|
||||
ui.label(f'Destino: {dst}').classes('font-mono text-xs bg-gray-100 p-1 rounded w-full break-all')
|
||||
|
||||
if count > 0:
|
||||
ui.label(f'{count} itens encontrados prontos para mover.').classes('font-bold text-green-700 mt-2')
|
||||
ui.label(f'{count} itens encontrados.').classes('font-bold text-green-700 mt-2')
|
||||
else:
|
||||
ui.label('Atenção: A pasta de origem parece vazia.').classes('font-bold text-orange-600 mt-2')
|
||||
|
||||
# Função wrapper para rodar o async corretamente
|
||||
async def execute_action():
|
||||
dialog.close()
|
||||
await asyncio.sleep(0.1)
|
||||
await self.move_process_from_preset(paths)
|
||||
|
||||
with ui.row().classes('w-full justify-end mt-4'):
|
||||
ui.button('Cancelar', on_click=dialog.close).props('flat text-color=grey')
|
||||
# Botão que realmente executa a ação
|
||||
ui.button('CONFIRMAR MOVIMENTAÇÃO',
|
||||
on_click=lambda: [dialog.close(), self.move_process_from_preset(paths)])\
|
||||
.props('color=green icon=check')
|
||||
ui.button('CONFIRMAR', on_click=execute_action).props('color=green icon=check')
|
||||
|
||||
dialog.open()
|
||||
|
||||
# --- 3. MOVIMENTAÇÃO E PENDÊNCIAS ---
|
||||
async def move_process(self, items_to_move, target_folder):
|
||||
"""Move arquivos em background e detecta conflitos"""
|
||||
for item_path in items_to_move:
|
||||
if not os.path.exists(item_path): continue
|
||||
|
||||
@@ -109,7 +142,6 @@ class DeployManager:
|
||||
self.refresh()
|
||||
|
||||
async def move_process_from_preset(self, paths):
|
||||
"""Executa a movimentação após confirmação"""
|
||||
src, dst = paths['src'], paths['dst']
|
||||
if os.path.exists(src):
|
||||
items = [os.path.join(src, f) for f in os.listdir(src)]
|
||||
@@ -148,7 +180,7 @@ class DeployManager:
|
||||
|
||||
if refresh: self.refresh()
|
||||
|
||||
# --- 5. NAVEGAÇÃO (BREADCRUMBS) ---
|
||||
# --- 5. NAVEGAÇÃO ---
|
||||
def render_breadcrumbs(self, current_path, root_dir, nav_callback):
|
||||
with ui.row().classes('items-center gap-1 bg-gray-100 p-1 rounded w-full mb-2'):
|
||||
ui.button('🏠', on_click=lambda: nav_callback(root_dir)).props('flat dense size=sm')
|
||||
@@ -177,10 +209,6 @@ class DeployManager:
|
||||
self.refresh()
|
||||
|
||||
# --- 6. INTERFACE PRINCIPAL ---
|
||||
def add_log(self, message, type="info"):
|
||||
self.logs.insert(0, message)
|
||||
if len(self.logs) > 30: self.logs.pop()
|
||||
|
||||
def refresh(self):
|
||||
if self.container:
|
||||
self.container.clear()
|
||||
@@ -194,7 +222,6 @@ class DeployManager:
|
||||
ui.label('SMART DEPLOYS:').classes('font-bold text-blue-900 mr-4')
|
||||
for name, paths in self.presets.items():
|
||||
with ui.button_group().props('rounded'):
|
||||
# AQUI MUDOU: Chama o diálogo de confirmação em vez de mover direto
|
||||
ui.button(name, on_click=lambda n=name, p=paths: self.confirm_preset_execution(n, p)).props('color=blue-6')
|
||||
ui.button(on_click=lambda n=name: self.delete_preset(n)).props('icon=delete color=red-4')
|
||||
|
||||
@@ -233,10 +260,13 @@ class DeployManager:
|
||||
|
||||
# PAINEL DE LOGS
|
||||
with ui.card().classes('flex-grow h-64 bg-slate-900 text-slate-200 shadow-none'):
|
||||
ui.label('📜 Log de Atividades').classes('font-bold border-b border-slate-700 w-full pb-2')
|
||||
ui.label('📜 Log de Atividades (Persistente)').classes('font-bold border-b border-slate-700 w-full pb-2')
|
||||
with ui.scroll_area().classes('w-full h-full'):
|
||||
# Renderiza os logs carregados
|
||||
for log in self.logs:
|
||||
ui.label(f"> {log}").classes('text-[10px] font-mono leading-tight')
|
||||
# Pinta de vermelho se tiver erro, verde se sucesso
|
||||
color_cls = 'text-red-400' if '❌' in log else 'text-green-400' if '✅' in log else 'text-slate-300'
|
||||
ui.label(f"> {log}").classes(f'text-[10px] font-mono leading-tight {color_cls}')
|
||||
|
||||
# BOTÃO GLOBAL
|
||||
ui.button('INICIAR MOVIMENTAÇÃO DOS SELECIONADOS', on_click=lambda: self.move_process(self.selected_items, self.dst_path))\
|
||||
@@ -244,7 +274,7 @@ class DeployManager:
|
||||
.props('color=green-7 icon=forward')\
|
||||
.bind_enabled_from(self, 'selected_items', backward=lambda x: len(x) > 0)
|
||||
|
||||
# --- AUXILIARES (Listas, Checkbox, etc) ---
|
||||
# --- AUXILIARES ---
|
||||
def render_file_list(self, path, is_source):
|
||||
try:
|
||||
entries = sorted(os.scandir(path), key=lambda e: (not e.is_dir(), e.name.lower()))
|
||||
|
||||
Reference in New Issue
Block a user