fix(vwb): resolve frontend services from runtime host
Some checks failed
tests / Lint (ruff + black) (push) Failing after 1m46s
tests / Tests unitaires (sans GPU) (push) Failing after 1m50s
tests / Tests sécurité (critique) (push) Has been skipped

This commit is contained in:
Dom
2026-06-17 17:53:57 +02:00
parent 667575c3ad
commit 9605cc9d95
15 changed files with 110 additions and 53 deletions

View File

@@ -9,6 +9,16 @@
import { BoundingBox } from '../types';
// Origine de l'API core (port 8000) résolue dynamiquement à partir de l'hôte courant.
// NOTE: le port 8000 (API core upload) est conservé tel quel ; seul l'hôte devient
// dynamique pour rester compatible avec un accès distant (IP DGX).
const getCoreApiOrigin = (): string => {
if (typeof window !== 'undefined') {
return `http://${window.location.hostname}:8000`;
}
return (process.env.REACT_APP_CORE_API_ORIGIN || '').replace(/\/$/, '');
};
interface VisualMetadata {
element_type: string;
relative_position?: string;
@@ -63,7 +73,7 @@ class VisualCaptureService {
private cache: Map<string, any>;
private cacheTimeout: number;
constructor(baseUrl: string = 'http://localhost:8000') {
constructor(baseUrl: string = getCoreApiOrigin()) {
this.baseUrl = baseUrl;
this.timeout = 30000; // 30 secondes
this.cache = new Map();
@@ -527,4 +537,4 @@ class VisualCaptureService {
// Instance singleton du service
export const visualCaptureService = new VisualCaptureService();
export default VisualCaptureService;
export default VisualCaptureService;

View File

@@ -8,8 +8,28 @@
*/
import { BoundingBox } from '../types';
import { getApiOrigin } from './apiClient';
const API_BASE = 'http://localhost:5001';
// Origine de l'API résolue dynamiquement (compatible IP DGX / accès distant).
// Calculée à l'appel pour refléter window.location au runtime.
const apiBase = (): string => getApiOrigin();
/**
* Normalise une URL d'ancre potentiellement importée d'un autre poste.
*
* Les workflows importés peuvent contenir des URLs absolues codées sur une
* ancienne origine applicative. On réécrit
* uniquement le chemin /api/anchor-images vers l'origine API courante, sans
* muter le workflow source (transformation à l'usage uniquement).
*/
const normalizeAnchorUrl = (url: string): string => {
const marker = '/api/anchor-images';
const idx = url.indexOf(marker);
if (idx === -1) {
return url;
}
return `${apiBase()}${url.slice(idx)}`;
};
export interface AnchorImageUploadResult {
success: boolean;
@@ -67,7 +87,7 @@ export async function uploadAnchorImage(
anchorId
});
const response = await fetch(`${API_BASE}/api/anchor-images`, {
const response = await fetch(`${apiBase()}/api/anchor-images`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
@@ -106,7 +126,7 @@ export async function uploadAnchorImage(
* @returns URL complète de la miniature
*/
export function getThumbnailUrl(anchorId: string): string {
return `${API_BASE}/api/anchor-images/${anchorId}/thumbnail`;
return `${apiBase()}/api/anchor-images/${anchorId}/thumbnail`;
}
/**
@@ -116,7 +136,7 @@ export function getThumbnailUrl(anchorId: string): string {
* @returns URL complète de l'image originale
*/
export function getOriginalUrl(anchorId: string): string {
return `${API_BASE}/api/anchor-images/${anchorId}/original`;
return `${apiBase()}/api/anchor-images/${anchorId}/original`;
}
/**
@@ -126,7 +146,7 @@ export function getOriginalUrl(anchorId: string): string {
* @returns Métadonnées de l'ancre
*/
export async function getAnchorMetadata(anchorId: string): Promise<AnchorMetadata> {
const response = await fetch(`${API_BASE}/api/anchor-images/${anchorId}/metadata`);
const response = await fetch(`${apiBase()}/api/anchor-images/${anchorId}/metadata`);
if (!response.ok) {
throw new Error(`Ancre '${anchorId}' non trouvée`);
@@ -143,7 +163,7 @@ export async function getAnchorMetadata(anchorId: string): Promise<AnchorMetadat
* @returns true si supprimé avec succès
*/
export async function deleteAnchorImage(anchorId: string): Promise<boolean> {
const response = await fetch(`${API_BASE}/api/anchor-images/${anchorId}`, {
const response = await fetch(`${apiBase()}/api/anchor-images/${anchorId}`, {
method: 'DELETE',
});
@@ -166,7 +186,7 @@ export async function listAnchorImages(
offset: number = 0
): Promise<{ anchors: AnchorMetadata[]; total: number }> {
const response = await fetch(
`${API_BASE}/api/anchor-images?limit=${limit}&offset=${offset}`
`${apiBase()}/api/anchor-images?limit=${limit}&offset=${offset}`
);
if (!response.ok) {
@@ -186,7 +206,7 @@ export async function listAnchorImages(
* @returns Statistiques de stockage
*/
export async function getStorageStats(): Promise<StorageStats> {
const response = await fetch(`${API_BASE}/api/anchor-images/stats`);
const response = await fetch(`${apiBase()}/api/anchor-images/stats`);
if (!response.ok) {
throw new Error('Erreur lors de la récupération des statistiques');
@@ -205,7 +225,7 @@ export async function getStorageStats(): Promise<StorageStats> {
export async function anchorExists(anchorId: string): Promise<boolean> {
try {
const response = await fetch(
`${API_BASE}/api/anchor-images/${anchorId}/metadata`,
`${apiBase()}/api/anchor-images/${anchorId}/metadata`,
{ method: 'HEAD' }
);
return response.ok;
@@ -229,17 +249,18 @@ export function getPreviewImageUrl(anchor: {
}): string | null {
// Priorité 1: URL de miniature serveur
if (anchor.thumbnail_url) {
// Si l'URL est relative, ajouter le préfixe API
// URL absolue: normaliser une éventuelle origine périmée (workflow importé).
// URL relative: préfixer avec l'origine API courante.
return anchor.thumbnail_url.startsWith('http')
? anchor.thumbnail_url
: `${API_BASE}${anchor.thumbnail_url}`;
? normalizeAnchorUrl(anchor.thumbnail_url)
: `${apiBase()}${anchor.thumbnail_url}`;
}
// Priorité 2: URL d'image originale serveur
if (anchor.reference_image_url) {
return anchor.reference_image_url.startsWith('http')
? anchor.reference_image_url
: `${API_BASE}${anchor.reference_image_url}`;
? normalizeAnchorUrl(anchor.reference_image_url)
: `${apiBase()}${anchor.reference_image_url}`;
}
// Priorité 3: Construire l'URL depuis anchor_id si présent

View File

@@ -46,20 +46,33 @@ type ConnectionState = 'online' | 'offline' | 'checking';
// Callbacks pour les changements d'état
type ConnectionStateCallback = (state: ConnectionState) => void;
// Détection automatique de l'hôte pour support multi-machines
// Si on accède via une IP (ex: 192.168.1.40), utiliser cette IP pour l'API
// Sinon utiliser localhost
const getApiHost = (): string => {
// Détection automatique de l'hôte pour support multi-machines.
// Si on accède via une IP ou un nom DNS DGX, utiliser cet hôte pour l'API.
//
// IMPORTANT: le backend Flask (dashboard/API VWB) écoute sur le port 5001.
// On résout dynamiquement l'origine à partir de window.location.hostname pour
// rester compatible avec un accès distant (IP DGX) sans URL codée en dur.
/**
* Origine de l'API (sans suffixe /api), résolue dynamiquement.
* Exemple: http://192.168.1.45:5001 ou http://dgx-site:5001
*/
export const getApiOrigin = (): string => {
if (typeof window !== 'undefined') {
const hostname = window.location.hostname;
// Si c'est localhost ou 127.0.0.1, garder localhost
if (hostname === 'localhost' || hostname === '127.0.0.1') {
return 'http://localhost:5001/api';
}
// Sinon utiliser le même hostname (IP) avec le port 5000
return `http://${hostname}:5000/api`;
// En accès distant comme en local, le backend Flask reste sur le port 5001
return `http://${hostname}:5001`;
}
return 'http://localhost:5001/api';
return (process.env.REACT_APP_API_ORIGIN || '').replace(/\/$/, '');
};
/**
* URL de base de l'API (avec suffixe /api), résolue dynamiquement.
* Exemple: http://192.168.1.45:5001/api ou http://dgx-site:5001/api
*/
export const getApiHost = (): string => {
const origin = getApiOrigin();
return origin ? `${origin}/api` : '/api';
};
// Configuration par défaut

View File

@@ -46,6 +46,9 @@ import {
getStaticCatalogStats,
} from '../data/staticCatalog';
// Origine de l'API résolue dynamiquement (compatible IP DGX / accès distant).
import { getApiOrigin } from './apiClient';
// Configuration du service catalogue
interface CatalogServiceConfig {
urls: string[];
@@ -173,18 +176,17 @@ class CatalogService {
const currentOrigin = window.location.origin;
candidateUrls.push(currentOrigin);
// 4. Localhost standard (développement) - Port 5001 en priorité
if (!candidateUrls.includes('http://localhost:5001')) {
candidateUrls.push('http://localhost:5001');
}
if (!candidateUrls.includes('http://localhost:5001')) {
candidateUrls.push('http://localhost:5001');
// 4. Origine API courante (port 5001), résolue dynamiquement.
// En accès navigateur -> http://<hostname>:5001
const apiOrigin = getApiOrigin();
if (!candidateUrls.includes(apiOrigin)) {
candidateUrls.push(apiOrigin);
}
// 5. IP locale détectée (cross-machine)
try {
const localIp = this.detectLocalIp();
if (localIp && localIp !== '127.0.0.1') {
if (localIp && !localIp.startsWith('127.')) {
candidateUrls.push(`http://${localIp}:5000`);
candidateUrls.push(`http://${localIp}:5004`);
}
@@ -973,4 +975,4 @@ export type {
CatalogActionCategory,
};
export default CatalogService;
export default CatalogService;

View File

@@ -4,13 +4,15 @@
*/
import { VWBEvidence, EvidenceFilters, EvidenceExportOptions, EvidenceStats, EvidenceUtils } from '../types/evidence';
import { getApiOrigin } from './apiClient';
export class EvidenceService {
private baseUrl: string;
private cache: Map<string, VWBEvidence[]> = new Map();
private cacheTimeout: number = 5 * 60 * 1000; // 5 minutes
constructor(baseUrl: string = 'http://localhost:5001') {
// baseUrl résolu dynamiquement par défaut (origine API courante, compatible IP DGX)
constructor(baseUrl: string = getApiOrigin()) {
this.baseUrl = baseUrl;
}

View File

@@ -6,8 +6,11 @@
* en utilisant le service RealScreenCaptureService du backend.
*/
import { getApiHost } from './apiClient';
// Configuration du service
const BACKEND_BASE_URL = 'http://localhost:5001/api';
// Base URL de l'API résolue dynamiquement (compatible IP DGX / accès distant).
const BACKEND_BASE_URL = getApiHost();
const REQUEST_TIMEOUT = 20000; // 20 secondes pour la capture avec détection
// Types pour les réponses API

View File

@@ -7,9 +7,11 @@
*/
import { BoundingBox, VisualSelection } from '../types';
import { getApiHost } from './apiClient';
// Configuration du service
const BACKEND_BASE_URL = 'http://localhost:5001/api';
// Base URL de l'API résolue dynamiquement (compatible IP DGX / accès distant).
const BACKEND_BASE_URL = getApiHost();
const REQUEST_TIMEOUT = 15000; // 15 secondes pour la capture d'écran
// Types pour les réponses API