feat: capture Windows temps réel via mini serveur HTTP (port 5006)
- CaptureServer : serveur HTTP daemon sur l'agent Windows - Capture fraîche mss en ~94ms à chaque requête - Plus de lecture de vieux heartbeats sur disque - Fallback capture locale si agent indisponible - Firewall Windows port 5006 configuré Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -124,57 +124,36 @@ def capture_screen():
|
||||
@cross_origin()
|
||||
def capture_windows():
|
||||
"""
|
||||
Récupère le dernier screenshot du PC Windows (via streaming server).
|
||||
Capture l'ecran Windows EN TEMPS REEL via le mini-serveur de l'agent.
|
||||
|
||||
Le client Agent V1 envoie des heartbeats toutes les 5s.
|
||||
On récupère le plus récent comme capture.
|
||||
L'agent V1 expose un serveur HTTP sur le port 5006 qui retourne
|
||||
un screenshot frais a chaque requete (< 100ms).
|
||||
Plus besoin de lire de vieux fichiers heartbeat sur disque.
|
||||
"""
|
||||
import glob
|
||||
from pathlib import Path
|
||||
import requests as http_client
|
||||
|
||||
# Remonter jusqu'à la racine du projet (rpa_vision_v3/)
|
||||
project_root = Path(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))))
|
||||
live_dir = project_root / "data" / "training" / "live_sessions"
|
||||
|
||||
# Chercher aussi dans les sous-dossiers machine (multi-machine)
|
||||
import time as _time
|
||||
|
||||
# Chercher le screenshot le plus récent dans TOUS les dossiers
|
||||
all_shots = []
|
||||
for pattern in ["bg_*/shots/heartbeat_*.png", "sess_*/shots/heartbeat_*.png",
|
||||
"*/bg_*/shots/heartbeat_*.png", "bg_*/shots/shot_*_full.png",
|
||||
"sess_*/shots/shot_*_full.png"]:
|
||||
all_shots.extend(live_dir.glob(pattern))
|
||||
|
||||
if not all_shots:
|
||||
return jsonify({'error': 'Aucun screenshot Windows. Lancez l\'agent V1 sur le PC cible.'}), 404
|
||||
|
||||
# Trier par date de modification (le plus récent en premier)
|
||||
all_shots.sort(key=lambda p: p.stat().st_mtime, reverse=True)
|
||||
latest_shot = all_shots[0]
|
||||
age_seconds = _time.time() - latest_shot.stat().st_mtime
|
||||
|
||||
if not latest_shot:
|
||||
return jsonify({'error': 'Aucun screenshot Windows disponible'}), 404
|
||||
agent_host = os.environ.get('RPA_WINDOWS_AGENT_HOST', '192.168.1.11')
|
||||
agent_port = int(os.environ.get('RPA_WINDOWS_AGENT_PORT', '5006'))
|
||||
agent_url = f'http://{agent_host}:{agent_port}/capture'
|
||||
|
||||
try:
|
||||
from PIL import Image
|
||||
img = Image.open(latest_shot)
|
||||
buf = io.BytesIO()
|
||||
img.save(buf, format='PNG')
|
||||
img_base64 = base64.b64encode(buf.getvalue()).decode('utf-8')
|
||||
|
||||
resp = http_client.get(agent_url, timeout=10)
|
||||
if resp.ok:
|
||||
return jsonify(resp.json())
|
||||
return jsonify({
|
||||
'image': img_base64,
|
||||
'width': img.width,
|
||||
'height': img.height,
|
||||
'format': 'png',
|
||||
'source': 'windows',
|
||||
'file': str(latest_shot.name),
|
||||
'session': latest_shot.parent.parent.name,
|
||||
'age_seconds': round(age_seconds, 1),
|
||||
'fresh': age_seconds < 30,
|
||||
})
|
||||
'error': f'Agent a repondu {resp.status_code}',
|
||||
'detail': resp.text[:500],
|
||||
}), 503
|
||||
except http_client.ConnectionError:
|
||||
return jsonify({
|
||||
'error': f'Agent Windows non connecte ({agent_host}:{agent_port})',
|
||||
'hint': 'Verifiez que l\'agent V1 est lance sur le PC Windows '
|
||||
'et que le port 5006 est accessible.',
|
||||
}), 503
|
||||
except http_client.Timeout:
|
||||
return jsonify({
|
||||
'error': 'Timeout — l\'agent n\'a pas repondu dans les 10s',
|
||||
}), 504
|
||||
except Exception as e:
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
|
||||
@@ -111,25 +111,28 @@ export default function CapturePanel({
|
||||
}
|
||||
};
|
||||
|
||||
// Capture intelligente — auto-détection OS
|
||||
// Capture intelligente — tente d'abord l'agent Windows distant,
|
||||
// puis fallback sur la capture locale si l'agent est injoignable.
|
||||
const doSmartCapture = async () => {
|
||||
const isWindows = navigator.platform?.includes('Win') || navigator.userAgent?.includes('Windows');
|
||||
if (isWindows) {
|
||||
try {
|
||||
const resp = await fetch('/api/screen-capture/capture-windows', { method: 'POST' });
|
||||
const data = await resp.json();
|
||||
if (data.image) {
|
||||
setCurrentCapture({
|
||||
screenshot_base64: data.image,
|
||||
width: data.width,
|
||||
height: data.height,
|
||||
source: 'windows',
|
||||
} as any);
|
||||
}
|
||||
} catch {}
|
||||
} else {
|
||||
onCapture();
|
||||
try {
|
||||
const resp = await fetch('/api/screen-capture/capture-windows', { method: 'POST' });
|
||||
const data = await resp.json();
|
||||
if (resp.ok && data.image) {
|
||||
setCurrentCapture({
|
||||
screenshot_base64: data.image,
|
||||
width: data.width,
|
||||
height: data.height,
|
||||
source: data.source || 'windows',
|
||||
} as any);
|
||||
return;
|
||||
}
|
||||
// Agent indisponible — fallback capture locale
|
||||
console.warn('Agent Windows indisponible, fallback local:', data.error);
|
||||
} catch (err) {
|
||||
console.warn('Erreur capture Windows, fallback local:', err);
|
||||
}
|
||||
// Fallback : capture locale (ecran du serveur Linux)
|
||||
onCapture();
|
||||
};
|
||||
|
||||
const handleTimerCapture = () => {
|
||||
|
||||
Reference in New Issue
Block a user