Actualiza barra de estado y README
This commit is contained in:
parent
9f7d1e247c
commit
2d62bf2530
83
README.md
83
README.md
|
|
@ -1,67 +1,68 @@
|
||||||
# Proyecto Global Dashboard
|
# Proyecto Global Dashboard
|
||||||
|
|
||||||
Panel de control escrito en Python 3.14 + Tkinter que reúne las prácticas solicitadas (scraping, monitorización, alarmas, notas, música y más) con una estética cuidada y paneles diferenciados.
|
Laboratorio interactivo construido con Python 3.13 + Tkinter. Reúne scraping, monitorización, chat TCP, alarmas, reproductor musical y utilidades varias en una única aplicación de escritorio.
|
||||||
|
|
||||||
## 🚀 Características principales
|
## 🎬 Demo en video
|
||||||
|
|
||||||
- **Dashboard modular**: panel izquierdo con accesos rápidos (scraping, clima de Jávea, Camellos, copias de seguridad, etc.), cuaderno central por pestañas y panel derecho con chat y listado de alumnos.
|
- YouTube: https://youtu.be/HgJwU_HagD8
|
||||||
- **Scraping integrado**: workflows para Wallapop y scraping genérico con popups dedicados y avisos de estado.
|
|
||||||
- **Monitor de sistema**: gráficas PSUtil actualizadas mediante `after` de Tk, evitando bloqueos y mostrando CPU/RAM/Net de forma fluida.
|
## 🚀 Características clave
|
||||||
- **Bloc de notas y backups reales**: edición rápida de texto con copias automáticas a una carpeta de respaldo mostrando progreso.
|
|
||||||
- **Widgets temáticos**: reproductor musical con tarjetas, gestor de alarmas rediseñado y popup meteorológico (OpenWeather, coordenadas de Jávea) cacheado para reducir llamadas.
|
- **Layout triple panel**: accesos rápidos a la izquierda, notebook central con pestañas temáticas y panel derecho para chat y utilidades.
|
||||||
- **Servidor de mensajería**: `servidor.py` permite broadcast TCP para pruebas de chat local.
|
- **Scraping Wallapop + genérico**: asistentes emergentes, validaciones y guardado de resultados.
|
||||||
|
- **Monitor de sistema**: gráficas en vivo de CPU, RAM e hilos gracias a psutil y matplotlib embebido.
|
||||||
|
- **Productividad integrada**: bloc de notas, gestor de alarmas, reproductor musical con pygame y lanzadores de procesos.
|
||||||
|
- **Popup meteorológico**: consulta OpenWeather para Jávea con caché y resumen formateado.
|
||||||
|
- **Servidor TCP incluido**: `servidor.py` permite pruebas de chat broadcast desde la propia app.
|
||||||
|
|
||||||
## ⚙️ Requisitos
|
## ⚙️ Requisitos
|
||||||
|
|
||||||
- Python 3.8 o superior (desarrollado con 3.14)
|
- Python 3.8 o superior (desarrollado en 3.13)
|
||||||
- Dependencias listadas en `requirements.txt`
|
- Dependencias listadas en `requirements.txt`
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
pip install -r requirements.txt
|
pip install -r requirements.txt
|
||||||
```
|
```
|
||||||
|
|
||||||
## ▶️ Puesta en marcha rápida
|
## ▶️ Puesta en marcha
|
||||||
|
|
||||||
1. (Opcional) Arranca el servidor de mensajería:
|
1. (Opcional) Inicia el servidor de chat:
|
||||||
```sh
|
```sh
|
||||||
python3 servidor.py
|
python3 servidor.py
|
||||||
```
|
```
|
||||||
Verás `Servidor escuchando en 0.0.0.0:3333` en consola.
|
2. Lanza el panel principal:
|
||||||
2. Lanza la interfaz gráfica:
|
```sh
|
||||||
```sh
|
python3 app.py
|
||||||
python3 app.py
|
```
|
||||||
```
|
3. Usa el panel izquierdo para abrir scraping, notas, alarmas o el popup del clima; el panel derecho gestiona el chat y el reproductor.
|
||||||
3. Desde el panel derecho ajusta host/puerto y pulsa `Conectar` para chatear. Explora el resto de pestañas (scraping, notas, alarmas, música, clima) desde los botones laterales.
|
|
||||||
|
|
||||||
## 🧱 Arquitectura de carpetas
|
## 🧱 Estructura del proyecto
|
||||||
|
|
||||||
```
|
```
|
||||||
app.py # GUI principal y lógica de scraping, clima, monitorización, alarmas...
|
app.py # GUI principal y lógica de negocio
|
||||||
servidor.py # Servidor TCP broadcast para el chat de pruebas
|
servidor.py # Servidor TCP broadcast para el chat
|
||||||
requirements.txt # Dependencias del proyecto
|
requirements.txt # Lista de dependencias
|
||||||
README.md # Este archivo
|
README.md # Documentación
|
||||||
```
|
```
|
||||||
|
|
||||||
## 🛠️ Funcionalidades destacadas
|
## 🛠️ Flujos destacados
|
||||||
|
|
||||||
- **Scraping Wallapop y genérico**: ventanas emergentes, peticiones HTTP con Requests + BeautifulSoup, mensajes de éxito/error.
|
- **Scraping**: workers en segundo plano con colas y retroalimentación visual en la pestaña de resultados.
|
||||||
- **Weather popup “API Tiempo”**: botón dedicado que consulta OpenWeather (con clave fallback), muestra iconos, temperaturas y caché temporal.
|
- **Copias de seguridad**: selección interactiva de carpetas y reporte final con totales copiados/omitidos.
|
||||||
- **Copias de seguridad guiadas**: barra de progreso y notificaciones durante la duplicación de directorios.
|
- **Gestor de alarmas**: creación, cancelación y popups dedicados con recordatorio sonoro.
|
||||||
- **Editor y bloc de notas**: pestañas separadas para notas rápidas y bloc organizado.
|
- **Panel de recursos**: mezcla de gráficas lineales, de área y de barras para CPU/RAM/hilos.
|
||||||
- **Gestor de alarmas**: UI modernizada con tarjetas, botones primarios y feedback visual.
|
- **Popup “API Tiempo”**: resumen meteorológico en ventana modal con refresco bajo demanda.
|
||||||
- **Música y utilidades**: reproductor basado en pygame y accesos a herramientas externas (“Camellos”, lanzadores, etc.).
|
|
||||||
|
|
||||||
## 🌤️ Servicios externos
|
## ⚙️ Configuración opcional
|
||||||
|
|
||||||
- **OpenWeatherMap**: usado para el popup del clima (coordenadas de Jávea). Define `OPENWEATHER_API_KEY` en el entorno para usar tu propia clave.
|
- `OPENWEATHER_API_KEY` / `OPENWEATHER_FALLBACK_API_KEY`: claves para OpenWeather.
|
||||||
- **Wallapop / sitios objetivo**: las rutinas de scraping respetan temporizadores y headers básicos; ajusta las URLs o parámetros dentro de `app.py` para nuevos escenarios.
|
- Variables `WALLAPOP_*`: encabezados y parámetros usados por el scraper.
|
||||||
|
- Ajusta el host/puerto del chat en el panel derecho o modificando `SERVER_HOST_DEFAULT` y `SERVER_PORT_DEFAULT` en `app.py`.
|
||||||
|
|
||||||
## 📌 Próximos pasos sugeridos
|
## 📌 Próximos pasos sugeridos
|
||||||
|
|
||||||
1. Añadir pruebas unitarias para la lógica no gráfica (scraping, backups, parsers).
|
1. Añadir almacenamiento persistente (SQLite) para chats y notas.
|
||||||
2. Persistir chats y notas en SQLite para mantener el historial.
|
2. Incorporar pruebas unitarias para scraping y rutinas de backup.
|
||||||
3. Integrar reproductor completo dentro de la app (playlist, carátulas).
|
3. Extender el reproductor musical con colas y visualizaciones.
|
||||||
|
|
||||||
---
|
¿Quieres ampliar alguna sección (scraping extra, nuevos paneles, automatización de tareas)? Adelante, la base está lista para seguir creciendo.
|
||||||
|
|
||||||
¿Necesitas extender alguna funcionalidad? Abre un issue o comenta qué módulo quieres potenciar (más scraping, dashboards adicionales, automatización de backups, etc.).
|
|
||||||
|
|
|
||||||
46
app.py
46
app.py
|
|
@ -68,7 +68,6 @@ OPENWEATHER_FALLBACK_API_KEY = os.environ.get(
|
||||||
).strip()
|
).strip()
|
||||||
_OPENWEATHER_ENV_KEY = os.environ.get('OPENWEATHER_API_KEY', '').strip()
|
_OPENWEATHER_ENV_KEY = os.environ.get('OPENWEATHER_API_KEY', '').strip()
|
||||||
OPENWEATHER_API_KEY = _OPENWEATHER_ENV_KEY or OPENWEATHER_FALLBACK_API_KEY
|
OPENWEATHER_API_KEY = _OPENWEATHER_ENV_KEY or OPENWEATHER_FALLBACK_API_KEY
|
||||||
OPENWEATHER_CITY = os.environ.get('OPENWEATHER_CITY', 'Madrid,ES')
|
|
||||||
JAVEA_LATITUDE = 38.789166
|
JAVEA_LATITUDE = 38.789166
|
||||||
JAVEA_LONGITUDE = 0.163055
|
JAVEA_LONGITUDE = 0.163055
|
||||||
|
|
||||||
|
|
@ -232,16 +231,12 @@ class DashboardApp(tk.Tk):
|
||||||
self.game_camel_count = 3
|
self.game_camel_count = 3
|
||||||
self.game_speed_factor = 1.0
|
self.game_speed_factor = 1.0
|
||||||
self.music_temp_file: str | None = None
|
self.music_temp_file: str | None = None
|
||||||
self.weather_city = OPENWEATHER_CITY
|
|
||||||
self.weather_api_key = OPENWEATHER_API_KEY
|
self.weather_api_key = OPENWEATHER_API_KEY
|
||||||
self.results_area: tk.Frame | None = None
|
self.results_area: tk.Frame | None = None
|
||||||
self.results_title: tk.Label | None = None
|
self.results_title: tk.Label | None = None
|
||||||
self.scraping_popup: tk.Toplevel | None = None
|
self.scraping_popup: tk.Toplevel | None = None
|
||||||
self.simple_scraping_popup: tk.Toplevel | None = None
|
self.simple_scraping_popup: tk.Toplevel | None = None
|
||||||
self.weather_popup: tk.Toplevel | None = None
|
self.weather_popup: tk.Toplevel | None = None
|
||||||
self._last_weather_data: dict[str, Any] | None = None
|
|
||||||
self._last_weather_error: str | None = None
|
|
||||||
self._last_weather_timestamp: datetime.datetime | None = None
|
|
||||||
self.chart_canvas = None
|
self.chart_canvas = None
|
||||||
self.ax_cpu = None
|
self.ax_cpu = None
|
||||||
self.ax_mem = None
|
self.ax_mem = None
|
||||||
|
|
@ -263,8 +258,8 @@ class DashboardApp(tk.Tk):
|
||||||
self._build_center_panel()
|
self._build_center_panel()
|
||||||
self._build_right_panel()
|
self._build_right_panel()
|
||||||
self._build_status_bar()
|
self._build_status_bar()
|
||||||
|
|
||||||
self._update_clock()
|
self._update_clock()
|
||||||
|
|
||||||
if psutil:
|
if psutil:
|
||||||
self.after(1000, self._update_traffic)
|
self.after(1000, self._update_traffic)
|
||||||
try:
|
try:
|
||||||
|
|
@ -275,8 +270,6 @@ class DashboardApp(tk.Tk):
|
||||||
threading.Thread(target=self._chat_loop, daemon=True).start()
|
threading.Thread(target=self._chat_loop, daemon=True).start()
|
||||||
self.after(100, self._process_scraping_queue)
|
self.after(100, self._process_scraping_queue)
|
||||||
self.after(1000, self._refresh_alarms_loop)
|
self.after(1000, self._refresh_alarms_loop)
|
||||||
if REQUESTS_AVAILABLE and self.weather_api_key:
|
|
||||||
self.after(2000, self._update_weather)
|
|
||||||
|
|
||||||
# ------------------------ UI ------------------------
|
# ------------------------ UI ------------------------
|
||||||
def _maximize_with_borders(self) -> None:
|
def _maximize_with_borders(self) -> None:
|
||||||
|
|
@ -765,7 +758,7 @@ class DashboardApp(tk.Tk):
|
||||||
def _build_status_bar(self) -> None:
|
def _build_status_bar(self) -> None:
|
||||||
status = tk.Frame(self, bg='#f1f1f1', bd=2, relief='ridge')
|
status = tk.Frame(self, bg='#f1f1f1', bd=2, relief='ridge')
|
||||||
status.grid(row=2, column=0, columnspan=3, sticky='ew')
|
status.grid(row=2, column=0, columnspan=3, sticky='ew')
|
||||||
for idx in range(4):
|
for idx in range(3):
|
||||||
status.columnconfigure(idx, weight=1)
|
status.columnconfigure(idx, weight=1)
|
||||||
|
|
||||||
tk.Label(status, text='Correos sin leer', font=('Arial', 11, 'bold'), bg='#f1f1f1').grid(row=0, column=0, padx=16, pady=6, sticky='w')
|
tk.Label(status, text='Correos sin leer', font=('Arial', 11, 'bold'), bg='#f1f1f1').grid(row=0, column=0, padx=16, pady=6, sticky='w')
|
||||||
|
|
@ -778,11 +771,8 @@ class DashboardApp(tk.Tk):
|
||||||
)
|
)
|
||||||
self.traffic_label.grid(row=0, column=1, padx=16, pady=6)
|
self.traffic_label.grid(row=0, column=1, padx=16, pady=6)
|
||||||
|
|
||||||
self.weather_label = tk.Label(status, text='Clima: configure API', font=('Arial', 11, 'bold'), bg='#f1f1f1')
|
|
||||||
self.weather_label.grid(row=0, column=2, padx=16, pady=6, sticky='n')
|
|
||||||
|
|
||||||
self.clock_label = tk.Label(status, text='--:--:--', font=('Arial', 12, 'bold'), bg='#f1f1f1')
|
self.clock_label = tk.Label(status, text='--:--:--', font=('Arial', 12, 'bold'), bg='#f1f1f1')
|
||||||
self.clock_label.grid(row=0, column=3, padx=16, pady=6, sticky='e')
|
self.clock_label.grid(row=0, column=2, padx=16, pady=6, sticky='e')
|
||||||
|
|
||||||
# ------------------------ acciones ------------------------
|
# ------------------------ acciones ------------------------
|
||||||
def _open_web(self, url: str) -> None:
|
def _open_web(self, url: str) -> None:
|
||||||
|
|
@ -2020,36 +2010,6 @@ class DashboardApp(tk.Tk):
|
||||||
def _fetch_javea_weather(self) -> dict[str, Any]:
|
def _fetch_javea_weather(self) -> dict[str, Any]:
|
||||||
return self._fetch_weather_by_coordinates(JAVEA_LATITUDE, JAVEA_LONGITUDE)
|
return self._fetch_weather_by_coordinates(JAVEA_LATITUDE, JAVEA_LONGITUDE)
|
||||||
|
|
||||||
def _update_weather(self) -> None:
|
|
||||||
if not self._running:
|
|
||||||
return
|
|
||||||
try:
|
|
||||||
data = self._fetch_weather_data(self.weather_city)
|
|
||||||
temp = data.get('main', {}).get('temp')
|
|
||||||
desc = data.get('weather', [{}])[0].get('description', '').capitalize()
|
|
||||||
city = data.get('name') or self.weather_city
|
|
||||||
if temp is None:
|
|
||||||
raise ValueError('Respuesta sin temperatura')
|
|
||||||
self.weather_label.config(text=f'{city}: {temp:.1f}°C, {desc}')
|
|
||||||
self._last_weather_data = data
|
|
||||||
self._last_weather_error = None
|
|
||||||
self._last_weather_timestamp = datetime.datetime.now()
|
|
||||||
except RuntimeError as exc:
|
|
||||||
self.weather_label.config(text='Clima: configure API')
|
|
||||||
self._log(f'Clima: {exc}')
|
|
||||||
self._last_weather_data = None
|
|
||||||
self._last_weather_error = str(exc)
|
|
||||||
self._last_weather_timestamp = datetime.datetime.now()
|
|
||||||
except Exception as exc:
|
|
||||||
self.weather_label.config(text='Clima: N/D')
|
|
||||||
self._log(f'Clima: {exc}')
|
|
||||||
self._last_weather_data = None
|
|
||||||
self._last_weather_error = str(exc)
|
|
||||||
self._last_weather_timestamp = datetime.datetime.now()
|
|
||||||
finally:
|
|
||||||
if self._running:
|
|
||||||
self.after(300000, self._update_weather)
|
|
||||||
|
|
||||||
# ------------------------ chat ------------------------
|
# ------------------------ chat ------------------------
|
||||||
def _connect_chat(self) -> None:
|
def _connect_chat(self) -> None:
|
||||||
host = self.host_entry.get().strip() or SERVER_HOST_DEFAULT
|
host = self.host_entry.get().strip() or SERVER_HOST_DEFAULT
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue