- Changed /api/auth/login to /api/collections/users/auth-with-password - Changed /api/systems to /api/collections/systems/records - Added proper error messages for connection issues
373 lines
14 KiB
Markdown
373 lines
14 KiB
Markdown
# Beszel Tool for OpenWebUI
|
|
|
|
## Description
|
|
|
|
Permet à Effi d'accéder aux métriques système de FolzoCorp, EffiServer et NAS Synology via Beszel.
|
|
|
|
## Installation
|
|
|
|
1. **Admin Panel** → **Tools** → **Create Tool**
|
|
2. **Name**: `Beszel System Monitor`
|
|
3. **Description**: `Monitor system resources via Beszel. Usage: get_all_stats(), get_system_stats("folzocorp"), check_alerts("folzocorp"), get_gpu_status("folzocorp"), can_run_comfyui()`
|
|
4. **Coller le code ci-dessous**
|
|
5. **Save and Activate**
|
|
6. **Configurer les variables** via la roue crantée (Vannes)
|
|
|
|
## Tool Code
|
|
|
|
```python
|
|
import requests
|
|
from pydantic import BaseModel, Field
|
|
|
|
|
|
class Tools:
|
|
class Valves(BaseModel):
|
|
beszel_url: str = Field(
|
|
default="http://192.168.1.25:8090",
|
|
description="Beszel Hub URL",
|
|
)
|
|
beszel_user: str = Field(
|
|
default="admin",
|
|
description="Beszel username (email)",
|
|
)
|
|
beszel_password: str = Field(
|
|
default="",
|
|
description="Beszel password",
|
|
)
|
|
|
|
def __init__(self):
|
|
self.valves = self.Valves()
|
|
|
|
def _login(self):
|
|
try:
|
|
response = requests.post(
|
|
f"{self.valves.beszel_url}/api/collections/users/auth-with-password",
|
|
json={
|
|
"identity": self.valves.beszel_user,
|
|
"password": self.valves.beszel_password
|
|
},
|
|
timeout=10
|
|
)
|
|
if response.status_code == 200:
|
|
data = response.json()
|
|
return data.get("token")
|
|
return None
|
|
except Exception:
|
|
return None
|
|
|
|
def _get_systems(self):
|
|
token = self._login()
|
|
if not token:
|
|
return None
|
|
try:
|
|
headers = {
|
|
"Authorization": f"{token}",
|
|
"Content-Type": "application/json"
|
|
}
|
|
response = requests.get(
|
|
f"{self.valves.beszel_url}/api/collections/systems/records",
|
|
headers=headers,
|
|
timeout=10
|
|
)
|
|
if response.status_code == 200:
|
|
data = response.json()
|
|
return data.get("items", [])
|
|
return None
|
|
except Exception:
|
|
return None
|
|
|
|
def _get_system_stats(self, system_id):
|
|
token = self._login()
|
|
if not token:
|
|
return None
|
|
try:
|
|
headers = {
|
|
"Authorization": f"{token}",
|
|
"Content-Type": "application/json"
|
|
}
|
|
response = requests.get(
|
|
f"{self.valves.beszel_url}/api/collections/systems/records/{system_id}",
|
|
headers=headers,
|
|
timeout=10
|
|
)
|
|
if response.status_code == 200:
|
|
return response.json()
|
|
return None
|
|
except Exception:
|
|
return None
|
|
|
|
def _format_stats_text(self, stats, system_name="System"):
|
|
lines = []
|
|
lines.append(f"=== {system_name} ===")
|
|
lines.append(f"CPU: {stats.get('cpu', 0):.1f}%")
|
|
lines.append(f"Mémoire: {stats.get('memory', 0):.1f}%")
|
|
|
|
if 'swap' in stats:
|
|
lines.append(f"Swap: {stats.get('swap', 0):.1f}%")
|
|
|
|
if 'disk' in stats:
|
|
disk_data = stats.get('disk', {})
|
|
if isinstance(disk_data, dict):
|
|
for disk, info in disk_data.items():
|
|
lines.append(f"Disque {disk}: {info.get('used_percent', 0):.1f}%")
|
|
|
|
if 'load' in stats:
|
|
load = stats.get('load', [])
|
|
if len(load) >= 3:
|
|
lines.append(f"Load: {load[0]:.2f} {load[1]:.2f} {load[2]:.2f}")
|
|
|
|
if 'gpu' in stats and stats['gpu']:
|
|
lines.append("")
|
|
lines.append("GPU:")
|
|
gpu_data = stats.get('gpu', {})
|
|
if isinstance(gpu_data, dict):
|
|
for gpu_name, gpu_info in gpu_data.items():
|
|
if isinstance(gpu_info, dict):
|
|
vram_used = gpu_info.get('memory_used', 0) / (1024**3)
|
|
vram_total = gpu_info.get('memory_total', 0) / (1024**3)
|
|
vram_pct = (gpu_info.get('memory_used', 0) / max(gpu_info.get('memory_total', 1), 1)) * 100
|
|
lines.append(f" {gpu_name}:")
|
|
lines.append(f" VRAM: {vram_used:.2f}GB / {vram_total:.2f}GB ({vram_pct:.1f}%)")
|
|
lines.append(f" Utilisation GPU: {gpu_info.get('utilization_gpu', 0)}%")
|
|
if 'temperature' in gpu_info:
|
|
lines.append(f" Température: {gpu_info.get('temperature', 0)}°C")
|
|
if 'power_draw' in gpu_info:
|
|
lines.append(f" Consommation: {gpu_info.get('power_draw', 0)}W")
|
|
|
|
if 'containers' in stats and stats['containers']:
|
|
lines.append("")
|
|
lines.append("Containers Docker:")
|
|
for container in stats['containers']:
|
|
name = container.get('name', 'unknown')
|
|
cpu = container.get('cpu_percent', 0)
|
|
mem = container.get('memory_percent', 0)
|
|
lines.append(f" {name}: CPU {cpu:.1f}% MEM {mem:.1f}%")
|
|
|
|
return "\n".join(lines)
|
|
|
|
def _check_thresholds(self, stats, thresholds=None):
|
|
if thresholds is None:
|
|
thresholds = {'cpu': 80, 'memory': 90, 'vram': 90}
|
|
|
|
alerts = []
|
|
|
|
cpu = stats.get('cpu', 0)
|
|
if cpu > thresholds['cpu']:
|
|
alerts.append(f"⚠️ CPU elevated: {cpu:.1f}%")
|
|
|
|
memory = stats.get('memory', 0)
|
|
if memory > thresholds['memory']:
|
|
alerts.append(f"⚠️ Memory elevated: {memory:.1f}%")
|
|
|
|
if 'gpu' in stats and stats['gpu']:
|
|
gpu_data = stats.get('gpu', {})
|
|
if isinstance(gpu_data, dict):
|
|
for gpu_name, gpu_info in gpu_data.items():
|
|
if isinstance(gpu_info, dict):
|
|
vram_pct = (gpu_info.get('memory_used', 0) / max(gpu_info.get('memory_total', 1), 1)) * 100
|
|
if vram_pct > thresholds['vram']:
|
|
alerts.append(f"⚠️ VRAM {gpu_name} elevated: {vram_pct:.1f}%")
|
|
|
|
util = gpu_info.get('utilization_gpu', 0)
|
|
if util > thresholds['vram']:
|
|
alerts.append(f"⚠️ GPU {gpu_name} utilization elevated: {util}%")
|
|
|
|
return alerts
|
|
|
|
def get_all_stats(self):
|
|
systems = self._get_systems()
|
|
|
|
if not systems:
|
|
return "[ERROR] Cannot connect to Beszel. Check URL, username and password."
|
|
|
|
results = []
|
|
for sys_info in systems:
|
|
sys_id = sys_info.get('id')
|
|
sys_name = sys_info.get('name', sys_id)
|
|
stats = self._get_system_stats(sys_id)
|
|
|
|
if stats:
|
|
results.append(self._format_stats_text(stats, sys_name))
|
|
else:
|
|
results.append(f"=== {sys_name} ===\n[ERROR] Cannot get stats")
|
|
|
|
return "\n\n".join(results)
|
|
|
|
def get_system_stats(self, system_name: str):
|
|
systems = self._get_systems()
|
|
|
|
if not systems:
|
|
return "[ERROR] Cannot connect to Beszel. Check URL, username and password."
|
|
|
|
system_map = {
|
|
'folzocorp': 'folzocorp',
|
|
'effiserver': 'effiserver',
|
|
'synology': 'synology-nas',
|
|
'nas': 'synology-nas'
|
|
}
|
|
|
|
target_name = system_map.get(system_name.lower(), system_name.lower())
|
|
|
|
for sys_info in systems:
|
|
sys_id = sys_info.get('id')
|
|
sys_nm = sys_info.get('name', '')
|
|
if sys_nm.lower() == target_name or sys_id == target_name:
|
|
stats = self._get_system_stats(sys_id)
|
|
if stats:
|
|
return self._format_stats_text(stats, sys_nm or system_name)
|
|
return f"[ERROR] Cannot get stats for {system_name}"
|
|
|
|
available = [s.get('name', s.get('id')) for s in systems]
|
|
return f"[ERROR] System '{system_name}' not found. Available: {', '.join(available)}"
|
|
|
|
def check_alerts(self, system_name: str = "all"):
|
|
systems = self._get_systems()
|
|
|
|
if not systems:
|
|
return "[ERROR] Cannot connect to Beszel"
|
|
|
|
alerts = []
|
|
|
|
for sys_info in systems:
|
|
sys_id = sys_info.get('id')
|
|
sys_name = sys_info.get('name', sys_id)
|
|
|
|
if system_name.lower() != 'all' and sys_name.lower() != system_name.lower():
|
|
continue
|
|
|
|
stats = self._get_system_stats(sys_id)
|
|
if stats:
|
|
system_alerts = self._check_thresholds(stats)
|
|
if system_alerts:
|
|
alerts.append(f"=== {sys_name} ===")
|
|
alerts.extend(system_alerts)
|
|
|
|
if not alerts:
|
|
return "✅ All systems nominal"
|
|
|
|
return "\n".join(alerts)
|
|
|
|
def get_gpu_status(self, system_name: str = "folzocorp"):
|
|
systems = self._get_systems()
|
|
|
|
if not systems:
|
|
return "[ERROR] Cannot connect to Beszel"
|
|
|
|
target_name = system_map.get(system_name.lower(), system_name.lower())
|
|
|
|
for sys_info in systems:
|
|
sys_id = sys_info.get('id')
|
|
sys_name = sys_info.get('name', '')
|
|
if sys_name.lower() == target_name or sys_id == target_name:
|
|
stats = self._get_system_stats(sys_id)
|
|
if stats:
|
|
if 'gpu' not in stats or not stats['gpu']:
|
|
return f"[INFO] No GPU data for {system_name}"
|
|
|
|
lines = [f"=== GPU Status: {system_name} ==="]
|
|
gpu_data = stats.get('gpu', {})
|
|
if isinstance(gpu_data, dict):
|
|
for gpu_name, gpu_info in gpu_data.items():
|
|
if isinstance(gpu_info, dict):
|
|
vram_used = gpu_info.get('memory_used', 0) / (1024**3)
|
|
vram_total = gpu_info.get('memory_total', 0) / (1024**3)
|
|
vram_pct = (gpu_info.get('memory_used', 0) / max(gpu_info.get('memory_total', 1), 1)) * 100
|
|
|
|
lines.append(f"\n{gpu_name}:")
|
|
lines.append(f" VRAM: {vram_used:.2f}GB / {vram_total:.2f}GB ({vram_pct:.1f}%)")
|
|
lines.append(f" GPU Utilization: {gpu_info.get('utilization_gpu', 0)}%")
|
|
|
|
if 'temperature' in gpu_info:
|
|
lines.append(f" Temperature: {gpu_info.get('temperature', 0)}°C")
|
|
if 'power_draw' in gpu_info:
|
|
lines.append(f" Power: {gpu_info.get('power_draw', 0)}W")
|
|
|
|
return "\n".join(lines)
|
|
return f"[ERROR] Cannot get stats for {system_name}"
|
|
|
|
available = [s.get('name', s.get('id')) for s in systems]
|
|
return f"[ERROR] System '{system_name}' not found. Available: {', '.join(available)}"
|
|
|
|
def can_run_comfyui(self):
|
|
stats = self._get_system_stats("folzocorp")
|
|
if not stats:
|
|
return "[ERROR] Cannot get FolzoCorp stats"
|
|
|
|
memory_pct = stats.get('memory', 0)
|
|
|
|
vram_info = None
|
|
vram_pct = 0
|
|
if 'gpu' in stats and stats['gpu']:
|
|
gpu_data = stats.get('gpu', {})
|
|
if isinstance(gpu_data, dict):
|
|
for gpu_name, gpu_info in gpu_data.items():
|
|
if isinstance(gpu_info, dict):
|
|
vram_total = gpu_info.get('memory_total', 0) / (1024**3)
|
|
vram_free = gpu_info.get('memory_total', 0) - gpu_info.get('memory_used', 0)
|
|
vram_free_gb = vram_free / (1024**3)
|
|
vram_pct = (gpu_info.get('memory_used', 0) / max(gpu_info.get('memory_total', 1), 1)) * 100
|
|
vram_info = f"{gpu_name}: {vram_free_gb:.1f}GB free ({vram_pct:.1f}% used)"
|
|
|
|
can_run = memory_pct < 85 and vram_pct < 85
|
|
|
|
lines = ["=== ComfyUI Readiness Check ==="]
|
|
lines.append(f"RAM: {100-memory_pct:.1f}% available ({memory_pct:.1f}% used)")
|
|
if vram_info:
|
|
lines.append(f"VRAM: {vram_info}")
|
|
lines.append(f"\nConclusion: {'✅ Can run ComfyUI' if can_run else '⚠️ Resources low, may be slow'}")
|
|
|
|
return "\n".join(lines)
|
|
|
|
|
|
def get_all_stats():
|
|
tools = Tools()
|
|
return tools.get_all_stats()
|
|
|
|
def get_system_stats(system_name: str):
|
|
tools = Tools()
|
|
return tools.get_system_stats(system_name)
|
|
|
|
def check_alerts(system_name: str = "all"):
|
|
tools = Tools()
|
|
return tools.check_alerts(system_name)
|
|
|
|
def get_gpu_status(system_name: str = "folzocorp"):
|
|
tools = Tools()
|
|
return tools.get_gpu_status(system_name)
|
|
|
|
def can_run_comfyui():
|
|
tools = Tools()
|
|
return tools.can_run_comfyui()
|
|
```
|
|
|
|
## Usage Examples
|
|
|
|
```
|
|
get_all_stats()
|
|
get_system_stats("folzocorp")
|
|
check_alerts()
|
|
check_alerts("folzocorp")
|
|
get_gpu_status("folzocorp")
|
|
can_run_comfyui()
|
|
```
|
|
|
|
## System IDs
|
|
|
|
| System | ID | GPU |
|
|
|--------|-----|-----|
|
|
| FolzoCorp | `folzocorp` | AMD RX 7800 XT |
|
|
| EffiServer | `effiserver` | None (Intel J5040) |
|
|
| NAS Synology | `synology-nas` | Intel HD Graphics |
|
|
|
|
## Variables (Vannes) Configurables
|
|
|
|
| Variable | Description |
|
|
|----------|-------------|
|
|
| `beszel_url` | URL du hub Beszel (défaut: http://192.168.1.25:8090) |
|
|
| `beszel_user` | Email utilisateur Beszel |
|
|
| `beszel_password` | Mot de passe Beszel |
|
|
|
|
---
|
|
|
|
*Created 12 April 2026 - Updated API endpoints for PocketBase*
|