v1.0 - Version stable: multi-PC, détection UI-DETR-1, 3 modes exécution

- Frontend v4 accessible sur réseau local (192.168.1.40)
- Ports ouverts: 3002 (frontend), 5001 (backend), 5004 (dashboard)
- Ollama GPU fonctionnel
- Self-healing interactif
- Dashboard confiance

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Dom
2026-01-29 11:23:51 +01:00
parent 21bfa3b337
commit a27b74cf22
1595 changed files with 412691 additions and 400 deletions

View File

@@ -0,0 +1,15 @@
"""Analytics dashboard module."""
from .dashboard_manager import (
DashboardManager,
Dashboard,
DashboardWidget,
DashboardTemplate
)
__all__ = [
'DashboardManager',
'Dashboard',
'DashboardWidget',
'DashboardTemplate'
]

View File

@@ -0,0 +1,468 @@
"""Dashboard management for analytics."""
import logging
import json
import uuid
from typing import Dict, List, Optional, Any
from datetime import datetime
from pathlib import Path
from dataclasses import dataclass, field
logger = logging.getLogger(__name__)
@dataclass
class DashboardWidget:
"""Dashboard widget configuration."""
widget_id: str
widget_type: str # chart, table, metric, insight
title: str
config: Dict[str, Any]
position: Dict[str, int] # x, y, width, height
def to_dict(self) -> Dict:
"""Convert to dictionary."""
return {
'widget_id': self.widget_id,
'widget_type': self.widget_type,
'title': self.title,
'config': self.config,
'position': self.position
}
@classmethod
def from_dict(cls, data: Dict) -> 'DashboardWidget':
"""Create from dictionary."""
return cls(**data)
@dataclass
class Dashboard:
"""Dashboard configuration."""
dashboard_id: str
name: str
description: str
owner: str
widgets: List[DashboardWidget] = field(default_factory=list)
layout: str = 'grid' # grid, flex
refresh_interval: int = 30 # seconds
is_public: bool = False
shared_with: List[str] = field(default_factory=list)
created_at: datetime = field(default_factory=datetime.now)
updated_at: datetime = field(default_factory=datetime.now)
def to_dict(self) -> Dict:
"""Convert to dictionary."""
return {
'dashboard_id': self.dashboard_id,
'name': self.name,
'description': self.description,
'owner': self.owner,
'widgets': [w.to_dict() for w in self.widgets],
'layout': self.layout,
'refresh_interval': self.refresh_interval,
'is_public': self.is_public,
'shared_with': self.shared_with,
'created_at': self.created_at.isoformat(),
'updated_at': self.updated_at.isoformat()
}
@classmethod
def from_dict(cls, data: Dict) -> 'Dashboard':
"""Create from dictionary."""
data = data.copy()
data['widgets'] = [DashboardWidget.from_dict(w) for w in data.get('widgets', [])]
data['created_at'] = datetime.fromisoformat(data['created_at'])
data['updated_at'] = datetime.fromisoformat(data['updated_at'])
return cls(**data)
@dataclass
class DashboardTemplate:
"""Pre-built dashboard template."""
template_id: str
name: str
description: str
category: str
widgets: List[DashboardWidget]
def to_dict(self) -> Dict:
"""Convert to dictionary."""
return {
'template_id': self.template_id,
'name': self.name,
'description': self.description,
'category': self.category,
'widgets': [w.to_dict() for w in self.widgets]
}
class DashboardManager:
"""Manage analytics dashboards."""
def __init__(self, storage_dir: str = "data/analytics/dashboards"):
"""
Initialize dashboard manager.
Args:
storage_dir: Directory for dashboard storage
"""
self.storage_dir = Path(storage_dir)
self.storage_dir.mkdir(parents=True, exist_ok=True)
self.dashboards: Dict[str, Dashboard] = {}
self.templates: Dict[str, DashboardTemplate] = {}
self._load_dashboards()
self._init_templates()
logger.info("DashboardManager initialized")
def create_dashboard(
self,
name: str,
description: str,
owner: str,
template_id: Optional[str] = None
) -> Dashboard:
"""
Create a new dashboard.
Args:
name: Dashboard name
description: Dashboard description
owner: Owner username
template_id: Optional template to use
Returns:
Created dashboard
"""
dashboard_id = str(uuid.uuid4())
# Create from template if specified
if template_id and template_id in self.templates:
template = self.templates[template_id]
widgets = [
DashboardWidget(
widget_id=str(uuid.uuid4()),
widget_type=w.widget_type,
title=w.title,
config=w.config.copy(),
position=w.position.copy()
)
for w in template.widgets
]
else:
widgets = []
dashboard = Dashboard(
dashboard_id=dashboard_id,
name=name,
description=description,
owner=owner,
widgets=widgets
)
self.dashboards[dashboard_id] = dashboard
self._save_dashboard(dashboard)
logger.info(f"Created dashboard: {dashboard_id}")
return dashboard
def get_dashboard(self, dashboard_id: str) -> Optional[Dashboard]:
"""Get dashboard by ID."""
return self.dashboards.get(dashboard_id)
def list_dashboards(
self,
owner: Optional[str] = None,
include_shared: bool = True
) -> List[Dashboard]:
"""
List dashboards.
Args:
owner: Filter by owner (None = all)
include_shared: Include dashboards shared with owner
Returns:
List of dashboards
"""
dashboards = list(self.dashboards.values())
if owner:
dashboards = [
d for d in dashboards
if d.owner == owner or
(include_shared and (d.is_public or owner in d.shared_with))
]
return dashboards
def update_dashboard(
self,
dashboard_id: str,
updates: Dict[str, Any]
) -> Optional[Dashboard]:
"""
Update dashboard configuration.
Args:
dashboard_id: Dashboard identifier
updates: Dictionary of updates
Returns:
Updated dashboard or None
"""
dashboard = self.dashboards.get(dashboard_id)
if not dashboard:
return None
# Apply updates
for key, value in updates.items():
if hasattr(dashboard, key):
setattr(dashboard, key, value)
dashboard.updated_at = datetime.now()
self._save_dashboard(dashboard)
logger.info(f"Updated dashboard: {dashboard_id}")
return dashboard
def delete_dashboard(self, dashboard_id: str) -> bool:
"""
Delete a dashboard.
Args:
dashboard_id: Dashboard identifier
Returns:
True if deleted, False if not found
"""
if dashboard_id not in self.dashboards:
return False
del self.dashboards[dashboard_id]
# Delete file
filepath = self.storage_dir / f"{dashboard_id}.json"
if filepath.exists():
filepath.unlink()
logger.info(f"Deleted dashboard: {dashboard_id}")
return True
def add_widget(
self,
dashboard_id: str,
widget_type: str,
title: str,
config: Dict[str, Any],
position: Dict[str, int]
) -> Optional[DashboardWidget]:
"""
Add widget to dashboard.
Args:
dashboard_id: Dashboard identifier
widget_type: Widget type
title: Widget title
config: Widget configuration
position: Widget position
Returns:
Created widget or None
"""
dashboard = self.dashboards.get(dashboard_id)
if not dashboard:
return None
widget = DashboardWidget(
widget_id=str(uuid.uuid4()),
widget_type=widget_type,
title=title,
config=config,
position=position
)
dashboard.widgets.append(widget)
dashboard.updated_at = datetime.now()
self._save_dashboard(dashboard)
logger.info(f"Added widget to dashboard {dashboard_id}")
return widget
def remove_widget(
self,
dashboard_id: str,
widget_id: str
) -> bool:
"""
Remove widget from dashboard.
Args:
dashboard_id: Dashboard identifier
widget_id: Widget identifier
Returns:
True if removed, False if not found
"""
dashboard = self.dashboards.get(dashboard_id)
if not dashboard:
return False
dashboard.widgets = [w for w in dashboard.widgets if w.widget_id != widget_id]
dashboard.updated_at = datetime.now()
self._save_dashboard(dashboard)
logger.info(f"Removed widget from dashboard {dashboard_id}")
return True
def share_dashboard(
self,
dashboard_id: str,
username: str
) -> bool:
"""
Share dashboard with a user.
Args:
dashboard_id: Dashboard identifier
username: Username to share with
Returns:
True if shared, False if not found
"""
dashboard = self.dashboards.get(dashboard_id)
if not dashboard:
return False
if username not in dashboard.shared_with:
dashboard.shared_with.append(username)
dashboard.updated_at = datetime.now()
self._save_dashboard(dashboard)
logger.info(f"Shared dashboard {dashboard_id} with {username}")
return True
def make_public(
self,
dashboard_id: str,
is_public: bool = True
) -> bool:
"""
Make dashboard public or private.
Args:
dashboard_id: Dashboard identifier
is_public: Whether dashboard should be public
Returns:
True if updated, False if not found
"""
dashboard = self.dashboards.get(dashboard_id)
if not dashboard:
return False
dashboard.is_public = is_public
dashboard.updated_at = datetime.now()
self._save_dashboard(dashboard)
logger.info(f"Dashboard {dashboard_id} public: {is_public}")
return True
def get_templates(self) -> List[DashboardTemplate]:
"""Get all dashboard templates."""
return list(self.templates.values())
def _load_dashboards(self) -> None:
"""Load dashboards from storage."""
for filepath in self.storage_dir.glob('*.json'):
try:
with open(filepath, 'r') as f:
data = json.load(f)
dashboard = Dashboard.from_dict(data)
self.dashboards[dashboard.dashboard_id] = dashboard
except Exception as e:
logger.error(f"Error loading dashboard {filepath}: {e}")
logger.info(f"Loaded {len(self.dashboards)} dashboards")
def _save_dashboard(self, dashboard: Dashboard) -> None:
"""Save dashboard to storage."""
filepath = self.storage_dir / f"{dashboard.dashboard_id}.json"
with open(filepath, 'w') as f:
json.dump(dashboard.to_dict(), f, indent=2)
def _init_templates(self) -> None:
"""Initialize default dashboard templates."""
# Performance Overview Template
self.templates['performance'] = DashboardTemplate(
template_id='performance',
name='Performance Overview',
description='Overview of workflow performance metrics',
category='performance',
widgets=[
DashboardWidget(
widget_id='perf_chart',
widget_type='chart',
title='Execution Duration Trend',
config={
'chart_type': 'line',
'metric': 'duration',
'time_range': '7d'
},
position={'x': 0, 'y': 0, 'width': 6, 'height': 4}
),
DashboardWidget(
widget_id='success_rate',
widget_type='metric',
title='Success Rate',
config={
'metric': 'success_rate',
'format': 'percentage'
},
position={'x': 6, 'y': 0, 'width': 3, 'height': 2}
),
DashboardWidget(
widget_id='bottlenecks',
widget_type='table',
title='Top Bottlenecks',
config={
'metric': 'bottlenecks',
'limit': 10
},
position={'x': 0, 'y': 4, 'width': 9, 'height': 4}
)
]
)
# Anomaly Detection Template
self.templates['anomalies'] = DashboardTemplate(
template_id='anomalies',
name='Anomaly Detection',
description='Real-time anomaly detection and alerts',
category='monitoring',
widgets=[
DashboardWidget(
widget_id='anomaly_chart',
widget_type='chart',
title='Anomalies Over Time',
config={
'chart_type': 'scatter',
'metric': 'anomalies',
'time_range': '24h'
},
position={'x': 0, 'y': 0, 'width': 8, 'height': 4}
),
DashboardWidget(
widget_id='anomaly_list',
widget_type='table',
title='Recent Anomalies',
config={
'metric': 'anomalies',
'limit': 20
},
position={'x': 0, 'y': 4, 'width': 12, 'height': 4}
)
]
)
logger.info(f"Initialized {len(self.templates)} dashboard templates")