feat: Initial commit for OnEver Drive centralized backup system with Windows PyQt6 agent and Proxmox LXC deployment
This commit is contained in:
@@ -0,0 +1,140 @@
|
||||
import os
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
from pathlib import Path
|
||||
from PIL import Image
|
||||
import pystray
|
||||
from pystray import MenuItem as item
|
||||
|
||||
# Add agent root to sys.path
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
||||
|
||||
from agent.config import load_config, AgentConfig
|
||||
from agent.service import AgentDaemon
|
||||
from agent.uploader import ChunkUploader
|
||||
from create_icons import generate_app_icons
|
||||
|
||||
class WindowsTrayAgent:
|
||||
"""Windows System Tray (Área de Notificaciones) Application for OnEver Drive."""
|
||||
|
||||
def __init__(self):
|
||||
self.config = load_config()
|
||||
self.daemon = AgentDaemon(self.config)
|
||||
self.icon = None
|
||||
self.is_paused = False
|
||||
self.icon_image = self._load_icon()
|
||||
|
||||
def _load_icon(self) -> Image.Image:
|
||||
assets_dir = Path(__file__).resolve().parent / "assets"
|
||||
png_path = assets_dir / "icon.png"
|
||||
if not png_path.exists():
|
||||
_, png_path = generate_app_icons()
|
||||
return Image.open(png_path)
|
||||
|
||||
def _get_status_text(self) -> str:
|
||||
if not self.config.client_code:
|
||||
return "Estado: Sin Registrar"
|
||||
if self.is_paused:
|
||||
return "Estado: Pausado"
|
||||
return f"Estado: Conectado ({self.config.client_code})"
|
||||
|
||||
def _toggle_pause(self, icon, item_obj):
|
||||
self.is_paused = not self.is_paused
|
||||
if self.is_paused:
|
||||
self.daemon.stop()
|
||||
self.notify("Sincronización en pausa", "El servicio de backup ha sido pausado.")
|
||||
else:
|
||||
self.daemon.start()
|
||||
self.notify("Sincronización activa", "El servicio de backup se ha reanudado.")
|
||||
|
||||
def _open_gui(self, icon=None, item_obj=None):
|
||||
def run_gui():
|
||||
from agent_gui import launch_gui
|
||||
launch_gui()
|
||||
|
||||
threading.Thread(target=run_gui, daemon=True).start()
|
||||
|
||||
def _manual_backup(self, icon=None, item_obj=None):
|
||||
import tkinter as tk
|
||||
from tkinter import filedialog, messagebox
|
||||
|
||||
def run_picker():
|
||||
root = tk.Tk()
|
||||
root.withdraw()
|
||||
filepath = filedialog.askopenfilename(
|
||||
title="Seleccionar archivo para backup",
|
||||
filetypes=[("Archivos SQL / Datos", "*.bak;*.mdf;*.*")]
|
||||
)
|
||||
if not filepath:
|
||||
root.destroy()
|
||||
return
|
||||
|
||||
self.notify("Iniciando Backup", f"Preparando transferencia por chunks: {Path(filepath).name}")
|
||||
|
||||
def upload_worker():
|
||||
try:
|
||||
uploader = ChunkUploader(self.config)
|
||||
res = uploader.upload_file(Path(filepath))
|
||||
self.notify("Backup Completado ✓", f"{Path(filepath).name} verificado con éxito en el servidor.")
|
||||
except Exception as ex:
|
||||
self.notify("Error en Backup ✗", f"Fallo al subir {Path(filepath).name}: {str(ex)}")
|
||||
|
||||
threading.Thread(target=upload_worker, daemon=True).start()
|
||||
root.destroy()
|
||||
|
||||
threading.Thread(target=run_picker, daemon=True).start()
|
||||
|
||||
def notify(self, title: str, message: str):
|
||||
"""Displays a native Windows Notification Balloon."""
|
||||
if self.icon:
|
||||
try:
|
||||
self.icon.notify(message, title)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _on_exit(self, icon, item_obj):
|
||||
self.daemon.stop()
|
||||
icon.stop()
|
||||
|
||||
def build_menu(self):
|
||||
client_title = f"OnEver Drive ({self.config.client_name or 'Agente'})"
|
||||
return pystray.Menu(
|
||||
item(client_title, lambda: None, enabled=False),
|
||||
item(lambda text: self._get_status_text(), lambda: None, enabled=False),
|
||||
pystray.Menu.SEPARATOR,
|
||||
item("Abrir Panel de Control...", self._open_gui, default=True),
|
||||
item("Hacer Backup Manual...", self._manual_backup),
|
||||
item(lambda text: "Reanudar Servicio" if self.is_paused else "Pausar Servicio", self._toggle_pause),
|
||||
pystray.Menu.SEPARATOR,
|
||||
item("Salir", self._on_exit)
|
||||
)
|
||||
|
||||
def run(self):
|
||||
# Start background daemon worker
|
||||
if self.config.device_id:
|
||||
self.daemon.start()
|
||||
|
||||
# Create tray icon
|
||||
self.icon = pystray.Icon(
|
||||
name="OnEverDrive",
|
||||
icon=self.icon_image,
|
||||
title="OnEver Drive — Agente de Backup",
|
||||
menu=self.build_menu()
|
||||
)
|
||||
|
||||
# Notify on startup
|
||||
if self.config.client_code:
|
||||
self.notify("OnEver Drive Activo", f"Agente en ejecución ({self.config.client_code})")
|
||||
else:
|
||||
self.notify("OnEver Drive", "Agente iniciado. Requiere registro en el servidor.")
|
||||
|
||||
# Run system tray event loop
|
||||
self.icon.run()
|
||||
|
||||
def main():
|
||||
agent = WindowsTrayAgent()
|
||||
agent.run()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user