feat: Implement system tray integration and enhance application features
This commit is contained in:
parent
bcd4f126ae
commit
5d1dd2ea52
39
README.md
39
README.md
|
@ -1,7 +1,7 @@
|
||||||
# AutoBackups Project
|
# AutoBackups Project
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
AutoBackups is a Python application designed to automate the backup of Simatic S7 projects and other user-defined directories. The application leverages the Everything API to efficiently locate files and manage backups based on user-defined configurations.
|
AutoBackups is a Python application designed to automate the backup of Simatic S7 projects and other user-defined directories. The application leverages the Everything API to efficiently locate files and manage backups based on user-defined configurations. Features a system tray integration for easy access and background operation.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
- Monitors specified directories for Simatic S7 files (*.s7p) and other user-defined directories.
|
- Monitors specified directories for Simatic S7 files (*.s7p) and other user-defined directories.
|
||||||
|
@ -10,6 +10,8 @@ AutoBackups is a Python application designed to automate the backup of Simatic S
|
||||||
- Skips projects with files in use and retries after one hour.
|
- Skips projects with files in use and retries after one hour.
|
||||||
- Maintains a two-stage hash system: first checking *.s7p files, then all files to avoid unnecessary backups.
|
- Maintains a two-stage hash system: first checking *.s7p files, then all files to avoid unnecessary backups.
|
||||||
- User-friendly web interface built with Flask for configuration and status monitoring.
|
- User-friendly web interface built with Flask for configuration and status monitoring.
|
||||||
|
- **System tray integration** with options to open browser and close application.
|
||||||
|
- **Custom favicon** for both frontend and system tray.
|
||||||
- Comprehensive logging system with timestamped log files.
|
- Comprehensive logging system with timestamped log files.
|
||||||
|
|
||||||
## Project Structure
|
## Project Structure
|
||||||
|
@ -24,14 +26,19 @@ AutoBackups is a Python application designed to automate the backup of Simatic S
|
||||||
│ ├── services
|
│ ├── services
|
||||||
│ │ └── __init__.py # Business logic for backups
|
│ │ └── __init__.py # Business logic for backups
|
||||||
│ └── utils
|
│ └── utils
|
||||||
│ └── __init__.py # Utility functions for hashing and file access
|
│ ├── __init__.py # Utility functions for hashing and file access
|
||||||
|
│ └── system_tray.py # System tray functionality
|
||||||
├── static
|
├── static
|
||||||
│ ├── css # CSS files for styling
|
│ ├── css # CSS files for styling
|
||||||
|
│ ├── icons
|
||||||
|
│ │ └── favicon.png # Application icon (used in web and system tray)
|
||||||
│ └── js # JavaScript files for client-side functionality
|
│ └── js # JavaScript files for client-side functionality
|
||||||
├── templates
|
├── templates
|
||||||
│ └── index.html # Main HTML template for the web interface
|
│ └── index.html # Main HTML template for the web interface
|
||||||
├── config.json # Global configuration settings for the application
|
├── config.json # Global configuration settings for the application
|
||||||
├── projects.json # Per-project configuration, schedules, and hash storage
|
├── projects.json # Per-project configuration, schedules, and hash storage
|
||||||
|
├── launch_tray.py # System tray launcher
|
||||||
|
├── launch_with_tray.bat # Windows batch file for easy launching
|
||||||
├── requirements.txt # Python dependencies
|
├── requirements.txt # Python dependencies
|
||||||
└── README.md # Project documentation
|
└── README.md # Project documentation
|
||||||
```
|
```
|
||||||
|
@ -57,13 +64,39 @@ AutoBackups is a Python application designed to automate the backup of Simatic S
|
||||||
4. Configure the application by editing `config.json` to specify directories to monitor and backup settings.
|
4. Configure the application by editing `config.json` to specify directories to monitor and backup settings.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
|
### Regular Mode
|
||||||
- Start the Flask application:
|
- Start the Flask application:
|
||||||
```
|
```
|
||||||
python src/app.py
|
python src/app.py
|
||||||
```
|
```
|
||||||
|
|
||||||
- Access the web interface at `http://localhost:5120` to configure settings and monitor backup statuses.
|
- Access the web interface at `http://localhost:5120` to configure settings and monitor backup statuses.
|
||||||
|
|
||||||
|
### System Tray Mode (Recommended)
|
||||||
|
- Start the application with system tray integration:
|
||||||
|
```
|
||||||
|
python launch_tray.py
|
||||||
|
```
|
||||||
|
- Or use the Windows batch file:
|
||||||
|
```
|
||||||
|
launch_with_tray.bat
|
||||||
|
```
|
||||||
|
|
||||||
|
#### System Tray Features:
|
||||||
|
- **Auto-start**: Application starts in the background with system tray icon
|
||||||
|
- **Quick Access**: Double-click the tray icon to open the web interface
|
||||||
|
- **Right-click Menu**:
|
||||||
|
- "Open AutoBackups" - Opens the web interface in your default browser
|
||||||
|
- "Status" - Shows application status (future feature)
|
||||||
|
- "Exit" - Safely closes the application
|
||||||
|
- **Custom Icon**: Uses `static/icons/favicon.png` for both web interface and system tray
|
||||||
|
- **Silent Operation**: Application runs silently in the background
|
||||||
|
|
||||||
|
#### System Tray Requirements:
|
||||||
|
- Windows 10/11 (tested)
|
||||||
|
- Python packages: `pystray`, `pillow` (automatically installed)
|
||||||
|
- Valid favicon.png file in `static/icons/`
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
Contributions are welcome! Please submit a pull request or open an issue for any enhancements or bug fixes.
|
Contributions are welcome! Please submit a pull request or open an issue for any enhancements or bug fixes.
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,12 @@
|
||||||
"type": "siemens_s7",
|
"type": "siemens_s7",
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"description": "Directorio principal de proyectos Siemens"
|
"description": "Directorio principal de proyectos Siemens"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "C:/Trabajo/SIDEL/05 - E5.007161 - Modifica O&U - SAE346",
|
||||||
|
"type": "siemens_s7",
|
||||||
|
"enabled": true,
|
||||||
|
"description": "SAE346"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"backup_destination": "D:\\Backups\\AutoBackups",
|
"backup_destination": "D:\\Backups\\AutoBackups",
|
||||||
|
|
|
@ -0,0 +1,94 @@
|
||||||
|
"""
|
||||||
|
AutoBackups System Tray Launcher
|
||||||
|
Lanza la aplicación AutoBackups con icono en el system tray
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
import logging
|
||||||
|
|
||||||
|
# Agregar el directorio src al path para imports
|
||||||
|
current_dir = Path(__file__).parent
|
||||||
|
src_dir = current_dir / "src"
|
||||||
|
sys.path.insert(0, str(src_dir))
|
||||||
|
|
||||||
|
# Imports de la aplicación
|
||||||
|
from src.app import AutoBackupsFlaskApp, app
|
||||||
|
|
||||||
|
|
||||||
|
def run_flask_app(autobackups_app, host, port, debug=False):
|
||||||
|
"""Ejecuta la aplicación Flask en un hilo separado"""
|
||||||
|
try:
|
||||||
|
app.run(host=host, port=port, debug=debug, use_reloader=False)
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"Error ejecutando Flask app: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Función principal para el launcher del system tray"""
|
||||||
|
try:
|
||||||
|
print("AutoBackups - System Tray Mode")
|
||||||
|
print("=" * 30)
|
||||||
|
|
||||||
|
# Crear y configurar aplicación Flask
|
||||||
|
autobackups_app = AutoBackupsFlaskApp()
|
||||||
|
|
||||||
|
# Verificar requerimientos del sistema
|
||||||
|
if not autobackups_app.check_system_requirements():
|
||||||
|
print("Error en la verificación de requerimientos del sistema.")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Ejecutar descubrimiento inicial de proyectos
|
||||||
|
projects_found = autobackups_app.discover_projects()
|
||||||
|
msg = f"Descubrimiento inicial: {projects_found} proyectos encontrados"
|
||||||
|
print(msg)
|
||||||
|
|
||||||
|
# Configurar Flask
|
||||||
|
host = autobackups_app.config.web_interface.get("host", "127.0.0.1")
|
||||||
|
port = autobackups_app.config.web_interface.get("port", 5000)
|
||||||
|
debug = autobackups_app.config.web_interface.get("debug", False)
|
||||||
|
|
||||||
|
print(f"Servidor web iniciando en http://{host}:{port}")
|
||||||
|
|
||||||
|
# Iniciar system tray
|
||||||
|
if autobackups_app.start_system_tray():
|
||||||
|
print("✓ System tray iniciado correctamente")
|
||||||
|
else:
|
||||||
|
print("⚠ System tray no disponible, continuando...")
|
||||||
|
|
||||||
|
# Ejecutar Flask en un hilo separado
|
||||||
|
flask_thread = threading.Thread(
|
||||||
|
target=run_flask_app, args=(autobackups_app, host, port, debug), daemon=True
|
||||||
|
)
|
||||||
|
flask_thread.start()
|
||||||
|
|
||||||
|
print("✓ Aplicación iniciada")
|
||||||
|
print("• Accede desde el icono del system tray")
|
||||||
|
print("• Usa Ctrl+C para salir desde la consola")
|
||||||
|
print("• O usa 'Exit' desde el menú del system tray")
|
||||||
|
|
||||||
|
# Mantener el programa corriendo
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
time.sleep(1)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("\nCerrando aplicación...")
|
||||||
|
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("\nAplicación interrumpida por el usuario")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error inesperado: {e}")
|
||||||
|
sys.exit(1)
|
||||||
|
finally:
|
||||||
|
# Limpieza
|
||||||
|
if "autobackups_app" in locals():
|
||||||
|
if autobackups_app.scheduler:
|
||||||
|
autobackups_app.scheduler.shutdown()
|
||||||
|
if autobackups_app.system_tray:
|
||||||
|
autobackups_app.system_tray.stop_tray()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
|
@ -0,0 +1,24 @@
|
||||||
|
@echo off
|
||||||
|
echo AutoBackups - Launching with System Tray...
|
||||||
|
echo.
|
||||||
|
|
||||||
|
REM Activate conda environment
|
||||||
|
call conda activate autobackups
|
||||||
|
|
||||||
|
REM Check if activation was successful
|
||||||
|
if errorlevel 1 (
|
||||||
|
echo Error: Could not activate conda environment 'autobackups'
|
||||||
|
echo Please make sure the environment exists and conda is installed.
|
||||||
|
pause
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
|
||||||
|
REM Launch the application with system tray
|
||||||
|
python launch_tray.py
|
||||||
|
|
||||||
|
REM Pause to see any error messages
|
||||||
|
if errorlevel 1 (
|
||||||
|
echo.
|
||||||
|
echo Application exited with an error.
|
||||||
|
pause
|
||||||
|
)
|
304
projects.json
304
projects.json
|
@ -1,8 +1,8 @@
|
||||||
{
|
{
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"version": "1.0",
|
"version": "1.0",
|
||||||
"last_updated": "2025-09-02T07:15:16.655495+00:00",
|
"last_updated": "2025-09-02T10:51:48.579987+00:00",
|
||||||
"total_projects": 3
|
"total_projects": 9
|
||||||
},
|
},
|
||||||
"projects": [
|
"projects": [
|
||||||
{
|
{
|
||||||
|
@ -21,10 +21,10 @@
|
||||||
"next_scheduled_backup": ""
|
"next_scheduled_backup": ""
|
||||||
},
|
},
|
||||||
"backup_history": {
|
"backup_history": {
|
||||||
"last_backup_date": "2025-09-02T08:48:07.555782+00:00",
|
"last_backup_date": "2025-09-02T10:52:24.182464+00:00",
|
||||||
"last_backup_file": "D:\\Backups\\AutoBackups\\Ssae0452 Last Version Walter\\2025-09-02\\10-48-04_projects.zip",
|
"last_backup_file": "D:\\Backups\\AutoBackups\\Ssae0452 Last Version Walter\\2025-09-02\\12-52-22_projects.zip",
|
||||||
"backup_count": 1,
|
"backup_count": 2,
|
||||||
"last_successful_backup": "2025-09-02T08:48:07.555782+00:00"
|
"last_successful_backup": "2025-09-02T10:52:24.182464+00:00"
|
||||||
},
|
},
|
||||||
"hash_info": {
|
"hash_info": {
|
||||||
"last_s7p_hash": "",
|
"last_s7p_hash": "",
|
||||||
|
@ -42,7 +42,7 @@
|
||||||
"next_retry": null,
|
"next_retry": null,
|
||||||
"files_in_use": false,
|
"files_in_use": false,
|
||||||
"exclusivity_check_passed": true,
|
"exclusivity_check_passed": true,
|
||||||
"last_status_update": "2025-09-02T08:48:07.555782+00:00"
|
"last_status_update": "2025-09-02T10:52:24.182464+00:00"
|
||||||
},
|
},
|
||||||
"discovery_info": {
|
"discovery_info": {
|
||||||
"discovered_date": "",
|
"discovered_date": "",
|
||||||
|
@ -66,10 +66,10 @@
|
||||||
"next_scheduled_backup": ""
|
"next_scheduled_backup": ""
|
||||||
},
|
},
|
||||||
"backup_history": {
|
"backup_history": {
|
||||||
"last_backup_date": "2025-09-02T08:48:07.539194+00:00",
|
"last_backup_date": "2025-09-02T10:52:24.159989+00:00",
|
||||||
"last_backup_file": "D:\\Backups\\AutoBackups\\LineaB\\Ssae04_11 - compiled\\2025-09-02\\10-48-04_projects.zip",
|
"last_backup_file": "D:\\Backups\\AutoBackups\\LineaB\\Ssae04_11 - compiled\\2025-09-02\\12-52-22_projects.zip",
|
||||||
"backup_count": 2,
|
"backup_count": 3,
|
||||||
"last_successful_backup": "2025-09-02T08:48:07.539194+00:00"
|
"last_successful_backup": "2025-09-02T10:52:24.159989+00:00"
|
||||||
},
|
},
|
||||||
"hash_info": {
|
"hash_info": {
|
||||||
"last_s7p_hash": "",
|
"last_s7p_hash": "",
|
||||||
|
@ -87,7 +87,7 @@
|
||||||
"next_retry": null,
|
"next_retry": null,
|
||||||
"files_in_use": false,
|
"files_in_use": false,
|
||||||
"exclusivity_check_passed": true,
|
"exclusivity_check_passed": true,
|
||||||
"last_status_update": "2025-09-02T08:48:07.539194+00:00"
|
"last_status_update": "2025-09-02T10:52:24.159989+00:00"
|
||||||
},
|
},
|
||||||
"discovery_info": {
|
"discovery_info": {
|
||||||
"discovered_date": "",
|
"discovered_date": "",
|
||||||
|
@ -111,10 +111,10 @@
|
||||||
"next_scheduled_backup": ""
|
"next_scheduled_backup": ""
|
||||||
},
|
},
|
||||||
"backup_history": {
|
"backup_history": {
|
||||||
"last_backup_date": "2025-09-02T08:48:07.592782+00:00",
|
"last_backup_date": "2025-09-02T10:52:24.172317+00:00",
|
||||||
"last_backup_file": "D:\\Backups\\AutoBackups\\LineaB\\Ssae04_14 - TIA\\2025-09-02\\10-48-04_projects.zip",
|
"last_backup_file": "D:\\Backups\\AutoBackups\\LineaB\\Ssae04_14 - TIA\\2025-09-02\\12-52-22_projects.zip",
|
||||||
"backup_count": 1,
|
"backup_count": 2,
|
||||||
"last_successful_backup": "2025-09-02T08:48:07.592782+00:00"
|
"last_successful_backup": "2025-09-02T10:52:24.172317+00:00"
|
||||||
},
|
},
|
||||||
"hash_info": {
|
"hash_info": {
|
||||||
"last_s7p_hash": "",
|
"last_s7p_hash": "",
|
||||||
|
@ -132,7 +132,277 @@
|
||||||
"next_retry": null,
|
"next_retry": null,
|
||||||
"files_in_use": false,
|
"files_in_use": false,
|
||||||
"exclusivity_check_passed": true,
|
"exclusivity_check_passed": true,
|
||||||
"last_status_update": "2025-09-02T08:48:07.592782+00:00"
|
"last_status_update": "2025-09-02T10:52:24.172317+00:00"
|
||||||
|
},
|
||||||
|
"discovery_info": {
|
||||||
|
"discovered_date": "",
|
||||||
|
"discovery_method": "",
|
||||||
|
"auto_discovered": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "project_c437a7bb_20250902_125148",
|
||||||
|
"name": "Inverter",
|
||||||
|
"path": "C:\\Trabajo\\SIDEL\\05 - E5.007161 - Modifica O&U - SAE346\\InLavoro\\PLC\\Varios\\Inverter",
|
||||||
|
"type": "siemens_s7",
|
||||||
|
"s7p_file": "C:\\Trabajo\\SIDEL\\05 - E5.007161 - Modifica O&U - SAE346\\InLavoro\\PLC\\Varios\\Inverter\\InverterGSD.s7p",
|
||||||
|
"observation_directory": "C:/Trabajo/SIDEL/05 - E5.007161 - Modifica O&U - SAE346",
|
||||||
|
"relative_path": "InLavoro\\PLC\\Varios\\Inverter",
|
||||||
|
"backup_path": "InLavoro\\PLC\\Varios\\Inverter",
|
||||||
|
"schedule_config": {
|
||||||
|
"schedule": "daily",
|
||||||
|
"schedule_time": "02:00",
|
||||||
|
"enabled": true,
|
||||||
|
"next_scheduled_backup": ""
|
||||||
|
},
|
||||||
|
"backup_history": {
|
||||||
|
"last_backup_date": "2025-09-02T10:52:22.919831+00:00",
|
||||||
|
"last_backup_file": "D:\\Backups\\AutoBackups\\InLavoro\\PLC\\Varios\\Inverter\\2025-09-02\\12-52-22_projects.zip",
|
||||||
|
"backup_count": 1,
|
||||||
|
"last_successful_backup": "2025-09-02T10:52:22.919831+00:00"
|
||||||
|
},
|
||||||
|
"hash_info": {
|
||||||
|
"last_s7p_hash": "",
|
||||||
|
"last_full_hash": "",
|
||||||
|
"last_s7p_timestamp": "",
|
||||||
|
"last_s7p_size": 0,
|
||||||
|
"last_scan_timestamp": "",
|
||||||
|
"file_count": 0,
|
||||||
|
"total_size_bytes": 0
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"current_status": "ready",
|
||||||
|
"last_error": null,
|
||||||
|
"retry_count": 0,
|
||||||
|
"next_retry": null,
|
||||||
|
"files_in_use": false,
|
||||||
|
"exclusivity_check_passed": true,
|
||||||
|
"last_status_update": "2025-09-02T10:52:22.919831+00:00"
|
||||||
|
},
|
||||||
|
"discovery_info": {
|
||||||
|
"discovered_date": "",
|
||||||
|
"discovery_method": "",
|
||||||
|
"auto_discovered": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "project_315c5dfb_20250902_125148",
|
||||||
|
"name": "Ssae03_2",
|
||||||
|
"path": "C:\\Trabajo\\SIDEL\\05 - E5.007161 - Modifica O&U - SAE346\\InLavoro\\PLC\\Varios\\daSAP\\PLC SAE346\\Ssae03_2",
|
||||||
|
"type": "siemens_s7",
|
||||||
|
"s7p_file": "C:\\Trabajo\\SIDEL\\05 - E5.007161 - Modifica O&U - SAE346\\InLavoro\\PLC\\Varios\\daSAP\\PLC SAE346\\Ssae03_2\\Ssae0346.s7p",
|
||||||
|
"observation_directory": "C:/Trabajo/SIDEL/05 - E5.007161 - Modifica O&U - SAE346",
|
||||||
|
"relative_path": "InLavoro\\PLC\\Varios\\daSAP\\PLC SAE346\\Ssae03_2",
|
||||||
|
"backup_path": "InLavoro\\PLC\\Varios\\daSAP\\PLC SAE346\\Ssae03_2",
|
||||||
|
"schedule_config": {
|
||||||
|
"schedule": "daily",
|
||||||
|
"schedule_time": "02:00",
|
||||||
|
"enabled": true,
|
||||||
|
"next_scheduled_backup": ""
|
||||||
|
},
|
||||||
|
"backup_history": {
|
||||||
|
"last_backup_date": "2025-09-02T10:52:23.703364+00:00",
|
||||||
|
"last_backup_file": "D:\\Backups\\AutoBackups\\InLavoro\\PLC\\Varios\\daSAP\\PLC SAE346\\Ssae03_2\\2025-09-02\\12-52-22_projects.zip",
|
||||||
|
"backup_count": 1,
|
||||||
|
"last_successful_backup": "2025-09-02T10:52:23.703364+00:00"
|
||||||
|
},
|
||||||
|
"hash_info": {
|
||||||
|
"last_s7p_hash": "",
|
||||||
|
"last_full_hash": "",
|
||||||
|
"last_s7p_timestamp": "",
|
||||||
|
"last_s7p_size": 0,
|
||||||
|
"last_scan_timestamp": "",
|
||||||
|
"file_count": 0,
|
||||||
|
"total_size_bytes": 0
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"current_status": "ready",
|
||||||
|
"last_error": null,
|
||||||
|
"retry_count": 0,
|
||||||
|
"next_retry": null,
|
||||||
|
"files_in_use": false,
|
||||||
|
"exclusivity_check_passed": true,
|
||||||
|
"last_status_update": "2025-09-02T10:52:23.703364+00:00"
|
||||||
|
},
|
||||||
|
"discovery_info": {
|
||||||
|
"discovered_date": "",
|
||||||
|
"discovery_method": "",
|
||||||
|
"auto_discovered": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "project_8d1cfcd7_20250902_125148",
|
||||||
|
"name": "Rve116_PEPSI_OCTOBER L7",
|
||||||
|
"path": "C:\\Trabajo\\SIDEL\\05 - E5.007161 - Modifica O&U - SAE346\\InLavoro\\PLC\\Varios\\Filler\\Rve116_PEPSI_OCTOBER L7",
|
||||||
|
"type": "siemens_s7",
|
||||||
|
"s7p_file": "C:\\Trabajo\\SIDEL\\05 - E5.007161 - Modifica O&U - SAE346\\InLavoro\\PLC\\Varios\\Filler\\Rve116_PEPSI_OCTOBER L7\\Rve116.s7p",
|
||||||
|
"observation_directory": "C:/Trabajo/SIDEL/05 - E5.007161 - Modifica O&U - SAE346",
|
||||||
|
"relative_path": "InLavoro\\PLC\\Varios\\Filler\\Rve116_PEPSI_OCTOBER L7",
|
||||||
|
"backup_path": "InLavoro\\PLC\\Varios\\Filler\\Rve116_PEPSI_OCTOBER L7",
|
||||||
|
"schedule_config": {
|
||||||
|
"schedule": "daily",
|
||||||
|
"schedule_time": "02:00",
|
||||||
|
"enabled": true,
|
||||||
|
"next_scheduled_backup": ""
|
||||||
|
},
|
||||||
|
"backup_history": {
|
||||||
|
"last_backup_date": "2025-09-02T10:52:23.563211+00:00",
|
||||||
|
"last_backup_file": "D:\\Backups\\AutoBackups\\InLavoro\\PLC\\Varios\\Filler\\Rve116_PEPSI_OCTOBER L7\\2025-09-02\\12-52-22_projects.zip",
|
||||||
|
"backup_count": 1,
|
||||||
|
"last_successful_backup": "2025-09-02T10:52:23.563211+00:00"
|
||||||
|
},
|
||||||
|
"hash_info": {
|
||||||
|
"last_s7p_hash": "",
|
||||||
|
"last_full_hash": "",
|
||||||
|
"last_s7p_timestamp": "",
|
||||||
|
"last_s7p_size": 0,
|
||||||
|
"last_scan_timestamp": "",
|
||||||
|
"file_count": 0,
|
||||||
|
"total_size_bytes": 0
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"current_status": "ready",
|
||||||
|
"last_error": null,
|
||||||
|
"retry_count": 0,
|
||||||
|
"next_retry": null,
|
||||||
|
"files_in_use": false,
|
||||||
|
"exclusivity_check_passed": true,
|
||||||
|
"last_status_update": "2025-09-02T10:52:23.563211+00:00"
|
||||||
|
},
|
||||||
|
"discovery_info": {
|
||||||
|
"discovered_date": "",
|
||||||
|
"discovery_method": "",
|
||||||
|
"auto_discovered": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "project_42543170_20250902_125148",
|
||||||
|
"name": "TestCOM",
|
||||||
|
"path": "C:\\Trabajo\\SIDEL\\05 - E5.007161 - Modifica O&U - SAE346\\InLavoro\\PLC\\Varios\\Filler\\TestCOM",
|
||||||
|
"type": "siemens_s7",
|
||||||
|
"s7p_file": "C:\\Trabajo\\SIDEL\\05 - E5.007161 - Modifica O&U - SAE346\\InLavoro\\PLC\\Varios\\Filler\\TestCOM\\TestCOM.s7p",
|
||||||
|
"observation_directory": "C:/Trabajo/SIDEL/05 - E5.007161 - Modifica O&U - SAE346",
|
||||||
|
"relative_path": "InLavoro\\PLC\\Varios\\Filler\\TestCOM",
|
||||||
|
"backup_path": "InLavoro\\PLC\\Varios\\Filler\\TestCOM",
|
||||||
|
"schedule_config": {
|
||||||
|
"schedule": "daily",
|
||||||
|
"schedule_time": "02:00",
|
||||||
|
"enabled": true,
|
||||||
|
"next_scheduled_backup": ""
|
||||||
|
},
|
||||||
|
"backup_history": {
|
||||||
|
"last_backup_date": "2025-09-02T10:52:23.522388+00:00",
|
||||||
|
"last_backup_file": "D:\\Backups\\AutoBackups\\InLavoro\\PLC\\Varios\\Filler\\TestCOM\\2025-09-02\\12-52-23_projects.zip",
|
||||||
|
"backup_count": 1,
|
||||||
|
"last_successful_backup": "2025-09-02T10:52:23.522388+00:00"
|
||||||
|
},
|
||||||
|
"hash_info": {
|
||||||
|
"last_s7p_hash": "",
|
||||||
|
"last_full_hash": "",
|
||||||
|
"last_s7p_timestamp": "",
|
||||||
|
"last_s7p_size": 0,
|
||||||
|
"last_scan_timestamp": "",
|
||||||
|
"file_count": 0,
|
||||||
|
"total_size_bytes": 0
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"current_status": "ready",
|
||||||
|
"last_error": null,
|
||||||
|
"retry_count": 0,
|
||||||
|
"next_retry": null,
|
||||||
|
"files_in_use": false,
|
||||||
|
"exclusivity_check_passed": true,
|
||||||
|
"last_status_update": "2025-09-02T10:52:23.522388+00:00"
|
||||||
|
},
|
||||||
|
"discovery_info": {
|
||||||
|
"discovered_date": "",
|
||||||
|
"discovery_method": "",
|
||||||
|
"auto_discovered": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "project_86a0b02d_20250902_125148",
|
||||||
|
"name": "SAE446Mi",
|
||||||
|
"path": "C:\\Trabajo\\SIDEL\\05 - E5.007161 - Modifica O&U - SAE346\\InLavoro\\PLC\\Varios\\Last Backup\\SAE446Mi",
|
||||||
|
"type": "siemens_s7",
|
||||||
|
"s7p_file": "C:\\Trabajo\\SIDEL\\05 - E5.007161 - Modifica O&U - SAE346\\InLavoro\\PLC\\Varios\\Last Backup\\SAE446Mi\\SAE446Mi.s7p",
|
||||||
|
"observation_directory": "C:/Trabajo/SIDEL/05 - E5.007161 - Modifica O&U - SAE346",
|
||||||
|
"relative_path": "InLavoro\\PLC\\Varios\\Last Backup\\SAE446Mi",
|
||||||
|
"backup_path": "InLavoro\\PLC\\Varios\\Last Backup\\SAE446Mi",
|
||||||
|
"schedule_config": {
|
||||||
|
"schedule": "daily",
|
||||||
|
"schedule_time": "02:00",
|
||||||
|
"enabled": true,
|
||||||
|
"next_scheduled_backup": ""
|
||||||
|
},
|
||||||
|
"backup_history": {
|
||||||
|
"last_backup_date": "2025-09-02T10:52:24.414156+00:00",
|
||||||
|
"last_backup_file": "D:\\Backups\\AutoBackups\\InLavoro\\PLC\\Varios\\Last Backup\\SAE446Mi\\2025-09-02\\12-52-23_projects.zip",
|
||||||
|
"backup_count": 1,
|
||||||
|
"last_successful_backup": "2025-09-02T10:52:24.414156+00:00"
|
||||||
|
},
|
||||||
|
"hash_info": {
|
||||||
|
"last_s7p_hash": "",
|
||||||
|
"last_full_hash": "",
|
||||||
|
"last_s7p_timestamp": "",
|
||||||
|
"last_s7p_size": 0,
|
||||||
|
"last_scan_timestamp": "",
|
||||||
|
"file_count": 0,
|
||||||
|
"total_size_bytes": 0
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"current_status": "ready",
|
||||||
|
"last_error": null,
|
||||||
|
"retry_count": 0,
|
||||||
|
"next_retry": null,
|
||||||
|
"files_in_use": false,
|
||||||
|
"exclusivity_check_passed": true,
|
||||||
|
"last_status_update": "2025-09-02T10:52:24.414156+00:00"
|
||||||
|
},
|
||||||
|
"discovery_info": {
|
||||||
|
"discovered_date": "",
|
||||||
|
"discovery_method": "",
|
||||||
|
"auto_discovered": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "project_06372986_20250902_125148",
|
||||||
|
"name": "SAE446Mi",
|
||||||
|
"path": "C:\\Trabajo\\SIDEL\\05 - E5.007161 - Modifica O&U - SAE346\\Reporte\\LastWork\\PLC_Sae446_Mixer_20241217\\SAE446Mi",
|
||||||
|
"type": "siemens_s7",
|
||||||
|
"s7p_file": "C:\\Trabajo\\SIDEL\\05 - E5.007161 - Modifica O&U - SAE346\\Reporte\\LastWork\\PLC_Sae446_Mixer_20241217\\SAE446Mi\\SAE446Mi.s7p",
|
||||||
|
"observation_directory": "C:/Trabajo/SIDEL/05 - E5.007161 - Modifica O&U - SAE346",
|
||||||
|
"relative_path": "Reporte\\LastWork\\PLC_Sae446_Mixer_20241217\\SAE446Mi",
|
||||||
|
"backup_path": "Reporte\\LastWork\\PLC_Sae446_Mixer_20241217\\SAE446Mi",
|
||||||
|
"schedule_config": {
|
||||||
|
"schedule": "daily",
|
||||||
|
"schedule_time": "02:00",
|
||||||
|
"enabled": true,
|
||||||
|
"next_scheduled_backup": ""
|
||||||
|
},
|
||||||
|
"backup_history": {
|
||||||
|
"last_backup_date": "2025-09-02T10:52:24.343664+00:00",
|
||||||
|
"last_backup_file": "D:\\Backups\\AutoBackups\\Reporte\\LastWork\\PLC_Sae446_Mixer_20241217\\SAE446Mi\\2025-09-02\\12-52-23_projects.zip",
|
||||||
|
"backup_count": 1,
|
||||||
|
"last_successful_backup": "2025-09-02T10:52:24.343664+00:00"
|
||||||
|
},
|
||||||
|
"hash_info": {
|
||||||
|
"last_s7p_hash": "",
|
||||||
|
"last_full_hash": "",
|
||||||
|
"last_s7p_timestamp": "",
|
||||||
|
"last_s7p_size": 0,
|
||||||
|
"last_scan_timestamp": "",
|
||||||
|
"file_count": 0,
|
||||||
|
"total_size_bytes": 0
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"current_status": "ready",
|
||||||
|
"last_error": null,
|
||||||
|
"retry_count": 0,
|
||||||
|
"next_retry": null,
|
||||||
|
"files_in_use": false,
|
||||||
|
"exclusivity_check_passed": true,
|
||||||
|
"last_status_update": "2025-09-02T10:52:24.343664+00:00"
|
||||||
},
|
},
|
||||||
"discovery_info": {
|
"discovery_info": {
|
||||||
"discovered_date": "",
|
"discovered_date": "",
|
||||||
|
|
|
@ -14,6 +14,10 @@ psutil==5.9.5 # System utilities (disk space monitoring)
|
||||||
filelock==3.12.4 # File locking for concurrent access
|
filelock==3.12.4 # File locking for concurrent access
|
||||||
# tkinter is included with Python standard library
|
# tkinter is included with Python standard library
|
||||||
|
|
||||||
|
# System Tray Integration
|
||||||
|
pystray==0.19.5 # System tray icon support for Windows
|
||||||
|
Pillow==11.3.0 # Image processing for icons
|
||||||
|
|
||||||
# Web Interface
|
# Web Interface
|
||||||
Jinja2==3.1.2 # Template engine for Flask
|
Jinja2==3.1.2 # Template engine for Flask
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
@echo off
|
||||||
|
echo AutoBackups - Setup Script
|
||||||
|
echo ============================
|
||||||
|
echo.
|
||||||
|
|
||||||
|
echo [1/4] Activating conda environment...
|
||||||
|
call conda activate autobackups
|
||||||
|
if errorlevel 1 (
|
||||||
|
echo Error: Could not activate conda environment 'autobackups'
|
||||||
|
echo.
|
||||||
|
echo Creating new environment...
|
||||||
|
call conda create --name autobackups python=3.12 -y
|
||||||
|
if errorlevel 1 (
|
||||||
|
echo Error: Could not create conda environment
|
||||||
|
pause
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
call conda activate autobackups
|
||||||
|
)
|
||||||
|
|
||||||
|
echo [2/4] Installing Python dependencies...
|
||||||
|
pip install -r requirements.txt
|
||||||
|
if errorlevel 1 (
|
||||||
|
echo Error: Could not install dependencies
|
||||||
|
pause
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
|
||||||
|
echo [3/4] Checking favicon file...
|
||||||
|
if not exist "static\icons\favicon.png" (
|
||||||
|
echo Warning: favicon.png not found in static\icons\
|
||||||
|
echo System tray icon may not display correctly
|
||||||
|
)
|
||||||
|
|
||||||
|
echo [4/4] Testing system tray functionality...
|
||||||
|
python -c "import pystray, PIL; print('✓ System tray dependencies OK')"
|
||||||
|
if errorlevel 1 (
|
||||||
|
echo Error: System tray dependencies not working
|
||||||
|
pause
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ✓ Setup completed successfully!
|
||||||
|
echo.
|
||||||
|
echo Usage:
|
||||||
|
echo Regular mode: python src\app.py
|
||||||
|
echo System tray mode: python launch_tray.py
|
||||||
|
echo Or use: launch_with_tray.bat
|
||||||
|
echo.
|
||||||
|
pause
|
47
src/app.py
47
src/app.py
|
@ -22,6 +22,7 @@ sys.path.insert(0, str(src_dir))
|
||||||
from models.config_model import Config
|
from models.config_model import Config
|
||||||
from models.project_model import ProjectManager
|
from models.project_model import ProjectManager
|
||||||
from utils.file_utils import DiskSpaceChecker
|
from utils.file_utils import DiskSpaceChecker
|
||||||
|
from utils.system_tray import SystemTrayManager
|
||||||
from services.basic_backup_service import BasicBackupService
|
from services.basic_backup_service import BasicBackupService
|
||||||
from routes import register_api_routes, register_web_routes
|
from routes import register_api_routes, register_web_routes
|
||||||
|
|
||||||
|
@ -40,6 +41,7 @@ class AutoBackupsFlaskApp:
|
||||||
self.backup_service = None
|
self.backup_service = None
|
||||||
self.scheduler = None
|
self.scheduler = None
|
||||||
self.logger = None
|
self.logger = None
|
||||||
|
self.system_tray = None
|
||||||
|
|
||||||
# Inicializar aplicación
|
# Inicializar aplicación
|
||||||
self._setup_logging()
|
self._setup_logging()
|
||||||
|
@ -47,6 +49,7 @@ class AutoBackupsFlaskApp:
|
||||||
self._initialize_services()
|
self._initialize_services()
|
||||||
self._setup_scheduler()
|
self._setup_scheduler()
|
||||||
self._register_routes()
|
self._register_routes()
|
||||||
|
self._setup_system_tray()
|
||||||
|
|
||||||
def _setup_logging(self):
|
def _setup_logging(self):
|
||||||
"""Configurar sistema de logging"""
|
"""Configurar sistema de logging"""
|
||||||
|
@ -145,6 +148,31 @@ class AutoBackupsFlaskApp:
|
||||||
register_api_routes(app, self)
|
register_api_routes(app, self)
|
||||||
self.logger.info("Rutas Flask registradas desde módulos")
|
self.logger.info("Rutas Flask registradas desde módulos")
|
||||||
|
|
||||||
|
def _setup_system_tray(self):
|
||||||
|
"""Inicializa el icono de system tray"""
|
||||||
|
try:
|
||||||
|
host = self.config.web_interface.get("host", "127.0.0.1")
|
||||||
|
port = self.config.web_interface.get("port", 5000)
|
||||||
|
|
||||||
|
self.system_tray = SystemTrayManager(self, host, port)
|
||||||
|
self.logger.info("System tray configurado correctamente")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"Error configurando system tray: {e}")
|
||||||
|
self.system_tray = None
|
||||||
|
|
||||||
|
def start_system_tray(self):
|
||||||
|
"""Inicia el icono de system tray"""
|
||||||
|
if self.system_tray:
|
||||||
|
success = self.system_tray.start_tray()
|
||||||
|
if success:
|
||||||
|
self.logger.info("System tray iniciado correctamente")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
self.logger.warning("No se pudo iniciar el system tray")
|
||||||
|
return False
|
||||||
|
return False
|
||||||
|
|
||||||
def scheduled_project_scan(self):
|
def scheduled_project_scan(self):
|
||||||
"""Escaneo programado de proyectos"""
|
"""Escaneo programado de proyectos"""
|
||||||
try:
|
try:
|
||||||
|
@ -250,19 +278,30 @@ def main():
|
||||||
debug = autobackups_app.config.web_interface.get("debug", False)
|
debug = autobackups_app.config.web_interface.get("debug", False)
|
||||||
|
|
||||||
print(f"\nServidor web iniciando en http://{host}:{port}")
|
print(f"\nServidor web iniciando en http://{host}:{port}")
|
||||||
|
|
||||||
|
# Iniciar system tray
|
||||||
|
autobackups_app.start_system_tray()
|
||||||
|
|
||||||
print("Presiona Ctrl+C para detener el servidor")
|
print("Presiona Ctrl+C para detener el servidor")
|
||||||
|
print("Aplicación disponible en el system tray")
|
||||||
|
|
||||||
# Iniciar servidor Flask
|
# Iniciar servidor Flask
|
||||||
app.run(host=host, port=port, debug=debug, use_reloader=False)
|
app.run(host=host, port=port, debug=debug, use_reloader=False)
|
||||||
|
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
print("\nAplicación interrumpida por el usuario")
|
print("\nAplicación interrumpida por el usuario")
|
||||||
if autobackups_app and autobackups_app.scheduler:
|
if autobackups_app:
|
||||||
autobackups_app.scheduler.shutdown()
|
if autobackups_app.scheduler:
|
||||||
|
autobackups_app.scheduler.shutdown()
|
||||||
|
if autobackups_app.system_tray:
|
||||||
|
autobackups_app.system_tray.stop_tray()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error inesperado: {e}")
|
print(f"Error inesperado: {e}")
|
||||||
if autobackups_app and autobackups_app.scheduler:
|
if autobackups_app:
|
||||||
autobackups_app.scheduler.shutdown()
|
if autobackups_app.scheduler:
|
||||||
|
autobackups_app.scheduler.shutdown()
|
||||||
|
if autobackups_app.system_tray:
|
||||||
|
autobackups_app.system_tray.stop_tray()
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,154 @@
|
||||||
|
"""
|
||||||
|
System Tray functionality for AutoBackups application
|
||||||
|
Provides system tray icon with options to open browser and close application
|
||||||
|
"""
|
||||||
|
|
||||||
|
import threading
|
||||||
|
import webbrowser
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
import pystray
|
||||||
|
from PIL import Image
|
||||||
|
import logging
|
||||||
|
|
||||||
|
|
||||||
|
class SystemTrayManager:
|
||||||
|
"""Manages system tray icon and menu for AutoBackups"""
|
||||||
|
|
||||||
|
def __init__(self, app_instance, host="127.0.0.1", port=5000):
|
||||||
|
"""
|
||||||
|
Initialize system tray manager
|
||||||
|
|
||||||
|
Args:
|
||||||
|
app_instance: The main AutoBackups Flask application instance
|
||||||
|
host: Web server host (default: 127.0.0.1)
|
||||||
|
port: Web server port (default: 5000)
|
||||||
|
"""
|
||||||
|
self.app_instance = app_instance
|
||||||
|
self.host = host
|
||||||
|
self.port = port
|
||||||
|
self.icon = None
|
||||||
|
self.logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# Get icon path
|
||||||
|
self.icon_path = self._get_icon_path()
|
||||||
|
|
||||||
|
def _get_icon_path(self):
|
||||||
|
"""Get the path to the favicon.png icon"""
|
||||||
|
current_dir = Path(__file__).parent.parent.parent
|
||||||
|
icon_path = current_dir / "static" / "icons" / "favicon.png"
|
||||||
|
|
||||||
|
if not icon_path.exists():
|
||||||
|
self.logger.warning(f"Icon file not found at {icon_path}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
return str(icon_path)
|
||||||
|
|
||||||
|
def _load_icon_image(self):
|
||||||
|
"""Load the icon image from file"""
|
||||||
|
if not self.icon_path:
|
||||||
|
# Create a simple default icon if file not found
|
||||||
|
return Image.new("RGB", (64, 64), color="blue")
|
||||||
|
|
||||||
|
try:
|
||||||
|
return Image.open(self.icon_path)
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"Error loading icon image: {e}")
|
||||||
|
# Return a simple default icon
|
||||||
|
return Image.new("RGB", (64, 64), color="blue")
|
||||||
|
|
||||||
|
def _open_browser(self, icon, item):
|
||||||
|
"""Open web browser to the application URL"""
|
||||||
|
url = f"http://{self.host}:{self.port}"
|
||||||
|
try:
|
||||||
|
webbrowser.open(url)
|
||||||
|
self.logger.info(f"Opened browser to {url}")
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"Error opening browser: {e}")
|
||||||
|
|
||||||
|
def _quit_application(self, icon, item):
|
||||||
|
"""Quit the application"""
|
||||||
|
try:
|
||||||
|
self.logger.info("Shutting down application from system tray")
|
||||||
|
|
||||||
|
# Stop the scheduler if it exists
|
||||||
|
if (
|
||||||
|
self.app_instance
|
||||||
|
and hasattr(self.app_instance, "scheduler")
|
||||||
|
and self.app_instance.scheduler
|
||||||
|
):
|
||||||
|
self.app_instance.scheduler.shutdown(wait=False)
|
||||||
|
|
||||||
|
# Stop the system tray icon
|
||||||
|
if self.icon:
|
||||||
|
self.icon.stop()
|
||||||
|
|
||||||
|
# Exit the application
|
||||||
|
os._exit(0)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"Error during application shutdown: {e}")
|
||||||
|
os._exit(1)
|
||||||
|
|
||||||
|
def _show_status(self, icon, item):
|
||||||
|
"""Show application status (placeholder for future implementation)"""
|
||||||
|
# This could show a status window or notification in the future
|
||||||
|
self.logger.info("Status requested from system tray")
|
||||||
|
|
||||||
|
def create_tray_icon(self):
|
||||||
|
"""Create and configure the system tray icon"""
|
||||||
|
try:
|
||||||
|
# Load the icon image
|
||||||
|
image = self._load_icon_image()
|
||||||
|
|
||||||
|
# Create menu items
|
||||||
|
menu = pystray.Menu(
|
||||||
|
pystray.MenuItem(
|
||||||
|
"Open AutoBackups",
|
||||||
|
self._open_browser,
|
||||||
|
# This will be the action when double-clicking the icon
|
||||||
|
default=True,
|
||||||
|
),
|
||||||
|
pystray.MenuItem("Status", self._show_status),
|
||||||
|
pystray.Menu.SEPARATOR,
|
||||||
|
pystray.MenuItem("Exit", self._quit_application),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create the icon
|
||||||
|
self.icon = pystray.Icon(
|
||||||
|
"AutoBackups", image, "AutoBackups - Automatic Backup System", menu
|
||||||
|
)
|
||||||
|
|
||||||
|
self.logger.info("System tray icon created successfully")
|
||||||
|
return self.icon
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"Error creating system tray icon: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def start_tray(self):
|
||||||
|
"""Start the system tray icon in a separate thread"""
|
||||||
|
if not self.icon:
|
||||||
|
self.icon = self.create_tray_icon()
|
||||||
|
|
||||||
|
if self.icon:
|
||||||
|
try:
|
||||||
|
# Run the tray icon in a separate thread
|
||||||
|
tray_thread = threading.Thread(target=self.icon.run, daemon=True)
|
||||||
|
tray_thread.start()
|
||||||
|
self.logger.info("System tray started successfully")
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"Error starting system tray: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def stop_tray(self):
|
||||||
|
"""Stop the system tray icon"""
|
||||||
|
if self.icon:
|
||||||
|
try:
|
||||||
|
self.icon.stop()
|
||||||
|
self.logger.info("System tray stopped")
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"Error stopping system tray: {e}")
|
|
@ -5,6 +5,9 @@
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>{% block title %}AutoBackups{% endblock %}</title>
|
<title>{% block title %}AutoBackups{% endblock %}</title>
|
||||||
|
|
||||||
|
<!-- Favicon -->
|
||||||
|
<link rel="icon" type="image/png" href="{{ url_for('static', filename='icons/favicon.png') }}">
|
||||||
|
|
||||||
<!-- Bootstrap CSS -->
|
<!-- Bootstrap CSS -->
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
<!-- Bootstrap Icons -->
|
<!-- Bootstrap Icons -->
|
||||||
|
|
Loading…
Reference in New Issue