Files
Effi/docs/TOOL-BESZEL-OPENWEBUI.md
Folzo cf406b764f fix: Beszel tool - correct PocketBase API endpoints
- 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
2026-04-12 13:59:50 +02:00

14 KiB

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

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