bemade-addons/unifi_integration/models/unifi_site.py
2025-03-27 15:34:13 -04:00

4513 lines
190 KiB
Python

# -*- coding: utf-8 -*-
# These imports will work in an Odoo environment, even if your IDE marks them as not found
# pylint: disable=import-error
from odoo import models, fields, api, _
from odoo.exceptions import UserError, ValidationError
from .unifi_common import UnifiCommonMixin
# pylint: enable=import-error
import json
import logging
import requests
import urllib3
import tempfile
import os
import base64
import ast
from datetime import datetime, timedelta
from typing import Dict, Tuple, List, Any
from requests.exceptions import RequestException, ConnectionError
_logger = logging.getLogger(__name__)
# TODO: Refactorisation du modèle UnifiSite
# Changements effectués:
# 1. Fusion de trois fichiers en un seul:
# - unifi_site.py, unifi_site_controller.py et unifi_site_manager.py
# 2. Intégration directe des champs et méthodes spécifiques dans le modèle principal
# 3. Simplification des relations et de la validation
class UnifiSite(models.Model, UnifiCommonMixin):
"""Represents a UniFi site managed by one or more UniFi devices
This model is the central entity that groups all UniFi configurations and devices.
Each site can have multiple devices, networks, and users.
It supports both the Site Manager API (cloud) and the Controller API (local).
All functionality is now integrated in a single model for simplicity and maintainability.
The API type determines which fields and methods are applicable.
"""
_name = 'unifi.site'
_description = 'UniFi Site'
_order = 'name'
_inherit = ['mail.thread', 'mail.activity.mixin']
# Basic site information
name = fields.Char(
string='Name',
required=True,
help='Site name'
)
site_id = fields.Char(
string='Site ID',
help="Site identifier in UniFi (usually 'default')",
default='default',
readonly=True,
required=True
)
description = fields.Text(
string='Description',
help='Site description'
)
address = fields.Text(
string='Physical Address',
help='Physical location of this site'
)
active = fields.Boolean(
string='Active',
default=True,
help='Indicates if this site is currently active'
)
# API Type - New field to distinguish between Site Manager and Controller APIs
api_type = fields.Selection(
selection=[
('site_manager', 'Site Manager (Cloud)'),
('controller', 'Controller (Local)')
],
string='API Type',
required=True,
default='controller',
help='Type of API used to connect to this site'
)
# Relation avec la configuration API
api_config_id = fields.Many2one(
comodel_name='unifi.api.config',
string='Configuration API',
help='Configuration API utilisée pour ce site'
)
# Performance and synchronization settings
timeout = fields.Float(
string='Timeout',
default=10.0,
help='API request timeout in seconds'
)
max_retries = fields.Integer(
string='Max Retries',
default=3,
help='Maximum number of retries for API requests'
)
auto_sync = fields.Boolean(
string='Automatic Synchronization',
default=False,
help='Enable automatic synchronization of this site'
)
sync_interval = fields.Integer(
string='Sync Interval',
default=60,
help='Interval in minutes between automatic synchronizations'
)
# SSL verification - Common for both API types
verify_ssl = fields.Boolean(
string='Verify SSL',
default=True,
help='Enable SSL certificate verification',
)
# Connection information - Common fields
timestamp = fields.Datetime(
string='Created Date',
default=lambda self: fields.Datetime.now(),
readonly=True,
help='Date and time when this site was created'
)
last_sync = fields.Datetime(
string='Last Sync',
readonly=True,
help='Date and time when this site was last synchronized'
)
last_update = fields.Datetime(
string='Last Update',
readonly=True,
help='Date and time of the last successful synchronization'
)
last_import_date = fields.Datetime(
string='Last Import Date',
readonly=True,
help='Date and time of the last successful configuration import'
)
last_response_headers = fields.Text(
string='Last Response Headers',
readonly=True,
copy=False,
help='Headers from the last API response'
)
last_response_content = fields.Text(
string='Last Response Content',
readonly=True,
copy=False,
help='Content from the last API response'
)
last_successful_endpoint = fields.Char(
string='Last Successful Endpoint',
readonly=True,
copy=False,
help='The last endpoint that was successfully used for authentication'
)
import_status = fields.Selection(
selection=[
('success', 'Success'),
('failed', 'Failed'),
('pending', 'Pending')
],
string='Import Status',
default='pending',
help='Status of the last configuration import'
)
# SSL verification - Common for both API types
verify_ssl = fields.Boolean(
string='Verify SSL',
default=False,
help='Enable SSL certificate verification'
)
# Controller API specific fields
host = fields.Char(
string='Host',
help='IP address or hostname of the controller'
)
port = fields.Integer(
string='Port',
default=443,
help='Port number (default: 443)'
)
username = fields.Char(
string='Username',
help='Username for controller login'
)
password = fields.Char(
string='Password',
help='Password for controller login'
)
ssl_cert_file = fields.Binary(
string='Certificat SSL personnalisé',
attachment=True,
help='Fichier de certificat SSL personnalisé (.pem ou .crt)'
)
ssl_cert_filename = fields.Char(
string='Nom du fichier de certificat'
)
ssl_cert_path = fields.Char(
string='Chemin du certificat',
compute='_compute_ssl_cert_path',
store=True,
help='Chemin vers le fichier de certificat SSL'
)
# Site Manager API specific fields
api_key = fields.Char(
string='API Key',
help='API Key for Site Manager authentication'
)
mfa_enabled = fields.Boolean(
string='MFA Enabled',
default=False,
help='Enable Multi-Factor Authentication'
)
mfa_token = fields.Char(
string='MFA Token',
help='Multi-Factor Authentication token'
)
# Authentication fields
auth_session_id = fields.Many2one(
comodel_name='unifi.auth.session',
string='Session d\'authentification',
ondelete='set null',
help='Session d\'authentification active pour ce site'
)
# Relations avec d'autres modèles
device_ids = fields.One2many(
comodel_name='unifi.device',
inverse_name='site_id',
string='Devices',
help='Devices in this site'
)
device_count = fields.Integer(
string='Device Count',
compute='_compute_counts',
compute_sudo=True,
store=True,
help='Number of devices in this site'
)
network_ids = fields.One2many(
comodel_name='unifi.network',
inverse_name='site_id',
string='Networks',
help='Networks in this site'
)
network_count = fields.Integer(
string='Network Count',
compute='_compute_counts',
compute_sudo=True,
store=True,
help='Number of networks in this site'
)
user_ids = fields.One2many(
comodel_name='unifi.user',
inverse_name='site_id',
string='Users',
help='Users in this site'
)
user_count = fields.Integer(
string='User Count',
compute='_compute_counts',
compute_sudo=True,
store=True,
help='Number of users in this site'
)
# Relations pour les VLANs
vlan_ids = fields.One2many(
comodel_name='unifi.vlan',
inverse_name='site_id',
string='VLANs',
help='VLANs in this site'
)
vlan_count = fields.Integer(
string='VLAN Count',
compute='_compute_counts',
compute_sudo=True,
store=True,
help='Number of VLANs in this site'
)
# Relations pour les règles de pare-feu
firewall_rule_ids = fields.One2many(
comodel_name='unifi.firewall.rule',
inverse_name='site_id',
string='Firewall Rules',
help='Firewall rules in this site'
)
firewall_rule_count = fields.Integer(
string='Firewall Rule Count',
compute='_compute_counts',
compute_sudo=True,
store=True,
help='Number of firewall rules in this site'
)
# Relations pour les redirections de port
port_forward_ids = fields.One2many(
comodel_name='unifi.port.forward',
inverse_name='site_id',
string='Port Forwards',
help='Port forwarding rules in this site'
)
port_forward_count = fields.Integer(
string='Port Forward Count',
compute='_compute_counts',
compute_sudo=True,
store=True,
help='Number of port forwarding rules in this site'
)
# Relations pour les configurations de routage
routing_config_ids = fields.One2many(
comodel_name='unifi.routing.config',
inverse_name='site_id',
string='Routing Configurations',
help='Routing configurations in this site'
)
routing_config_count = fields.Integer(
string='Routing Config Count',
compute='_compute_counts',
compute_sudo=True,
store=True,
help='Number of routing configurations in this site'
)
# Relations pour les WiFi
wifi_ids = fields.One2many(
comodel_name='unifi.wifi',
inverse_name='site_id',
string='WiFi Networks',
help='WiFi networks in this site'
)
wifi_count = fields.Integer(
string='WiFi Count',
compute='_compute_counts',
compute_sudo=True,
store=True,
help='Number of WiFi networks in this site'
)
# Relations pour DNS
dns_ids = fields.One2many(
comodel_name='unifi.dns',
inverse_name='site_id',
string='DNS Entries',
help='DNS entries in this site'
)
dns_count = fields.Integer(
string='DNS Count',
compute='_compute_counts',
compute_sudo=True,
store=True,
help='Number of DNS entries in this site'
)
# Relations pour System Info
system_info_ids = fields.One2many(
comodel_name='unifi.system.info',
inverse_name='site_id',
string='System Info',
help='System information for this site'
)
system_info_count = fields.Integer(
string='System Info Count',
compute='_compute_counts',
compute_sudo=True,
store=True,
help='Number of system info entries in this site'
)
# Relations pour VPN
vpn_ids = fields.One2many(
comodel_name='unifi.vpn',
inverse_name='site_id',
string='VPN Configurations',
help='VPN configurations in this site'
)
vpn_count = fields.Integer(
string='VPN Count',
compute='_compute_counts',
compute_sudo=True,
store=True,
help='Number of VPN configurations in this site'
)
# Relations pour les logs API et les jobs de synchronisation
api_log_ids = fields.One2many(
comodel_name='unifi.api.log',
inverse_name='site_id',
string='API Logs',
help='API logs for this site'
)
sync_job_ids = fields.One2many(
comodel_name='unifi.sync.job',
inverse_name='site_id',
string='Sync Jobs',
help='Synchronization jobs for this site'
)
# Note: La méthode _compute_counts est définie plus bas dans le fichier
@api.depends('ssl_cert_file', 'ssl_cert_filename')
def _compute_connection_fields(self):
"""Compute connection fields based on API configuration
This method sets connection fields (like verify_ssl, host, port, etc.)
based on the selected API configuration.
"""
for record in self:
# Utiliser la valeur par défaut si aucune configuration n'est définie
if not record.api_config_id:
# Garder les valeurs actuelles ou utiliser les valeurs par défaut
if not hasattr(record, 'verify_ssl') or record.verify_ssl is None:
record.verify_ssl = False
continue
# Si une configuration API est définie, utiliser ses valeurs
if record.api_config_id.api_type == record.api_type:
# Copier les champs de connexion de la configuration API
record.verify_ssl = record.api_config_id.verify_ssl
# Définir les champs spécifiques au type d'API
if record.api_type == 'controller':
# Extraire l'hôte et le port de l'URL de base
from urllib.parse import urlparse
parsed_url = urlparse(record.api_config_id.base_url)
record.host = parsed_url.netloc.split(':')[0] if ':' in parsed_url.netloc else parsed_url.netloc
record.port = parsed_url.port or 443
record.username = record.api_config_id.username
record.password = record.api_config_id.password
elif record.api_type == 'site_manager':
record.api_key = record.api_config_id.token
#----------------------------------------------------------
# Méthodes pour la récupération des données (remplacent les méthodes du controller)
#----------------------------------------------------------
def _get_system_info_data(self):
"""Récupère les informations système du site
Cette méthode remplace l'ancienne méthode get_system_info_data du controller.
Returns:
dict: Données d'information système ou False en cas d'échec
"""
self.ensure_one()
# TODO: Implémenter la récupération des informations système
return {}
def _get_device_data(self):
"""Récupère les données des appareils du site
Cette méthode remplace l'ancienne méthode get_device_data du controller.
Returns:
dict: Données des appareils ou False en cas d'échec
"""
self.ensure_one()
_logger.info("=== DÉBUT DE LA RÉCUPÉRATION DES APPAREILS ===")
# Vérifier que nous avons une session d'authentification valide
if not self._check_auth_session():
_logger.error("Pas de session d'authentification valide pour récupérer les appareils")
return False
try:
# Construire l'URL pour récupérer les appareils
base_url = f"https://{self.host}:{self.port}"
# Essayer de se reconnecter pour obtenir des cookies frais
_logger.info("Tentative de reconnexion pour obtenir des cookies frais")
connection_result = self._test_controller_connection()
if connection_result.get('status') != 'success':
_logger.error(f"Impossible de se reconnecter: {connection_result.get('message')}")
return False
_logger.info("Reconnexion réussie, poursuite de la récupération des appareils")
# Déterminer si nous avons affaire à un UDM Pro/UCG Max ou un contrôleur standard
# Si nous nous sommes connectés avec /api/auth/login, c'est probablement un UDM Pro/UCG Max
# Ou si la réponse contient 'UDM Pro' ou 'Dream Machine' dans les headers ou le contenu
is_udm_pro = False
# Vérifier si l'endpoint d'authentification est celui d'un UDM Pro
if self.auth_session_id and self.auth_session_id.endpoint and '/api/auth/login' in self.auth_session_id.endpoint:
is_udm_pro = True
# Vérifier si le dernier contenu de réponse contient des indices d'un UDM Pro
if self.last_response_content and ('UDMPRO' in self.last_response_content or 'Dream Machine' in self.last_response_content):
is_udm_pro = True
_logger.info(f"Type de contrôleur détecté: {'UDM Pro/UCG Max' if is_udm_pro else 'Controller standard'}")
# Essayer différents site_id et endpoints
site_ids = [
self.site_id, # Utiliser le site_id configuré
"default", # Valeur par défaut souvent utilisée
"" # Essayer sans site_id
]
# Préparer la vérification SSL
verify = self.verify_ssl
if verify and self.ssl_cert_path:
verify = self.ssl_cert_path
# Essayer chaque combinaison de site_id et endpoint
response = None
success = False
for site_id in site_ids:
# Construire les endpoints avec le site_id actuel
device_endpoints = []
# Forcer is_udm_pro à True car nous avons réussi à nous connecter avec /api/auth/login
# Ce qui est caractéristique d'un UDM Pro
if self.last_successful_endpoint and '/api/auth/login' in self.last_successful_endpoint:
is_udm_pro = True
_logger.info("Détection forcée d'un UDM Pro basée sur l'endpoint d'authentification utilisé")
# Vérifier si les cookies contiennent un TOKEN, ce qui est caractéristique d'un UDM Pro
cookies = self._get_auth_cookies()
if cookies and 'TOKEN' in cookies:
is_udm_pro = True
_logger.info("Détection forcée d'un UDM Pro basée sur la présence d'un TOKEN dans les cookies")
if site_id:
if is_udm_pro:
# Pour UDM Pro/UCG Max, tous les endpoints doivent être préfixés avec /proxy/network
device_endpoints = [
f"/proxy/network/api/s/{site_id}/stat/device", # Endpoint pour UDM Pro/UCG Max
f"/proxy/network/v2/api/site/{site_id}/device", # Autre endpoint possible pour UDM Pro
f"/proxy/network/api/site/{site_id}/stat/device", # Autre endpoint possible
f"/api/s/{site_id}/stat/device", # Essayer aussi sans le préfixe
]
else:
# Pour les contrôleurs standard
device_endpoints = [
f"/api/s/{site_id}/stat/device", # Endpoint standard
f"/api/site/{site_id}/stat/device", # Endpoint alternatif
f"/v2/api/site/{site_id}/device" # Endpoint pour les versions plus récentes
]
else:
# Endpoints sans site_id
if is_udm_pro:
device_endpoints = [
"/proxy/network/api/stat/device", # Pour UDM Pro/UCG Max
"/proxy/network/api/s/default/stat/device", # Essai avec 'default'
"/proxy/network/v2/api/site/default/device", # Autre endpoint possible pour UDM Pro
"/api/stat/device" # Essai sans le préfixe
]
else:
device_endpoints = [
"/api/stat/device", # Essai sans site_id
"/v2/api/device" # Autre essai sans site_id
]
for endpoint in device_endpoints:
try:
current_url = f"{base_url}{endpoint}"
_logger.info(f"Essai avec l'endpoint: {endpoint} (site_id: {site_id or 'aucun'})")
# Récupérer les cookies de la session d'authentification
cookies = self._get_auth_cookies()
if not cookies:
_logger.error("Impossible de récupérer les cookies d'authentification")
continue
# Afficher les cookies pour débogage
_logger.info(f"Cookies utilisés: {cookies}")
# Construire les arguments de la requête
kwargs = {
"cookies": cookies,
"verify": verify,
"timeout": self.timeout
}
# Ajouter des headers spécifiques
# Extraire le token pour l'authentification
token = cookies.get('TOKEN', '')
_logger.info(f"Token utilisé pour l'authentification: {token[:20]}..." if token else "Pas de token disponible")
headers = {
"Content-Type": "application/json",
"Accept": "application/json",
"X-Csrf-Token": cookies.get('csrf_token', ''), # Essayer d'ajouter un token CSRF
"User-Agent": "Mozilla/5.0 Odoo UniFi Integration", # Ajouter un User-Agent
"Authorization": f"Bearer {token}" # Ajouter le token d'autorisation pour UDM Pro
}
kwargs["headers"] = headers
# Essayer avec une session requests pour maintenir les cookies
session = requests.Session()
for key, value in cookies.items():
session.cookies.set(key, value)
response = session.get(current_url, **kwargs)
_logger.info(f"Réponse reçue: Status code {response.status_code}")
# Stocker les headers de la réponse pour une utilisation ultérieure
self.write({'last_response_headers': str(dict(response.headers))})
# Afficher le contenu exact de la réponse pour débogage
_logger.info(f"Contenu brut de la réponse: '{response.text}'")
_logger.info(f"Longueur de la réponse: {len(response.text)} caractères")
_logger.info(f"Headers de la réponse: {dict(response.headers)}")
# Vérifier si la réponse est du JSON valide
try:
json_data = response.json()
if response.status_code == 200:
_logger.info(f"Récupération réussie avec l'endpoint {endpoint}")
success = True
break
except ValueError:
_logger.warning(f"La réponse n'est pas du JSON valide: {response.text[:100]}...")
except Exception as e:
_logger.warning(f"Échec avec l'endpoint {endpoint}: {str(e)}")
if success:
break
if not response or not success:
_logger.error("Tous les endpoints ont échoué pour la récupération des appareils")
return False
# Analyser la réponse JSON
try:
# Vérifier si la réponse est vide
if not response.text.strip():
_logger.error("La réponse est vide")
return False
# Essayer d'analyser la réponse JSON
data = response.json()
_logger.info(f"Données reçues: {len(data.get('data', []))} appareils")
_logger.info(f"Structure des données: {list(data.keys())}")
return data
except ValueError as e:
_logger.error(f"Erreur lors de l'analyse de la réponse JSON: {str(e)}")
# Essayer de comprendre pourquoi l'analyse JSON a échoué
if response.text.strip().startswith('<'):
_logger.error("La réponse semble être du HTML ou du XML, pas du JSON")
elif response.text.strip() == '':
_logger.error("La réponse est vide")
else:
_logger.error(f"Les 100 premiers caractères de la réponse: {repr(response.text[:100])}")
return False
except Exception as e:
_logger.error(f"Erreur lors de la récupération des appareils: {str(e)}")
_logger.exception("Détails de l'erreur:")
return False
finally:
_logger.info("=== FIN DE LA RÉCUPÉRATION DES APPAREILS ===")
def _get_network_data(self):
"""Récupère les données des réseaux du site
Cette méthode remplace l'ancienne méthode get_network_data du controller.
Returns:
dict: Données des réseaux ou False en cas d'échec
"""
self.ensure_one()
# TODO: Implémenter la récupération des données des réseaux
return {}
def _get_vlan_data(self):
"""Récupère les données des VLANs du site
Cette méthode remplace l'ancienne méthode get_vlan_data du controller.
Returns:
dict: Données des VLANs ou False en cas d'échec
"""
self.ensure_one()
# TODO: Implémenter la récupération des données des VLANs
return {}
def _get_user_data(self):
"""Récupère les données des utilisateurs du site
Cette méthode remplace l'ancienne méthode get_user_data du controller.
Returns:
dict: Données des utilisateurs ou False en cas d'échec
"""
self.ensure_one()
# TODO: Implémenter la récupération des données des utilisateurs
return {}
def _get_firewall_data(self):
"""Récupère les données du pare-feu du site
Cette méthode remplace l'ancienne méthode get_firewall_data du controller.
Returns:
dict: Données du pare-feu ou False en cas d'échec
"""
self.ensure_one()
# TODO: Implémenter la récupération des données du pare-feu
return {}
def _get_port_forward_data(self):
"""Récupère les données de redirection de port du site
Cette méthode remplace l'ancienne méthode get_port_forward_data du controller.
Returns:
dict: Données de redirection de port ou False en cas d'échec
"""
self.ensure_one()
# TODO: Implémenter la récupération des données de redirection de port
return {}
# Note: La méthode _inverse_verify_ssl a été supprimée car elle n'est plus nécessaire
# puisque le champ verify_ssl n'est plus un champ calculé
def _compute_ssl_cert_path(self):
"""Calcule le chemin vers le fichier de certificat SSL
Cette méthode est appelée lorsque le fichier de certificat SSL est modifié.
Elle sauvegarde le certificat dans un fichier temporaire et stocke le chemin.
"""
for record in self:
if record.ssl_cert_file and record.ssl_cert_filename:
# Créer un fichier temporaire pour stocker le certificat
fd, path = tempfile.mkstemp(suffix='.pem')
try:
# Décoder le contenu du certificat et l'écrire dans le fichier
cert_content = base64.b64decode(record.ssl_cert_file)
os.write(fd, cert_content)
# Stocker le chemin du fichier
record.ssl_cert_path = path
finally:
os.close(fd)
else:
record.ssl_cert_path = False
#----------------------------------------------------------
# API Connection Methods
#----------------------------------------------------------
def test_connection(self):
"""Test the connection to the UniFi API
This method attempts to establish a connection with the UniFi API
to verify that the connection parameters are correct. It will use
either the Controller or Site Manager API based on the api_type.
Returns:
dict: Dictionary with status and message
"""
self.ensure_one()
# Create an API log entry for this test
api_log = self.env['unifi.api.log'].create({
'site_id': self.id,
'api_type': self.api_type,
'method': 'GET',
'endpoint': '/test',
'start_time': fields.Datetime.now(),
})
try:
if self.api_type == 'controller':
result = self._test_controller_connection(api_log)
elif self.api_type == 'site_manager':
result = self._test_site_manager_connection(api_log)
else:
raise ValidationError(_('Invalid API type'))
return result
except Exception as e:
_logger.error('Error testing connection: %s', str(e))
# Update the API log with the error
self._update_api_log(api_log, {
'status': 'error',
'response_code': 500,
'end_time': fields.Datetime.now(),
'execution_time': (fields.Datetime.now() - api_log.start_time).total_seconds(),
'error': str(e),
})
return {
'status': 'error',
'message': _('Connection test failed: %s') % str(e)
}
def _test_controller_connection(self, api_log=None):
"""Test the connection to the UniFi Controller API
Args:
api_log: Optional API log record to update
Returns:
dict: Dictionary with status and message
"""
self.ensure_one()
# Add debug logging
_logger.info("=== DÉBUT DU TEST DE CONNEXION ===")
_logger.info(f"API Type: {self.api_type}")
if self.api_type != 'controller':
_logger.error(f"Type d'API incorrect: {self.api_type}")
raise ValidationError(_('This method is only for Controller API'))
# Validate required fields
_logger.info(f"Host: {self.host}")
_logger.info(f"Port: {self.port}")
_logger.info(f"Username: {self.username}")
_logger.info(f"Password: {'*' * len(self.password) if self.password else 'Non défini'}")
_logger.info(f"Verify SSL: {self.verify_ssl}")
if not self.host:
_logger.error("Host manquant")
raise ValidationError(_('Host is required for Controller API'))
if not self.port:
_logger.error("Port manquant")
raise ValidationError(_('Port is required for Controller API'))
if not self.username:
_logger.error("Username manquant")
raise ValidationError(_('Username is required for Controller API'))
if not self.password:
_logger.error("Password manquant")
raise ValidationError(_('Password is required for Controller API'))
# Prepare URL
base_url = f"https://{self.host}:{self.port}"
# Essayer différents endpoints d'authentification selon les versions de l'API UniFi
# Certaines versions utilisent /api/login, d'autres /api/auth/login
login_endpoints = [
"/api/login", # Endpoint standard
"/api/auth/login", # Endpoint alternatif
"/v2/api/login", # Endpoint pour les versions plus récentes
"/v2/api/auth/login" # Autre endpoint possible
]
login_endpoint = login_endpoints[0] # Commencer par le premier endpoint
url = f"{base_url}{login_endpoint}"
_logger.info(f"URL de connexion: {url}")
_logger.info(f"Autres endpoints disponibles: {login_endpoints[1:]}")
# Prepare request data
login_data = {
'username': self.username,
'password': self.password,
'remember': True
}
_logger.info(f"Données de connexion: {{'username': '{self.username}', 'password': '****', 'remember': True}}")
# Disable SSL warnings if verify_ssl is False
if not self.verify_ssl:
_logger.info("Désactivation des avertissements SSL")
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
# Prepare SSL verification
verify = self.verify_ssl
if verify and self.ssl_cert_path:
verify = self.ssl_cert_path
_logger.info(f"Utilisation du certificat SSL: {self.ssl_cert_path}")
try:
# Make the request
_logger.info("Envoi de la requête de connexion...")
# Essayer chaque endpoint jusqu'à ce qu'un fonctionne
response = None
success = False
for endpoint in login_endpoints:
try:
current_url = f"{base_url}{endpoint}"
_logger.info(f"Essai avec l'endpoint: {endpoint}")
# Essayer avec différents formats de données
# Certaines versions attendent un JSON, d'autres des données de formulaire
for content_type, data in [
("json", login_data), # Format JSON standard
("data", login_data) # Format de données de formulaire
]:
try:
_logger.info(f"Essai avec le format de données: {content_type}")
# Construire les arguments de la requête
kwargs = {
content_type: login_data,
"verify": verify,
"timeout": self.timeout
}
# Ajouter des headers spécifiques
headers = {
"Content-Type": "application/json",
"Accept": "application/json"
}
kwargs["headers"] = headers
response = requests.post(current_url, **kwargs)
_logger.info(f"Réponse reçue: Status code {response.status_code}")
_logger.debug(f"Contenu de la réponse: {response.text}")
if response.status_code == 200:
_logger.info(f"Connexion réussie avec l'endpoint {endpoint} et le format {content_type}")
success = True
break
except Exception as e:
_logger.warning(f"Échec avec le format {content_type}: {str(e)}")
if success:
break
except Exception as e:
_logger.warning(f"Échec avec l'endpoint {endpoint}: {str(e)}")
if not response:
raise Exception("Tous les endpoints ont échoué")
# Update API log if we have a response
if api_log and response:
_logger.info("Mise à jour du log API")
self._update_api_log(api_log, {
'endpoint': login_endpoint,
'request_body': json.dumps(login_data),
'response_code': response.status_code,
'response_body': response.text,
'end_time': fields.Datetime.now(),
'execution_time': (fields.Datetime.now() - api_log.start_time).total_seconds(),
'status': 'success' if response.status_code == 200 else 'error',
})
# Check response
if response and response.status_code == 200:
_logger.info("Connexion réussie!")
# Store the cookies in the auth session
self._create_auth_session(response.cookies, endpoint=self.last_successful_endpoint)
return {
'status': 'success',
'message': _('Connection successful')
}
elif response:
_logger.error(f"Échec de la connexion: Status code {response.status_code}")
_logger.error(f"Message d'erreur: {response.text}")
return {
'status': 'error',
'message': _('Connection failed with status code %s: %s') % (response.status_code, response.text)
}
except requests.exceptions.ConnectTimeout as timeout_error:
_logger.error(f"Timeout de connexion: {str(timeout_error)}")
return {
'status': 'error',
'message': _('Connection timeout: %s') % str(timeout_error)
}
except requests.exceptions.SSLError as ssl_error:
_logger.error(f"Erreur SSL: {str(ssl_error)}")
return {
'status': 'error',
'message': _('SSL Error: %s') % str(ssl_error)
}
except Exception as e:
_logger.error('Error testing Controller API connection: %s', str(e))
_logger.exception("Détails de l'erreur:")
return {
'status': 'error',
'message': _('Connection test failed: %s') % str(e)
}
finally:
_logger.info("=== FIN DU TEST DE CONNEXION ===")
def _test_site_manager_connection(self, api_log=None):
"""Test the connection to the UniFi Site Manager API
Args:
api_log: Optional API log record to update
Returns:
dict: Dictionary with status and message
"""
self.ensure_one()
if self.api_type != 'site_manager':
raise ValidationError(_('This method is only for Site Manager API'))
# Validate required fields
if not self.api_key:
raise ValidationError(_('API Key is required for Site Manager API'))
# Prepare headers
headers = {
'X-CSRF-Token': 'unifi_site_manager',
'Authorization': f'Bearer {self.api_key}'
}
# Prepare URL
base_url = "https://sitemanager.unifi.ui.com"
endpoint = "/api/sites"
url = f"{base_url}{endpoint}"
# Disable SSL warnings if verify_ssl is False
if not self.verify_ssl:
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
try:
# Make the request
response = requests.get(
url,
headers=headers,
verify=self.verify_ssl,
timeout=self.timeout
)
# Update API log
if api_log:
self._update_api_log(api_log, {
'endpoint': endpoint,
'request_headers': json.dumps(headers),
'response_code': response.status_code,
'response_body': response.text,
'end_time': fields.Datetime.now(),
'execution_time': (fields.Datetime.now() - api_log.start_date).total_seconds(),
'status': 'success' if response.status_code == 200 else 'error',
})
# Check response
if response.status_code == 200:
return {
'status': 'success',
'message': _('Connection successful')
}
else:
return {
'status': 'error',
'message': _('Connection failed with status code %s: %s') % (response.status_code, response.text)
}
except Exception as e:
_logger.error('Error testing Site Manager API connection: %s', str(e))
return {
'status': 'error',
'message': _('Connection test failed: %s') % str(e)
}
def _update_api_log(self, api_log, values):
"""Update API log record with values
Args:
api_log: API log record to update
values: Dictionary of values to update
"""
if not api_log:
return
try:
# Convertir les valeurs au format attendu par le modèle unifi.api.log
update_vals = {}
# Mapper les champs communs
if 'status' in values:
if values['status'] == 'success':
update_vals['success'] = True
elif values['status'] == 'error':
update_vals['success'] = False
# Mapper le message d'erreur
if 'message' in values:
if values.get('status') == 'error':
update_vals['error_message'] = values['message']
# Ajouter l'heure de fin si nécessaire
if 'end_time' not in update_vals and ('status' in values or values.get('message', '').startswith('Success')):
update_vals['end_time'] = fields.Datetime.now()
# Calculer la durée si possible
if 'end_time' in update_vals and api_log.start_time:
duration = (update_vals['end_time'] - api_log.start_time).total_seconds() * 1000
update_vals['duration'] = duration
# Mettre à jour le statut HTTP si fourni
if 'status_code' in values:
update_vals['status_code'] = values['status_code']
# Mettre à jour le corps de la réponse si fourni
if 'response_body' in values:
update_vals['response_body'] = values['response_body']
# Écrire les valeurs mises à jour
api_log.write(update_vals)
except Exception as e:
_logger.error('Error updating API log: %s', str(e))
def _check_auth_session(self):
"""Vérifie si la session d'authentification est valide
Returns:
bool: True si la session est valide, False sinon
"""
self.ensure_one()
_logger.info("Vérification de la session d'authentification")
# Vérifier si une session existe
if not self.auth_session_id:
_logger.warning("Pas de session d'authentification existante")
return False
# Vérifier si la session est expirée
if self.auth_session_id.expiry and self.auth_session_id.expiry < fields.Datetime.now():
_logger.warning(f"Session expirée: {self.auth_session_id.expiry}")
return False
# Mettre à jour la date de dernière utilisation
self.auth_session_id.write({
'last_used': fields.Datetime.now()
})
_logger.info("Session d'authentification valide")
return True
def _get_auth_cookies(self):
"""Récupère les cookies de la session d'authentification
Returns:
dict: Cookies de la session ou False si pas de session valide
"""
self.ensure_one()
# Vérifier si la session est valide
if not self._check_auth_session():
_logger.warning("Tentative de récupération des cookies sans session valide")
# Essayer de se reconnecter
_logger.info("Tentative de reconnexion automatique")
result = self._test_controller_connection()
if result.get('status') != 'success':
_logger.error(f"Échec de la reconnexion: {result.get('message')}")
return False
# Récupérer les cookies de la session
try:
cookie_str = self.auth_session_id.cookie
if not cookie_str:
_logger.error("Chaîne de cookies vide")
return False
# Afficher la chaîne de cookies brute pour débogage
_logger.info(f"Chaîne de cookies brute: {cookie_str}")
# Méthode 1: Extraire les cookies à partir de la chaîne
cookies = {}
# Vérifier si c'est un objet RequestsCookieJar
if cookie_str.startswith('<RequestsCookieJar[') and cookie_str.endswith(']>'):
cookie_content = cookie_str[len('<RequestsCookieJar['):-len(']>')]
cookie_pairs = cookie_content.split(', ')
for pair in cookie_pairs:
if '=' in pair:
# Corriger le format des cookies comme '<Cookie TOKEN=value...>'
if pair.startswith('<Cookie '):
# Extraire le nom du cookie et sa valeur
cookie_info = pair[len('<Cookie '):]
if '=' in cookie_info:
key, value = cookie_info.split('=', 1)
# Nettoyer la valeur si elle se termine par ' for domain>'
if ' for ' in value:
value = value.split(' for ')[0]
cookies[key.strip()] = value.strip()
# Si nous trouvons un cookie TOKEN, c'est un UDM Pro
if key.strip() == 'TOKEN' and not self.auth_session_id.is_udm_pro:
_logger.info("Détection d'un UDM Pro basé sur le cookie TOKEN")
self.auth_session_id.write({'is_udm_pro': True})
else:
key, value = pair.split('=', 1)
cookies[key.strip()] = value.strip()
# Sinon, essayer de parser comme un dictionnaire
elif cookie_str.startswith('{') and cookie_str.endswith('}'):
try:
cookies = ast.literal_eval(cookie_str)
except Exception as e:
_logger.warning(f"Impossible de parser les cookies comme un dictionnaire: {str(e)}")
# Sinon, essayer de parser comme une liste de paires clé=valeur
else:
cookie_pairs = cookie_str.split(';')
for pair in cookie_pairs:
if '=' in pair:
key, value = pair.split('=', 1)
cookies[key.strip()] = value.strip()
# Ajouter des cookies spécifiques pour UniFi
if 'TOKEN' not in cookies and 'unifises' not in cookies:
# Essayer d'extraire le token des headers de la réponse
if self.last_response_headers:
try:
headers_dict = ast.literal_eval(self.last_response_headers)
for header, value in headers_dict.items():
if header.lower() == 'set-cookie':
if 'TOKEN=' in value:
token_part = value.split('TOKEN=')[1].split(';')[0]
cookies['TOKEN'] = token_part
if 'unifises=' in value:
unifises_part = value.split('unifises=')[1].split(';')[0]
cookies['unifises'] = unifises_part
except (ValueError, SyntaxError) as e:
_logger.warning(f"Impossible de parser les headers: {str(e)}")
_logger.info(f"Cookies récupérés: {cookies}")
return cookies
except Exception as e:
_logger.error(f"Erreur lors de la récupération des cookies: {str(e)}")
return False
def _create_auth_session(self, cookies, endpoint=None):
"""Create or update authentication session
Args:
cookies: Session cookies from successful login
endpoint: The endpoint used for authentication
Returns:
unifi.auth.session: Created or updated auth session record
"""
self.ensure_one()
_logger.info("Création/mise à jour de la session d'authentification")
# Déterminer si c'est un UDM Pro en fonction de l'endpoint utilisé
is_udm_pro = False
if endpoint and '/api/auth/login' in endpoint:
is_udm_pro = True
_logger.info("Détection d'un contrôleur UDM Pro basé sur l'endpoint d'authentification")
# Vérifier si un token est présent dans les cookies (caractéristique des UDM Pro)
token = None
if cookies and 'TOKEN' in cookies:
token = cookies.get('TOKEN')
is_udm_pro = True
_logger.info("Détection d'un contrôleur UDM Pro basé sur le cookie TOKEN")
# Check if there's an existing session
if self.auth_session_id:
# Update existing session
_logger.info("Mise à jour de la session existante")
session_vals = {
'cookie': str(cookies),
'endpoint': endpoint,
'is_udm_pro': is_udm_pro,
'expiry': fields.Datetime.now() + timedelta(hours=24), # Default 24h expiry
'last_used': fields.Datetime.now()
}
# Ajouter le token si disponible
if token:
session_vals['token'] = token
self.auth_session_id.write(session_vals)
return self.auth_session_id
else:
# Create new session
session_vals = {
'site_id': self.id,
'auth_type': self.api_type,
'cookie': str(cookies),
'endpoint': endpoint,
'is_udm_pro': is_udm_pro,
'expiry': fields.Datetime.now() + timedelta(hours=24),
'last_used': fields.Datetime.now()
}
# Ajouter le token si disponible
if token:
session_vals['token'] = token
session = self.env['unifi.auth.session'].create(session_vals)
self.auth_session_id = session
return session
def _inverse_api_key(self):
"""Inverse pour le champ api_key
Cette méthode a été simplifiée dans le cadre de la refactorisation.
Elle n'a plus besoin de synchroniser les données avec l'ancien modèle unifi.site.manager
puisque toute la logique a été consolidée dans ce modèle.
"""
# Rien à faire, puisque api_key est maintenant directement géré par le modèle unifi.site
pass
def _inverse_mfa_enabled(self):
"""Inverse pour le champ mfa_enabled
Cette méthode a été simplifiée dans le cadre de la refactorisation.
Elle n'a plus besoin de synchroniser les données avec l'ancien modèle unifi.site.manager
puisque toute la logique a été consolidée dans ce modèle.
"""
# Rien à faire, puisque mfa_enabled est maintenant directement géré par le modèle unifi.site
pass
def _inverse_mfa_token(self):
"""Inverse pour le champ mfa_token
Cette méthode a été simplifiée dans le cadre de la refactorisation.
Elle n'a plus besoin de synchroniser les données avec l'ancien modèle unifi.site.manager
puisque toute la logique a été consolidée dans ce modèle.
"""
# Rien à faire, puisque mfa_token est maintenant directement géré par le modèle unifi.site
pass
def action_test_connection(self):
"""Test the connection to the UniFi API
This method tests the connection to the UniFi API using the appropriate
connection method based on the API type (Controller or Site Manager).
It displays a notification with the result of the connection test.
Returns:
dict: Action dictionary for client notification
"""
self.ensure_one()
try:
# Perform connection test based on API type
if self.api_type == 'controller':
result = self._test_controller_connection()
elif self.api_type == 'site_manager':
result = self._test_site_manager_connection()
else:
raise ValidationError(_('Invalid API type'))
# Check result and display appropriate notification
if result:
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'title': _('Connection Test'),
'message': _('Connection successful!'),
'sticky': False,
'type': 'success',
}
}
else:
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'title': _('Connection Test'),
'message': _('Connection failed. Please check your settings.'),
'sticky': True,
'type': 'danger',
}
}
except Exception as e:
# Handle any exceptions that occur during the connection test
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'title': _('Connection Test'),
'message': _('Error: %s') % str(e),
'sticky': True,
'type': 'danger',
}
}
def action_sync_now(self):
"""Trigger an immediate synchronization"""
self.ensure_one()
# Création d'un job de synchronisation dans le modèle UnifiSyncJob
sync_job = self.env['unifi.sync.job'].create({
'site_id': self.id, # Référence au site actuel
'api_type': self.api_type, # Type d'API (controller ou site_manager)
'sync_type': 'manual', # Type de synchronisation (manuel dans ce cas)
'start_time': fields.Datetime.now(), # Horodatage du début
'state': 'running', # État initial du job (en cours d'exécution)
})
try:
if self.api_type == 'controller':
result = self._sync_controller()
elif self.api_type == 'site_manager':
result = self._sync_site_manager()
else:
raise ValidationError(_('Invalid API type'))
# Update sync job with result
if result:
# Mise à jour des champs dans le modèle UnifiSyncJob pour indiquer le succès
sync_job.write({
'end_time': fields.Datetime.now(), # Horodatage de fin
'state': 'completed', # État: terminé
'status': 'success', # Statut: succès
})
# Update last_sync timestamp
self.write({
'last_sync': fields.Datetime.now(),
'last_update': fields.Datetime.now() if result else self.last_update,
})
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'title': _('Synchronization'),
'message': _('Synchronization completed successfully'),
'sticky': False,
'type': 'success',
}
}
else:
sync_job.write({
'end_time': fields.Datetime.now(),
'status': 'failed',
})
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'title': _('Synchronization'),
'message': _('Synchronization failed'),
'sticky': True,
'type': 'danger',
}
}
except Exception as e:
_logger.error('Error during synchronization: %s', str(e))
# Mise à jour du job de synchronisation avec les détails de l'erreur
# Note: Si le champ 'error' n'existe pas dans le modèle, on l'ignore
vals = {
'end_time': fields.Datetime.now(),
'status': 'failed',
}
# Vérifier si le champ 'error' existe dans le modèle
if 'error' in self.env['unifi.sync.job']._fields:
vals['error'] = str(e)
sync_job.write(vals)
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'title': _('Synchronization'),
'message': _('Synchronization failed: %s') % str(e),
'sticky': True,
'type': 'danger',
}
}
def _inverse_verify_ssl(self):
"""Inverse pour le champ verify_ssl
Cette méthode a été simplifiée dans le cadre de la refactorisation.
Elle n'a plus besoin de synchroniser les données avec les anciens modèles
unifi.site.controller et unifi.site.manager puisque toute la logique
a été consolidée dans ce modèle.
"""
# Rien à faire, puisque verify_ssl est maintenant directement géré par le modèle unifi.site
pass
# Configuration data
last_update = fields.Datetime(
string='Last Update',
default=fields.Datetime.now
)
raw_data = fields.Text(
string='Raw Data',
help='Raw configuration data in JSON format'
)
raw_data_json = fields.Text(
string='Données brutes (JSON)',
compute='_compute_raw_data_json',
help='Données brutes du site au format JSON formaté'
)
@api.depends('raw_data')
def _compute_raw_data_json(self):
for record in self:
record.raw_data_json = self.format_raw_data_json(record.raw_data)
# Synchronization settings
sync_interval = fields.Integer(
string='Sync Interval (minutes)',
default=60,
help='Interval in minutes between automatic synchronizations'
)
auto_sync = fields.Boolean(
string='Auto Sync',
default=True,
help='Enable automatic synchronization'
)
last_sync = fields.Datetime(
string='Last Sync',
readonly=True,
help='Date and time of the last synchronization'
)
# Authentication session
auth_session_id = fields.Many2one(
comodel_name='unifi.auth.session',
string='Authentication Session',
ondelete='cascade',
help='Current authentication session'
)
# Related records - Will be updated to point to new models
network_ids = fields.One2many(
comodel_name='unifi.network',
inverse_name='site_id',
string='Networks',
help='Networks in this site'
)
vlan_ids = fields.One2many(
comodel_name='unifi.vlan',
inverse_name='site_id',
string='VLANs',
help='VLANs in this site'
)
device_ids = fields.One2many(
comodel_name='unifi.device',
inverse_name='site_id',
string='Devices',
help='Devices in this site'
)
user_ids = fields.One2many(
comodel_name='unifi.user',
inverse_name='site_id',
string='Users',
help='Users in this site'
)
firewall_rule_ids = fields.One2many(
comodel_name='unifi.firewall.rule',
inverse_name='site_id',
string='Firewall Rules',
help='Firewall rules for this site'
)
port_forward_ids = fields.One2many(
comodel_name='unifi.port.forward',
inverse_name='site_id',
string='Port Forwards',
help='Port forwarding rules for this site'
)
dns_config_ids = fields.One2many(
comodel_name='unifi.dns.config',
inverse_name='site_id',
string='DNS Configurations',
help='DNS configurations for this site'
)
routing_config_ids = fields.One2many(
comodel_name='unifi.routing.config',
inverse_name='site_id',
string='Routing Configurations',
help='Routing configurations for this site'
)
# Relations with system models
system_info_id = fields.Many2one(
comodel_name='unifi.system.info',
string='Primary System Info',
ondelete='cascade',
help='Primary system information snapshot',
required=False
)
# Relations avec les appareils
device_ids = fields.One2many(
comodel_name='unifi.device',
inverse_name='site_id',
string='Appareils',
help='Appareils UniFi associés à ce site'
)
# API logs
api_log_ids = fields.One2many(
comodel_name='unifi.api.log',
inverse_name='site_id',
string='API Logs',
help='Logs of API calls'
)
# Sync jobs
sync_job_ids = fields.One2many(
comodel_name='unifi.sync.job',
inverse_name='site_id',
string='Sync Jobs',
help='Synchronization jobs'
)
# Computed fields
network_count = fields.Integer(
compute='_compute_counts',
string='Network Count',
store=True,
help='Total number of networks in this site'
)
device_count = fields.Integer(
compute='_compute_counts',
string='Device Count',
store=True,
help='Total number of devices in this site'
)
user_count = fields.Integer(
compute='_compute_counts',
string='User Count',
store=True,
help='Number of users in this site'
)
firewall_rule_count = fields.Integer(
compute='_compute_counts',
string='Firewall Rule Count',
store=True,
help='Number of firewall rules in this site'
)
client_count = fields.Integer(
compute='_compute_client_count',
string='Connected Clients',
store=True,
help='Number of currently connected clients'
)
# Dashboard Metrics - Updated to point to new models
dashboard_metric_ids = fields.One2many(
comodel_name='unifi.dashboard.metric',
inverse_name='site_id',
string='Real-time Dashboard Metrics',
help='Real-time metrics for this site'
)
dashboard_stat_ids = fields.One2many(
comodel_name='unifi.dashboard.stat',
inverse_name='site_id',
string='Historical Statistics',
help='Historical statistics for this site'
)
_sql_constraints = [
('name_uniq', 'unique(name)', 'Site name must be unique!'),
]
# Field dependencies and constraints
@api.constrains('api_type')
def _check_api_fields(self):
"""Validate that required fields are set based on API type
This method delegates to the appropriate API-specific model for validation.
"""
for site in self:
if site.api_type == 'controller':
# Check controller-specific required fields
if not site.host:
raise ValidationError(_('Host is required for Controller API'))
if not site.port:
raise ValidationError(_('Port is required for Controller API'))
if not site.username:
raise ValidationError(_('Username is required for Controller API'))
if not site.password:
raise ValidationError(_('Password is required for Controller API'))
elif site.api_type == 'site_manager':
# Check site manager-specific required fields
if not site.api_key:
raise ValidationError(_('API Key is required for Site Manager API'))
@api.onchange('api_type')
def _onchange_api_type(self):
"""Clear fields that are not relevant to the selected API type
This method implements field clearing logic directly based on the API type.
Previously this was delegated to specific models, but now it's integrated here
as part of the refactoring.
"""
if self.api_type == 'controller':
# Clear site_manager-specific fields
self.api_key = False
self.mfa_enabled = False
self.mfa_token = False
elif self.api_type == 'site_manager':
# Clear controller-specific fields
self.username = False
self.password = False
@api.depends('network_ids', 'device_ids', 'user_ids', 'firewall_rule_ids', 'vlan_ids', 'port_forward_ids', 'routing_config_ids', 'wifi_ids', 'dns_ids', 'system_info_ids', 'vpn_ids')
def _compute_counts(self):
"""Compute counts for related records
This method calculates the number of networks, devices, users, firewall rules,
VLANs, port forwards, routing configurations, and WiFi networks associated with this site.
It's triggered automatically when any of these related records are added or removed.
"""
for site in self:
# Safely get counts, handling potential errors
try:
site.network_count = len(site.network_ids) if site.network_ids else 0
site.device_count = len(site.device_ids) if site.device_ids else 0
site.user_count = len(site.user_ids) if site.user_ids else 0
site.firewall_rule_count = len(site.firewall_rule_ids) if site.firewall_rule_ids else 0
site.vlan_count = len(site.vlan_ids) if site.vlan_ids else 0
site.port_forward_count = len(site.port_forward_ids) if site.port_forward_ids else 0
site.routing_config_count = len(site.routing_config_ids) if site.routing_config_ids else 0
site.wifi_count = len(site.wifi_ids) if site.wifi_ids else 0
site.dns_count = len(site.dns_ids) if site.dns_ids else 0
site.system_info_count = len(site.system_info_ids) if site.system_info_ids else 0
site.vpn_count = len(site.vpn_ids) if site.vpn_ids else 0
except Exception as e:
_logger.error('Error computing counts for site %s: %s', site.name, str(e))
# Set default values in case of error
site.network_count = site.device_count = site.user_count = site.firewall_rule_count = site.vlan_count = site.port_forward_count = site.routing_config_count = site.wifi_count = site.dns_count = site.system_info_count = site.vpn_count = 0
@api.depends('user_ids')
def _compute_client_count(self):
"""Compute the number of connected clients
This method counts only users that are currently connected to the network.
It relies on the 'is_connected' flag on user records.
"""
for site in self:
try:
if site.user_ids:
# Filter users that have is_connected=True
site.client_count = len(site.user_ids.filtered(lambda u: u.is_connected if hasattr(u, 'is_connected') else False))
else:
site.client_count = 0
except Exception as e:
_logger.error('Error computing client count for site %s: %s', site.name, str(e))
site.client_count = 0
# Action methods
def action_view_networks(self):
"""Open the networks view filtered for this site"""
self.ensure_one()
return {
'name': _('Networks'),
'view_mode': 'list,form',
'res_model': 'unifi.network',
'domain': [('site_id', '=', self.id)],
'type': 'ir.actions.act_window',
'context': {'default_site_id': self.id}
}
def action_view_devices(self):
"""Open the devices view filtered for this site"""
self.ensure_one()
return {
'name': _('Devices'),
'view_mode': 'list,form',
'res_model': 'unifi.device',
'domain': [('site_id', '=', self.id)],
'type': 'ir.actions.act_window',
'context': {'default_site_id': self.id}
}
def action_view_users(self):
"""Open the users view filtered for this site"""
self.ensure_one()
return {
'name': _('Users'),
'view_mode': 'list,form',
'res_model': 'unifi.user',
'domain': [('site_id', '=', self.id)],
'type': 'ir.actions.act_window',
'context': {'default_site_id': self.id}
}
def action_view_firewall_rules(self):
"""Open the firewall rules view filtered for this site"""
self.ensure_one()
return {
'name': _('Firewall Rules'),
'view_mode': 'list,form',
'res_model': 'unifi.firewall.rule',
'domain': [('site_id', '=', self.id)],
'type': 'ir.actions.act_window',
'context': {'default_site_id': self.id}
}
def action_view_api_logs(self):
"""Open the API logs view filtered for this site"""
self.ensure_one()
return {
'name': _('API Logs'),
'view_mode': 'list,form',
'res_model': 'unifi.api.log',
'domain': [('site_id', '=', self.id)],
'type': 'ir.actions.act_window',
'context': {'default_site_id': self.id}
}
def action_view_sync_jobs(self):
"""Open the sync jobs view filtered for this site"""
self.ensure_one()
return {
'name': _('Sync Jobs'),
'view_mode': 'list,form',
'res_model': 'unifi.sync.job',
'domain': [('site_id', '=', self.id)],
'type': 'ir.actions.act_window',
'context': {'default_site_id': self.id}
}
def action_view_vlans(self):
"""Open the VLANs view filtered for this site"""
self.ensure_one()
return {
'name': _('VLANs'),
'view_mode': 'list,form',
'res_model': 'unifi.vlan',
'domain': [('site_id', '=', self.id)],
'type': 'ir.actions.act_window',
'context': {'default_site_id': self.id}
}
def action_view_port_forwards(self):
"""Open the port forwards view filtered for this site"""
self.ensure_one()
return {
'name': _('Port Forwards'),
'view_mode': 'list,form',
'res_model': 'unifi.port_forward',
'domain': [('site_id', '=', self.id)],
'type': 'ir.actions.act_window',
'context': {'default_site_id': self.id}
}
def action_view_system_info(self):
"""Open the system info view filtered for this site"""
self.ensure_one()
return {
'name': _('System Info'),
'view_mode': 'list,form',
'res_model': 'unifi.system.info',
'domain': [('site_id', '=', self.id)],
'type': 'ir.actions.act_window',
'context': {'default_site_id': self.id}
}
def action_view_dns(self):
"""Open the DNS entries view filtered for this site"""
self.ensure_one()
return {
'name': _('DNS Entries'),
'view_mode': 'list,form',
'res_model': 'unifi.dns',
'domain': [('site_id', '=', self.id)],
'type': 'ir.actions.act_window',
'context': {'default_site_id': self.id}
}
def action_view_vpn(self):
"""Open the VPN configurations view filtered for this site"""
self.ensure_one()
return {
'name': _('VPN Configurations'),
'view_mode': 'list,form',
'res_model': 'unifi.vpn',
'domain': [('site_id', '=', self.id)],
'type': 'ir.actions.act_window',
'context': {'default_site_id': self.id}
}
def action_configure_controller(self):
"""Open the controller configuration view for this site
This method checks if a controller configuration already exists for this site.
If it does, it opens the existing configuration in form view.
If not, it creates a new configuration and opens it in form view.
"""
self.ensure_one()
# Since we've consolidated all API functionality into the main model,
# we just need to open the current site record in form view
return {
'name': _('Controller API Configuration'),
'view_mode': 'form',
'res_model': 'unifi.site',
'res_id': self.id,
'type': 'ir.actions.act_window',
'target': 'current',
}
def action_configure_site_manager(self):
"""Open the site configuration form in edit mode
This method has been updated as part of the refactorization.
Since we've consolidated all API-related functionality into the main
unifi.site model, we simply open this record in form view for editing.
"""
self.ensure_one()
# Ensure API type is set to site_manager
if self.api_type != 'site_manager':
self.api_type = 'site_manager'
# Open this site record in form view
return {
'name': _('Site Manager API Configuration'),
'view_mode': 'form',
'res_model': 'unifi.site',
'res_id': self.id,
'type': 'ir.actions.act_window',
'target': 'current',
}
return True
def action_sync_networks(self):
"""Synchronize only networks for this site"""
self.ensure_one()
try:
# Vérifier si nous avons une session d'authentification valide
if not self.auth_session_id or not self._check_auth_session():
_logger.warning("Pas de session d'authentification valide, tentative de connexion automatique")
# Tenter de se connecter au contrôleur UniFi
connection_result = self._test_controller_connection()
_logger.info(f"Résultat de la connexion: {connection_result}")
if connection_result.get('status') != 'success':
_logger.error(f"Impossible de se connecter au contrôleur UniFi: {connection_result.get('message')}")
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'title': _('Connection Error'),
'message': _(f"Unable to connect to UniFi Controller: {connection_result.get('message')}"),
'sticky': True,
'type': 'danger',
}
}
_logger.info("Connexion au contrôleur UniFi réussie, poursuite de la synchronisation")
# Récupérer les données des réseaux depuis l'API UniFi
_logger.info("Récupération des données des réseaux depuis l'API UniFi")
# Déterminer quelle méthode utiliser en fonction du type d'API
if self.api_type == 'controller':
networks_data = self._get_controller_network_data()
elif self.api_type == 'site_manager':
networks_data = self._get_site_manager_network_data()
else:
networks_data = None
_logger.error(f"Type d'API non pris en charge: {self.api_type}")
if not networks_data:
_logger.error("Impossible de récupérer les données des réseaux")
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'title': _('Network Synchronization'),
'message': _('Failed to retrieve network data from UniFi API'),
'sticky': True,
'type': 'danger',
}
}
# Analyser les données des réseaux
_logger.info(f"Données de réseaux reçues: {type(networks_data)}")
# Vérifier la structure des données
if isinstance(networks_data, list):
networks_list = networks_data
elif isinstance(networks_data, dict):
# La plupart des API UniFi renvoient les données dans une clé 'data'
networks_list = networks_data.get('data', [])
if not networks_list and 'networks' in networks_data:
networks_list = networks_data.get('networks', [])
else:
networks_list = []
_logger.info(f"Nombre de réseaux trouvés dans l'API: {len(networks_list)}")
# Créer ou mettre à jour les réseaux
processed_networks = self.env['unifi.network']
for network_data in networks_list:
network_id = network_data.get('_id') or network_data.get('id')
if not network_id:
_logger.warning("Réseau sans identifiant ignoré")
continue
# Rechercher un réseau existant par ID
network = self.env['unifi.network'].search([
('site_id', '=', self.id),
('network_id', '=', network_id)
], limit=1)
if network:
_logger.info(f"Mise à jour du réseau existant: {network.name} (ID: {network_id})")
else:
_logger.info(f"Création d'un nouveau réseau avec ID: {network_id}")
# Créer ou mettre à jour le réseau
network = self.env['unifi.network'].create_or_update_from_data(self, network_data)
if network:
processed_networks += network
# Afficher un message de succès et retourner la vue des réseaux
# D'abord afficher la notification
self.env['bus.bus']._sendone(
self.env.user.partner_id,
'web_client.action',
{
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'title': _('Network Synchronization'),
'message': _(f'{len(networks_list)} réseaux trouvés, {len(processed_networks)} créés ou mis à jour'),
'sticky': False,
'type': 'success',
}
}
)
# Ensuite retourner la vue des réseaux
return {
'type': 'ir.actions.act_window',
'name': _('Networks'),
'res_model': 'unifi.network',
'domain': [('site_id', '=', self.id)],
'view_mode': 'list,form',
'target': 'current',
}
except Exception as e:
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'title': _('Network Synchronization'),
'message': _('Error: %s') % str(e),
'sticky': True,
'type': 'danger',
}
}
def action_sync_devices(self):
"""Synchronize only devices for this site"""
self.ensure_one()
_logger.info("=== DÉBUT DE LA SYNCHRONISATION DES APPAREILS (action_sync_devices) ===")
try:
# Vérifier si nous avons une session d'authentification valide
if not self.auth_session_id or not self._check_auth_session():
_logger.warning("Pas de session d'authentification valide, tentative de connexion automatique")
# Tenter de se connecter au contrôleur UniFi
connection_result = self._test_controller_connection()
_logger.info(f"Résultat de la connexion: {connection_result}")
if connection_result.get('status') != 'success':
_logger.error(f"Impossible de se connecter au contrôleur UniFi: {connection_result.get('message')}")
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'title': _('Connection Error'),
'message': _(f"Unable to connect to UniFi Controller: {connection_result.get('message')}"),
'sticky': True,
'type': 'danger',
}
}
_logger.info("Connexion au contrôleur UniFi réussie, poursuite de la synchronisation")
# Récupérer les données des appareils depuis l'API UniFi
_logger.info("Récupération des données des appareils depuis l'API UniFi")
device_data = self._get_device_data()
if not device_data:
_logger.error("Impossible de récupérer les données des appareils")
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'title': _('Device Synchronization'),
'message': _('Failed to retrieve device data from UniFi API'),
'sticky': True,
'type': 'danger',
}
}
# Analyser les données des appareils
_logger.info(f"Données d'appareils reçues: {type(device_data)}")
# Vérifier la structure des données
if isinstance(device_data, dict):
_logger.info(f"Clés dans les données: {list(device_data.keys())}")
# La plupart des API UniFi renvoient les données dans une clé 'data'
devices_list = device_data.get('data', [])
if not devices_list and 'devices' in device_data:
devices_list = device_data.get('devices', [])
_logger.info(f"Nombre d'appareils trouvés dans l'API: {len(devices_list)}")
if devices_list:
# Afficher des informations sur le premier appareil pour débogage
first_device = devices_list[0]
_logger.info(f"Premier appareil: {first_device.get('name', 'Sans nom')}")
_logger.info(f"MAC: {first_device.get('mac', 'N/A')}")
_logger.info(f"Modèle: {first_device.get('model', 'N/A')}")
# Créer ou mettre à jour les appareils à partir des données de l'API
existing_devices = self.env['unifi.device'].search([('site_id', '=', self.id)])
_logger.info(f"Nombre d'appareils existants dans Odoo: {len(existing_devices)}")
# Garder une trace des appareils traités
processed_devices = self.env['unifi.device']
# Créer ou mettre à jour les appareils
for device_data in devices_list:
mac = device_data.get('mac')
if not mac:
_logger.warning("Appareil sans adresse MAC ignoré")
continue
# Rechercher un appareil existant par MAC
device = self.env['unifi.device'].search([
('site_id', '=', self.id),
('mac_address', '=', mac)
], limit=1)
if device:
_logger.info(f"Mise à jour de l'appareil existant: {device.name} (MAC: {mac})")
else:
_logger.info(f"Création d'un nouvel appareil avec MAC: {mac}")
# Créer ou mettre à jour l'appareil
device = self.env['unifi.device'].create_from_api_data(self, device_data)
if device:
processed_devices += device
# Afficher un message de succès et retourner la vue des appareils
# D'abord afficher la notification
self.env['bus.bus']._sendone(
self.env.user.partner_id,
'web_client.action',
{
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'title': _('Device Synchronization'),
'message': _(f'{len(devices_list)} appareils trouvés, {len(processed_devices)} créés ou mis à jour'),
'sticky': False,
'type': 'success',
}
}
)
# Ensuite retourner la vue des appareils
return {
'type': 'ir.actions.act_window',
'name': _('Devices'),
'res_model': 'unifi.device',
'domain': [('site_id', '=', self.id)],
'view_mode': 'list,form',
'target': 'current',
}
else:
_logger.warning("Aucun appareil trouvé dans les données de l'API")
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'title': _('Device Synchronization'),
'message': _('No devices found in the UniFi API data'),
'sticky': False,
'type': 'warning',
}
}
else:
_logger.error(f"Format de données inattendu: {type(device_data)}")
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'title': _('Device Synchronization'),
'message': _('Unexpected data format received from UniFi API'),
'sticky': True,
'type': 'danger',
}
}
except Exception as e:
_logger.exception(f"Erreur lors de la synchronisation des appareils: {str(e)}")
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'title': _('Device Synchronization'),
'message': _('Error: %s') % str(e),
'sticky': True,
'type': 'danger',
}
}
def action_sync_dns(self):
"""Synchronize DNS entries from UniFi
This method retrieves DNS data from the UniFi API and creates or updates
DNS records in Odoo. It handles both success and error notifications.
"""
self.ensure_one()
try:
# Récupérer les données DNS depuis l'API UniFi en fonction du type d'API
if self.api_type == 'controller':
dns_data = self._get_controller_get_dns_data()
else:
dns_data = self._get_site_manager_dns_data()
if dns_data:
# Vérifier le format des données
if isinstance(dns_data, list):
# Initialiser la liste des entrées DNS traitées
processed_dns = self.env['unifi.dns']
dns_list = dns_data
# Traiter chaque entrée DNS
for dns_item in dns_list:
# Créer ou mettre à jour l'entrée DNS
dns = self.env['unifi.dns'].create_or_update_from_data(self, dns_item)
if dns:
processed_dns += dns
# Afficher un message de succès
self.env['bus.bus']._sendone(
self.env.user.partner_id,
'web_client.action',
{
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'title': _('DNS Synchronization'),
'message': _(f'{len(dns_list)} entrées DNS trouvées, {len(processed_dns)} créées ou mises à jour'),
'sticky': False,
'type': 'success',
}
}
)
# Retourner une action pour afficher la liste des entrées DNS
return {
'name': _('DNS Entries'),
'type': 'ir.actions.act_window',
'res_model': 'unifi.dns',
'view_mode': 'list,form',
'domain': [('site_id', '=', self.id)],
'context': {'default_site_id': self.id},
}
else:
# Format de données incorrect
raise UserError(_('Invalid data format received from UniFi API'))
else:
# Aucune donnée reçue, mais ce n'est pas une erreur
# Afficher un message d'information
self.env['bus.bus']._sendone(
self.env.user.partner_id,
'web_client.action',
{
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'title': _('DNS Synchronization'),
'message': _('Aucune entrée DNS trouvée dans le contrôleur UniFi.'),
'sticky': False,
'type': 'info',
}
}
)
# Retourner une action pour afficher la liste des entrées DNS (même si vide)
return {
'name': _('DNS Entries'),
'type': 'ir.actions.act_window',
'res_model': 'unifi.dns',
'view_mode': 'list,form',
'domain': [('site_id', '=', self.id)],
'context': {'default_site_id': self.id},
}
except Exception as e:
# Gérer les erreurs
_logger.error(f"Error synchronizing DNS entries: {str(e)}")
raise UserError(_(f"Error synchronizing DNS entries: {str(e)}"))
def action_sync_system_info(self):
"""Synchronize system information from UniFi
This method retrieves system information from the UniFi API and creates or updates
system info records in Odoo. It handles both success and error notifications.
"""
self.ensure_one()
try:
# Récupérer les données système depuis l'API UniFi
system_info_data = self._get_site_manager_system_info_data()
if system_info_data:
# Vérifier le format des données
if isinstance(system_info_data, list) or isinstance(system_info_data, dict):
# Convertir en liste si c'est un dictionnaire
if isinstance(system_info_data, dict):
system_info_list = [system_info_data]
else:
system_info_list = system_info_data
# Initialiser la liste des informations système traitées
processed_system_info = self.env['unifi.system.info']
# Traiter chaque information système
for system_info_item in system_info_list:
# Créer ou mettre à jour l'information système
system_info = self.env['unifi.system.info'].create_or_update_from_data(self, system_info_item)
if system_info:
processed_system_info += system_info
# Afficher un message de succès
self.env['bus.bus']._sendone(
self.env.user.partner_id,
'web_client.action',
{
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'title': _('System Info Synchronization'),
'message': _(f'{len(system_info_list)} informations système trouvées, {len(processed_system_info)} créées ou mises à jour'),
'sticky': False,
'type': 'success',
}
}
)
# Retourner une action pour afficher la liste des informations système
return {
'name': _('System Info'),
'type': 'ir.actions.act_window',
'res_model': 'unifi.system.info',
'view_mode': 'list,form',
'domain': [('site_id', '=', self.id)],
'context': {'default_site_id': self.id},
}
else:
# Format de données incorrect
raise UserError(_('Invalid data format received from UniFi API'))
else:
# Aucune donnée reçue
raise UserError(_('No system info data received from UniFi API'))
except Exception as e:
# Gérer les erreurs
_logger.error(f"Error synchronizing system info: {str(e)}")
raise UserError(_(f"Error synchronizing system info: {str(e)}"))
def action_sync_wifi(self):
"""Synchronize WiFi networks from UniFi
This method retrieves WiFi network data from the UniFi API and creates or updates
WiFi network records in Odoo. It handles both success and error notifications.
"""
self.ensure_one()
try:
# Récupérer les données WiFi depuis l'API UniFi
wifi_data = self._get_controller_wifi_data()
if wifi_data:
# Vérifier le format des données
if isinstance(wifi_data, list):
# Initialiser la liste des réseaux WiFi traités
processed_wifi = self.env['unifi.wifi']
wifi_list = wifi_data
# Traiter chaque réseau WiFi
for wifi_item in wifi_list:
# Créer ou mettre à jour le réseau WiFi
wifi = self.env['unifi.wifi'].create_or_update_from_data(self, wifi_item)
if wifi:
processed_wifi += wifi
# Afficher un message de succès
self.env['bus.bus']._sendone(
self.env.user.partner_id,
'web_client.action',
{
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'title': _('WiFi Network Synchronization'),
'message': _(f'{len(wifi_list)} réseaux WiFi trouvés, {len(processed_wifi)} créés ou mis à jour'),
'sticky': False,
'type': 'success',
}
}
)
# Retourner une action pour afficher la liste des réseaux WiFi
return {
'name': _('WiFi Networks'),
'type': 'ir.actions.act_window',
'res_model': 'unifi.wifi',
'view_mode': 'list,form',
'domain': [('site_id', '=', self.id)],
'context': {'default_site_id': self.id},
}
else:
# Format de données incorrect
raise UserError(_('Invalid data format received from UniFi API'))
else:
# Aucune donnée reçue
raise UserError(_('No WiFi network data received from UniFi API'))
except Exception as e:
# Gérer les erreurs
_logger.error(f"Error synchronizing WiFi networks: {str(e)}")
raise UserError(_(f"Error synchronizing WiFi networks: {str(e)}"))
def action_sync_users(self):
"""Synchronize only users for this site"""
self.ensure_one()
try:
# Récupérer les données des utilisateurs depuis l'API UniFi
user_data = self._get_controller_user_data()
if user_data:
# Vérifier le format des données
if isinstance(user_data, list):
# Initialiser la liste des utilisateurs traités
processed_users = self.env['unifi.user']
users_list = user_data
# Traiter chaque utilisateur
for user_item in users_list:
# Créer ou mettre à jour l'utilisateur
user = self.env['unifi.user'].create_or_update_from_data(self, user_item)
if user:
processed_users += user
# Afficher un message de succès et retourner la vue des utilisateurs
# D'abord afficher la notification
self.env['bus.bus']._sendone(
self.env.user.partner_id,
'web_client.action',
{
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'title': _('User Synchronization'),
'message': _(f'{len(users_list)} utilisateurs trouvés, {len(processed_users)} créés ou mis à jour'),
'sticky': False,
'type': 'success',
}
}
)
# Ensuite retourner la vue des utilisateurs
return {
'type': 'ir.actions.act_window',
'name': _('Users'),
'res_model': 'unifi.user',
'domain': [('site_id', '=', self.id)],
'view_mode': 'list,form',
'target': 'current',
}
else:
_logger.warning("Aucun utilisateur trouvé dans les données de l'API")
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'title': _('User Synchronization'),
'message': _('No users found in the UniFi API data'),
'sticky': False,
'type': 'warning',
}
}
else:
_logger.error(f"Format de données inattendu: {type(user_data)}")
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'title': _('User Synchronization'),
'message': _('Unexpected data format received from UniFi API'),
'sticky': True,
'type': 'danger',
}
}
except Exception as e:
_logger.exception(f"Erreur lors de la synchronisation des utilisateurs: {str(e)}")
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'title': _('User Synchronization'),
'message': _('Error: %s') % str(e),
'sticky': True,
'type': 'danger',
}
}
def action_sync_vlans(self):
"""Synchronize only VLANs for this site"""
self.ensure_one()
try:
# Utiliser la méthode de synchronisation du modèle unifi.vlan
vlans = self.env['unifi.vlan'].sync_vlans_from_api(self)
# Afficher un message de succès
# Vérifier si vlans est une liste ou un booléen
vlan_count = len(vlans) if isinstance(vlans, list) else 0
self.env['bus.bus']._sendone(
self.env.user.partner_id,
'web_client.action',
{
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'title': _('VLAN Synchronization'),
'message': _(f'{vlan_count} VLANs synchronisés avec succès!'),
'sticky': False,
'type': 'success',
}
}
)
# Retourner la vue des VLANs
return {
'type': 'ir.actions.act_window',
'name': _('VLANs'),
'res_model': 'unifi.vlan',
'domain': [('site_id', '=', self.id)],
'view_mode': 'list,form',
'target': 'current',
}
except Exception as e:
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'title': _('VLAN Synchronization'),
'message': _('Error: %s') % str(e),
'sticky': True,
'type': 'danger',
}
}
def action_sync_firewall_rules(self):
"""Synchronize only firewall rules for this site"""
self.ensure_one()
try:
# Utiliser la méthode de synchronisation du modèle unifi.firewall.rule
rules = self.env['unifi.firewall.rule'].sync_firewall_rules(self)
# Afficher un message de succès
# La méthode sync_firewall_rules retourne True/False et non une liste
# Récupérer le nombre de règles de pare-feu pour ce site
rule_count = self.env['unifi.firewall.rule'].search_count([('site_id', '=', self.id)])
self.env['bus.bus']._sendone(
self.env.user.partner_id,
'web_client.action',
{
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'title': _('Firewall Rule Synchronization'),
'message': _(f'{rule_count} règles de pare-feu synchronisées avec succès!'),
'sticky': False,
'type': 'success',
}
}
)
# Retourner la vue des règles de pare-feu
return {
'type': 'ir.actions.act_window',
'name': _('Firewall Rules'),
'res_model': 'unifi.firewall.rule',
'domain': [('site_id', '=', self.id)],
'view_mode': 'list,form',
'target': 'current',
}
except Exception as e:
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'title': _('Firewall Rule Synchronization'),
'message': _('Error: %s') % str(e),
'sticky': True,
'type': 'danger',
}
}
def action_sync_port_forwards(self):
"""Synchronize only port forwards for this site"""
self.ensure_one()
try:
# Utiliser la méthode de synchronisation du modèle unifi.port.forward
port_forwards = self.env['unifi.port.forward'].sync_port_forwards(self)
# Afficher un message de succès
# La méthode sync_port_forwards retourne True/False et non une liste
# Récupérer le nombre de redirections de port pour ce site
port_forward_count = self.env['unifi.port.forward'].search_count([('site_id', '=', self.id)])
self.env['bus.bus']._sendone(
self.env.user.partner_id,
'web_client.action',
{
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'title': _('Port Forward Synchronization'),
'message': _(f'{port_forward_count} redirections de port synchronisées avec succès!'),
'sticky': False,
'type': 'success',
}
}
)
# Retourner la vue des redirections de port
return {
'type': 'ir.actions.act_window',
'name': _('Port Forwards'),
'res_model': 'unifi.port.forward',
'domain': [('site_id', '=', self.id)],
'view_mode': 'list,form',
'target': 'current',
}
except Exception as e:
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'title': _('Port Forward Synchronization'),
'message': _('Error: %s') % str(e),
'sticky': True,
'type': 'danger',
}
}
def action_sync_routing(self):
"""Synchronize only routing configuration for this site"""
self.ensure_one()
try:
# Utiliser la méthode de synchronisation du modèle unifi.routing.config
routing_configs = self.env['unifi.routing.config'].search([('site_id', '=', self.id)])
for config in routing_configs:
config.sync_from_unifi()
# Synchroniser également les routes individuelles
routes = self.env['unifi.routing'].search([('site_id', '=', self.id)])
for route in routes:
route.sync_from_unifi()
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'title': _('Routing Configuration Synchronization'),
'message': _('Routing configuration synchronized successfully!'),
'sticky': False,
'type': 'success',
}
}
except Exception as e:
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'title': _('Routing Configuration Synchronization'),
'message': _('Error: %s') % str(e),
'sticky': True,
'type': 'danger',
}
}
# API-specific methods
def _sync_controller(self):
"""Synchronize data with the Controller API
This method orchestrates the synchronization process with the UniFi Controller API.
It retrieves data for all supported entity types (devices, networks, VLANs, users,
firewall rules, port forwards, and system info) and updates the corresponding
records in the Odoo database.
Returns:
bool: True if synchronization was successful, False otherwise
"""
# Initialiser sync_job en dehors du bloc try pour éviter les erreurs de lint
sync_job = None
try:
# Create a sync job
sync_job = self.env['unifi.sync.job'].create({
'site_id': self.id,
'start_time': fields.Datetime.now(),
'state': 'running',
'sync_type': 'manual',
'api_type': 'controller',
})
# Puisque nous avons fusionné les modèles, le site lui-même est le contrôleur
# Nous pouvons donc utiliser directement les méthodes du site
# Authenticate with the Controller API
if not self._test_controller_connection():
if sync_job:
sync_job.write({
'end_time': fields.Datetime.now(),
'state': 'failed',
'message': 'Authentication failed',
})
return False
success = True
sync_messages = []
# Synchronize system info
try:
# Utiliser une méthode du site pour récupérer les informations système
system_info_data = self._get_system_info_data()
if system_info_data:
# Process and store system info data
# TODO: Implement system info synchronization
sync_messages.append('System info synchronized')
else:
sync_messages.append('Failed to retrieve system info')
success = False
except Exception as e:
sync_messages.append(f'Error synchronizing system info: {str(e)}')
_logger.error('Error synchronizing system info: %s', str(e))
success = False
# Synchronize devices
try:
_logger.info("=== DÉBUT DE LA SYNCHRONISATION DES APPAREILS ===")
# Utiliser une méthode du site pour récupérer les données des appareils
device_data = self._get_device_data()
if device_data:
_logger.info(f"Données d'appareils reçues: {type(device_data)}")
# Vérifier la structure des données
if isinstance(device_data, dict):
_logger.info(f"Clés dans les données: {list(device_data.keys())}")
# La plupart des API UniFi renvoient les données dans une clé 'data'
devices = device_data.get('data', [])
if not devices and 'devices' in device_data:
devices = device_data.get('devices', [])
_logger.info(f"Nombre d'appareils trouvés: {len(devices)}")
if devices:
# Afficher des informations sur le premier appareil pour débogage
first_device = devices[0]
_logger.info(f"Premier appareil: {first_device.get('name', 'Sans nom')}")
_logger.info(f"MAC: {first_device.get('mac', 'N/A')}")
_logger.info(f"Modèle: {first_device.get('model', 'N/A')}")
# TODO: Implémenter la synchronisation des appareils
# Pour l'instant, juste compter les appareils
sync_messages.append(f'{len(devices)} appareils trouvés')
else:
_logger.warning("Aucun appareil trouvé dans les données")
sync_messages.append('Aucun appareil trouvé')
else:
_logger.warning(f"Format de données inattendu: {type(device_data)}")
sync_messages.append('Format de données inattendu')
success = False
else:
_logger.error("Impossible de récupérer les données des appareils")
sync_messages.append('Échec de la récupération des appareils')
success = False
_logger.info("=== FIN DE LA SYNCHRONISATION DES APPAREILS ===")
except Exception as e:
sync_messages.append(f'Erreur lors de la synchronisation des appareils: {str(e)}')
_logger.error('Erreur lors de la synchronisation des appareils: %s', str(e))
_logger.exception("Détails de l'erreur:")
success = False
# Synchronize networks
try:
# Utiliser une méthode du site pour récupérer les données des réseaux
network_data = self._get_network_data()
if network_data:
# Process and store network data
# TODO: Implement network synchronization
sync_messages.append('Networks synchronized')
else:
sync_messages.append('Failed to retrieve networks')
success = False
except Exception as e:
sync_messages.append(f'Error synchronizing networks: {str(e)}')
_logger.error('Error synchronizing networks: %s', str(e))
success = False
# Synchronize VLANs
try:
# Utiliser une méthode du site pour récupérer les données des VLANs
vlan_data = self._get_vlan_data()
if vlan_data:
# Process and store VLAN data
# TODO: Implement VLAN synchronization
sync_messages.append('VLANs synchronized')
else:
sync_messages.append('Failed to retrieve VLANs')
success = False
except Exception as e:
sync_messages.append(f'Error synchronizing VLANs: {str(e)}')
_logger.error('Error synchronizing VLANs: %s', str(e))
success = False
# Synchronize users
try:
# Utiliser une méthode du site pour récupérer les données des utilisateurs
user_data = self._get_user_data()
if user_data:
# Process and store user data
# TODO: Implement user synchronization
sync_messages.append('Users synchronized')
else:
sync_messages.append('Failed to retrieve users')
success = False
except Exception as e:
sync_messages.append(f'Error synchronizing users: {str(e)}')
_logger.error('Error synchronizing users: %s', str(e))
success = False
# Synchronize firewall rules
try:
# Utiliser une méthode du site pour récupérer les données du pare-feu
firewall_data = self._get_firewall_data()
if firewall_data:
# Process and store firewall data
# TODO: Implement firewall rule synchronization
sync_messages.append('Firewall rules synchronized')
else:
sync_messages.append('Failed to retrieve firewall rules')
success = False
except Exception as e:
sync_messages.append(f'Error synchronizing firewall rules: {str(e)}')
_logger.error('Error synchronizing firewall rules: %s', str(e))
success = False
# Synchronize port forwards
try:
# Utiliser une méthode du site pour récupérer les données de redirection de port
port_forward_data = self._get_port_forward_data()
if port_forward_data:
# Process and store port forward data
# TODO: Implement port forward synchronization
sync_messages.append('Port forwards synchronized')
else:
sync_messages.append('Failed to retrieve port forwards')
success = False
except Exception as e:
sync_messages.append(f'Error synchronizing port forwards: {str(e)}')
_logger.error('Error synchronizing port forwards: %s', str(e))
success = False
# Pas besoin de déconnexion explicite puisque nous utilisons directement les méthodes du site
# Update sync job
if sync_job:
sync_job.write({
'end_time': fields.Datetime.now(),
'state': 'completed' if success else 'failed',
'message': '\n'.join(sync_messages),
})
# Update last sync time
self.write({
'last_sync': fields.Datetime.now(),
})
return success
except Exception as e:
_logger.error('Error synchronizing with Controller API: %s', str(e))
if sync_job:
sync_job.write({
'end_time': fields.Datetime.now(),
'state': 'failed',
'message': str(e),
})
return False
def _sync_site_manager(self):
"""Synchronize data with the Site Manager API
This method orchestrates the synchronization process with the UniFi Site Manager API.
It retrieves data for all supported entity types (devices, networks, VLANs, users,
firewall rules, port forwards, and system info) and updates the corresponding
records in the Odoo database.
Returns:
bool: True if synchronization was successful, False otherwise
"""
# Initialiser sync_job en dehors du bloc try pour éviter les erreurs de lint
sync_job = None
try:
# Create a sync job
sync_job = self.env['unifi.sync.job'].create({
'site_id': self.id,
'start_time': fields.Datetime.now(),
'state': 'running',
'sync_type': 'manual',
'api_type': 'site_manager',
})
# Vérifier que l'API est configurée correctement
if self.api_type != 'site_manager':
if sync_job:
sync_job.write({
'end_time': fields.Datetime.now(),
'state': 'failed',
'message': 'Incorrect API type: site_manager required',
})
return False
# Test the connection to ensure we can authenticate
# Use the internal method directly
if not self._test_site_manager_connection():
if sync_job:
sync_job.write({
'end_time': fields.Datetime.now(),
'state': 'failed',
'message': 'Connection test failed',
})
return False
success = True
sync_messages = []
# Synchronize system info
try:
# Use the integrated method directly instead of calling site_manager model
system_info_data = self._get_site_manager_system_info_data()
if system_info_data:
# Process and store system info data
# TODO: Implement system info synchronization
sync_messages.append('System info synchronized')
else:
sync_messages.append('Failed to retrieve system info')
success = False
except Exception as e:
sync_messages.append(f'Error synchronizing system info: {str(e)}')
_logger.error('Error synchronizing system info: %s', str(e))
success = False
# Synchronize devices
try:
# Use the integrated method directly instead of calling site_manager model
device_data = self._get_site_manager_device_data()
if device_data:
# Process and store device data
# TODO: Implement device synchronization
sync_messages.append('Devices synchronized')
else:
sync_messages.append('Failed to retrieve devices')
success = False
except Exception as e:
sync_messages.append(f'Error synchronizing devices: {str(e)}')
_logger.error('Error synchronizing devices: %s', str(e))
success = False
# Synchronize networks
try:
# Use the integrated method directly instead of calling site_manager model
network_data = self._get_site_manager_network_data()
if network_data:
# Process and store network data
# TODO: Implement network synchronization
sync_messages.append('Networks synchronized')
else:
sync_messages.append('Failed to retrieve networks')
success = False
except Exception as e:
sync_messages.append(f'Error synchronizing networks: {str(e)}')
_logger.error('Error synchronizing networks: %s', str(e))
success = False
# Synchronize VLANs
try:
# Use the integrated method directly instead of calling site_manager model
vlan_data = self._get_site_manager_vlan_data()
if vlan_data:
# Process and store VLAN data
# TODO: Implement VLAN synchronization
sync_messages.append('VLANs synchronized')
else:
sync_messages.append('Failed to retrieve VLANs')
success = False
except Exception as e:
sync_messages.append(f'Error synchronizing VLANs: {str(e)}')
_logger.error('Error synchronizing VLANs: %s', str(e))
success = False
# Synchronize users
try:
# Use the integrated method directly instead of calling site_manager model
user_data = self._get_site_manager_user_data()
if user_data:
# Process and store user data
# TODO: Implement user synchronization
sync_messages.append('Users synchronized')
else:
sync_messages.append('Failed to retrieve users')
success = False
except Exception as e:
sync_messages.append(f'Error synchronizing users: {str(e)}')
_logger.error('Error synchronizing users: %s', str(e))
success = False
# Synchronize firewall rules
try:
# Use the integrated method directly instead of calling site_manager model
firewall_data = self._get_site_manager_firewall_data()
if firewall_data:
# Process and store firewall data
# TODO: Implement firewall rule synchronization
sync_messages.append('Firewall rules synchronized')
else:
sync_messages.append('Failed to retrieve firewall rules')
success = False
except Exception as e:
sync_messages.append(f'Error synchronizing firewall rules: {str(e)}')
_logger.error('Error synchronizing firewall rules: %s', str(e))
success = False
# Synchronize port forwards
try:
# Use the integrated method directly instead of calling site_manager model
port_forward_data = self._get_site_manager_port_forward_data()
if port_forward_data:
# Process and store port forward data
# TODO: Implement port forward synchronization
sync_messages.append('Port forwards synchronized')
else:
sync_messages.append('Failed to retrieve port forwards')
success = False
except Exception as e:
sync_messages.append(f'Error synchronizing port forwards: {str(e)}')
_logger.error('Error synchronizing port forwards: %s', str(e))
success = False
# Update sync job
if sync_job:
sync_job.write({
'end_time': fields.Datetime.now(),
'state': 'completed' if success else 'failed',
'message': '\n'.join(sync_messages),
})
# Update last sync time
self.write({
'last_sync': fields.Datetime.now(),
})
return success
except Exception as e:
_logger.error('Error synchronizing with Site Manager API: %s', str(e))
if sync_job:
sync_job.write({
'end_time': fields.Datetime.now(),
'state': 'failed',
'message': str(e),
})
return False
# Override create and write methods
@api.model_create_multi
def create(self, vals_list):
"""Override create to verify connection before saving
Args:
vals_list (list): List of values to create records with
Returns:
unifi.site: The created records
"""
# Create the records
sites = super(UnifiSite, self).create(vals_list)
# Test the connection for each site
for site in sites:
try:
if site.api_type == 'controller':
site._test_controller_connection()
elif site.api_type == 'site_manager':
site._test_site_manager_connection()
except Exception as e:
_logger.warning('Connection test failed during creation: %s', str(e))
# We don't raise an error here, just log a warning
return sites
def write(self, vals):
"""Override write to verify connection if connection details change"""
# Check if connection details have changed
connection_fields = ['api_type', 'host', 'port', 'username', 'password',
'controller_type', 'api_key', 'mfa_enabled', 'mfa_token']
connection_changed = any(field in vals for field in connection_fields)
# Write the values
result = super(UnifiSite, self).write(vals)
# Test the connection if connection details have changed
if connection_changed:
for site in self:
try:
if site.api_type == 'controller':
site._test_controller_connection()
elif site.api_type == 'site_manager':
site._test_site_manager_connection()
except Exception as e:
_logger.warning('Connection test failed after update: %s', str(e))
# We don't raise an error here, just log a warning
return result
def get_device_data(self):
"""Récupère les données des appareils du site
Cette méthode utilise l'API appropriée en fonction du type de site
pour obtenir les informations sur les appareils.
Returns:
list: Liste des données de tous les appareils
"""
self.ensure_one()
# Determine which API implementation to use
if self.api_type == 'controller':
# Use Controller API implementation
# This is now directly implemented here instead of delegating to another model
return self._get_controller_device_data()
elif self.api_type == 'site_manager':
# Use Site Manager API implementation
# This is now directly implemented here instead of delegating to another model
return self._get_site_manager_device_data()
def _create_api_log(self, api_method, message_text, direction):
"""Create a new API log entry
Args:
api_method: API method being called (e.g., 'get_device_data')
message_text: Log message
direction: Direction of the API call (e.g., 'outgoing', 'incoming')
Returns:
Record: Newly created API log record
"""
try:
# Déterminer le type d'API en fonction du contexte
api_type = self.api_type or 'controller'
# Créer un endpoint basé sur le nom de la méthode
endpoint = f"/api/{api_method}"
# Déterminer la méthode HTTP en fonction de la direction
http_method = 'GET' if direction == 'outgoing' else 'POST'
# Create a new api.log record
api_log_vals = {
'site_id': self.id,
'api_type': api_type,
'endpoint': endpoint,
'method': http_method,
'error_message': message_text if direction != 'outgoing' else None,
'start_time': fields.Datetime.now(),
}
# Create and return the log record
return self.env['unifi.api.log'].create(api_log_vals)
except Exception as e:
_logger.error('Error creating API log: %s', str(e))
return False
def _get_controller_device_data(self):
"""Get device data from UniFi Controller API
This method directly implements the device data retrieval logic for the Controller API type.
It was previously in the unifi.site.controller model, but has been integrated here.
Returns:
list: Device data from Controller API
"""
# Create a log entry for this API call
api_log = self._create_api_log('get_device_data', 'Getting device data from Controller API', 'outgoing')
try:
# Implement the Controller-specific API call logic here
# This would be similar to what was in the unifi.site.controller model
base_url = f"https://{self.host}:{self.port}"
endpoint = f"/api/s/{self.site_id}/stat/device" # Using site_id field
# Make the API request and process the response
# For now, this is a placeholder
result = []
# Log the successful API call
self._update_api_log(api_log, {'message': 'Success: Retrieved device data', 'status': 'success'})
return result
except Exception as e:
# Log the error
self._update_api_log(api_log, {'message': f'Error: {str(e)}', 'status': 'error'})
_logger.error("Error getting device data from Controller API: %s", str(e))
return False
def _get_site_manager_device_data(self):
"""Get device data from UniFi Site Manager API
This method directly implements the device data retrieval logic for the Site Manager API type.
It was previously in the unifi.site.manager model, but has been integrated here.
Returns:
list: Device data from Site Manager API
"""
# Create a log entry for this API call
api_log = self._create_api_log('get_device_data', 'Getting device data from Site Manager API', 'outgoing')
try:
# Implement the Site Manager-specific API call logic here
# This would be similar to what was in the unifi.site.manager model
# Make the API request and process the response
# For now, this is a placeholder
result = []
# Log the successful API call
self._update_api_log(api_log, {'message': 'Success: Retrieved device data', 'status': 'success'})
return result
except Exception as e:
# Log the error
self._update_api_log(api_log, {'message': f'Error: {str(e)}', 'status': 'error'})
_logger.error("Error getting device data from Site Manager API: %s", str(e))
return False
else:
# Unsupported API type
_logger.error("Unsupported API type: %s", self.api_type)
return False
def _get_site_manager_vlan_data(self):
"""Get VLAN data from UniFi Site Manager API
This method directly implements the VLAN data retrieval logic for the Site Manager API type.
It was previously in the unifi.site.manager model, but has been integrated here.
Returns:
list: VLAN data from Site Manager API
"""
# Create a log entry for this API call
api_log = self._create_api_log('get_vlan_data', 'Getting VLAN data from Site Manager API', 'outgoing')
try:
# Implement the Site Manager-specific API call logic here
# Make the API request and process the response
# For now, this is a placeholder
result = []
# Log the successful API call
self._update_api_log(api_log, {'message': 'Success: Retrieved VLAN data', 'status': 'success'})
return result
except Exception as e:
# Log the error
self._update_api_log(api_log, {'message': f'Error: {str(e)}', 'status': 'error'})
_logger.error("Error getting VLAN data from Site Manager API: %s", str(e))
return False
def _get_site_manager_user_data(self):
"""Get user data from UniFi Site Manager API
This method directly implements the user data retrieval logic for the Site Manager API type.
It was previously in the unifi.site.manager model, but has been integrated here.
Returns:
list: User data from Site Manager API
"""
# Create a log entry for this API call
api_log = self._create_api_log('get_user_data', 'Getting user data from Site Manager API', 'outgoing')
try:
# Implement the Site Manager-specific API call logic here
# Make the API request and process the response
# For now, this is a placeholder
result = []
# Log the successful API call
self._update_api_log(api_log, {'message': 'Success: Retrieved user data', 'status': 'success'})
return result
except Exception as e:
# Log the error
self._update_api_log(api_log, {'message': f'Error: {str(e)}', 'status': 'error'})
_logger.error("Error getting user data from Site Manager API: %s", str(e))
return False
def _get_site_manager_firewall_data(self):
"""Get firewall data from UniFi Site Manager API
This method directly implements the firewall data retrieval logic for the Site Manager API type.
It was previously in the unifi.site.manager model, but has been integrated here.
Returns:
list: Firewall data from Site Manager API
"""
# Create a log entry for this API call
api_log = self._create_api_log('get_firewall_data', 'Getting firewall data from Site Manager API', 'outgoing')
try:
# Implement the Site Manager-specific API call logic here
# Make the API request and process the response
# For now, this is a placeholder
result = []
# Log the successful API call
self._update_api_log(api_log, {'message': 'Success: Retrieved firewall data', 'status': 'success'})
return result
except Exception as e:
# Log the error
self._update_api_log(api_log, {'message': f'Error: {str(e)}', 'status': 'error'})
_logger.error("Error getting firewall data from Site Manager API: %s", str(e))
return False
def _get_site_manager_port_forward_data(self):
"""Get port forward data from UniFi Site Manager API
This method directly implements the port forward data retrieval logic for the Site Manager API type.
It was previously in the unifi.site.manager model, but has been integrated here.
Returns:
list: Port forward data from Site Manager API
"""
# Create a log entry for this API call
api_log = self._create_api_log('get_port_forward_data', 'Getting port forward data from Site Manager API', 'outgoing')
try:
# Implement the Site Manager-specific API call logic here
# Make the API request and process the response
# For now, this is a placeholder
result = []
# Log the successful API call
self._update_api_log(api_log, {'message': 'Success: Retrieved port forward data', 'status': 'success'})
return result
except Exception as e:
# Log the error
self._update_api_log(api_log, {'message': f'Error: {str(e)}', 'status': 'error'})
_logger.error("Error getting port forward data from Site Manager API: %s", str(e))
return False
def _get_site_manager_system_info_data(self):
"""Get system info data from UniFi Site Manager API
This method directly implements the system info data retrieval logic for the Site Manager API type.
It was previously in the unifi.site.manager model, but has been integrated here.
Returns:
dict: System info data from Site Manager API
"""
# Create a log entry for this API call
api_log = self._create_api_log('get_system_info_data', 'Getting system info data from Site Manager API', 'outgoing')
try:
# Implement the Site Manager-specific API call logic here
# Make the API request and process the response
# For now, this is a placeholder
result = {}
# Log the successful API call
self._update_api_log(api_log, {'message': 'Success: Retrieved system info data', 'status': 'success'})
return result
except Exception as e:
# Log the error
self._update_api_log(api_log, {'message': f'Error: {str(e)}', 'status': 'error'})
_logger.error("Error getting system info data from Site Manager API: %s", str(e))
return False
def _get_site_manager_dns_data(self):
"""Get DNS data from UniFi Site Manager API
This method directly implements the DNS data retrieval logic for the Site Manager API type.
Returns:
list: DNS data from Site Manager API
"""
# Create a log entry for this API call
api_log = self._create_api_log('get_dns_data', 'Getting DNS data from Site Manager API', 'outgoing')
try:
# Implement the Site Manager-specific API call logic here
# Make the API request and process the response
# For now, this is a placeholder
result = []
# Log the successful API call
self._update_api_log(api_log, {'message': 'Success: Retrieved DNS data', 'status': 'success'})
return result
except Exception as e:
# Log the error
self._update_api_log(api_log, {'message': f'Error: {str(e)}', 'status': 'error'})
_logger.error("Error getting DNS data from Site Manager API: %s", str(e))
return False
def _get_controller_wifi_data(self):
"""Get WiFi network data from UniFi Controller API
This method directly implements the WiFi network data retrieval logic for the Controller API type.
Returns:
list: WiFi network data from Controller API
"""
# Create a log entry for this API call
api_log = self._create_api_log('get_wifi_data', 'Getting WiFi network data from Controller API', 'outgoing')
try:
# Vérifier si nous avons une session d'authentification valide
if not self._check_auth_session():
_logger.error("Pas de session d'authentification valide pour récupérer les données des réseaux WiFi")
return False
# Construire l'URL de base en fonction du type de contrôleur (standard ou UDM Pro)
base_url = f"https://{self.host}:{self.port}"
# Déterminer si nous avons affaire à un UDM Pro
is_udm_pro = self.auth_session_id and self.auth_session_id.is_udm_pro
# Construire l'endpoint en fonction du type de contrôleur
if is_udm_pro:
# Pour UDM Pro, ajouter le préfixe /proxy/network
endpoint = f"/proxy/network/api/s/{self.site_id}/rest/wlanconf"
else:
# Pour les contrôleurs standard
endpoint = f"/api/s/{self.site_id}/rest/wlanconf"
_logger.info(f"Récupération des réseaux WiFi depuis l'endpoint: {endpoint}")
# Préparer les en-têtes de la requête
headers = {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
# Ajouter le token d'authentification si disponible (pour UDM Pro)
if is_udm_pro and self.auth_session_id.token:
headers['Authorization'] = f"Bearer {self.auth_session_id.token}"
# Préparer les cookies pour l'authentification
cookies = self._get_auth_cookies()
if not cookies:
_logger.error("Impossible de récupérer les cookies d'authentification")
return False
# Effectuer la requête HTTP
url = f"{base_url}{endpoint}"
_logger.info(f"Envoi de la requête GET à {url}")
response = requests.get(
url,
headers=headers,
cookies=cookies,
verify=self.verify_ssl
)
# Vérifier le code de statut de la réponse
if response.status_code != 200:
_logger.error(f"Erreur lors de la récupération des données des réseaux WiFi: {response.status_code} - {response.text}")
self._update_api_log(api_log, {
'status_code': response.status_code,
'response_body': response.text,
'message': f'Error: HTTP {response.status_code}',
'status': 'error'
})
return False
# Analyser la réponse JSON
try:
result = response.json()
_logger.info(f"Réponse reçue: {type(result)}")
# Enregistrer les détails de la réponse dans le log API
self._update_api_log(api_log, {
'status_code': response.status_code,
'response_body': response.text,
'message': 'Success: Retrieved WiFi network data',
'status': 'success'
})
# La plupart des API UniFi renvoient les données dans une clé 'data'
if isinstance(result, dict) and 'data' in result:
return result['data']
return result
except json.JSONDecodeError as e:
_logger.error(f"Erreur lors de l'analyse de la réponse JSON: {str(e)}")
_logger.error(f"Contenu de la réponse: {response.text}")
self._update_api_log(api_log, {
'status_code': response.status_code,
'response_body': response.text,
'message': f'Error: Invalid JSON response - {str(e)}',
'status': 'error'
})
return False
except Exception as e:
# Log the error
_logger.exception(f"Erreur lors de la récupération des données des réseaux WiFi: {str(e)}")
self._update_api_log(api_log, {
'message': f'Error: {str(e)}',
'status': 'error'
})
return False
def _get_controller_user_data(self):
"""Get user data from UniFi Controller API
This method directly implements the user data retrieval logic for the Controller API type.
Returns:
list: User data from Controller API
"""
# Create a log entry for this API call
api_log = self._create_api_log('get_user_data', 'Getting user data from Controller API', 'outgoing')
try:
# Vérifier si nous avons une session d'authentification valide
if not self._check_auth_session():
_logger.error("Pas de session d'authentification valide pour récupérer les données des utilisateurs")
return False
# Construire l'URL de base en fonction du type de contrôleur (standard ou UDM Pro)
base_url = f"https://{self.host}:{self.port}"
# Déterminer si nous avons affaire à un UDM Pro
is_udm_pro = self.auth_session_id and self.auth_session_id.is_udm_pro
# Construire l'endpoint en fonction du type de contrôleur
if is_udm_pro:
# Pour UDM Pro, ajouter le préfixe /proxy/network
endpoint = f"/proxy/network/api/s/{self.site_id}/rest/user"
else:
# Pour les contrôleurs standard
endpoint = f"/api/s/{self.site_id}/rest/user"
_logger.info(f"Récupération des utilisateurs depuis l'endpoint: {endpoint}")
# Préparer les en-têtes de la requête
headers = {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
# Ajouter le token d'authentification si disponible (pour UDM Pro)
if is_udm_pro and self.auth_session_id.token:
headers['Authorization'] = f"Bearer {self.auth_session_id.token}"
# Préparer les cookies pour l'authentification
cookies = self._get_auth_cookies()
if not cookies:
_logger.error("Impossible de récupérer les cookies d'authentification")
return False
# Effectuer la requête HTTP
url = f"{base_url}{endpoint}"
_logger.info(f"Envoi de la requête GET à {url}")
response = requests.get(
url,
headers=headers,
cookies=cookies,
verify=self.verify_ssl
)
# Vérifier le code de statut de la réponse
if response.status_code != 200:
_logger.error(f"Erreur lors de la récupération des données des utilisateurs: {response.status_code} - {response.text}")
self._update_api_log(api_log, {
'status_code': response.status_code,
'response_body': response.text,
'message': f'Error: HTTP {response.status_code}',
'status': 'error'
})
return False
# Analyser la réponse JSON
try:
result = response.json()
_logger.info(f"Réponse reçue: {type(result)}")
# Enregistrer les détails de la réponse dans le log API
self._update_api_log(api_log, {
'status_code': response.status_code,
'response_body': response.text,
'message': 'Success: Retrieved user data',
'status': 'success'
})
# La plupart des API UniFi renvoient les données dans une clé 'data'
if isinstance(result, dict) and 'data' in result:
return result['data']
return result
except json.JSONDecodeError as e:
_logger.error(f"Erreur lors de l'analyse de la réponse JSON: {str(e)}")
_logger.error(f"Contenu de la réponse: {response.text}")
self._update_api_log(api_log, {
'status_code': response.status_code,
'response_body': response.text,
'message': f'Error: Invalid JSON response - {str(e)}',
'status': 'error'
})
return False
except Exception as e:
# Log the error
_logger.exception(f"Erreur lors de la récupération des données des utilisateurs: {str(e)}")
self._update_api_log(api_log, {
'message': f'Error: {str(e)}',
'status': 'error'
})
return False
def _get_controller_get_firewall_data(self):
"""Get firewall rules data from UniFi Controller API
This method directly implements the firewall rules data retrieval logic for the Controller API type.
Returns:
list: Firewall rules data from Controller API
"""
# Create a log entry for this API call
api_log = self._create_api_log('get_firewall_data', 'Getting firewall rules data from Controller API', 'outgoing')
try:
# Vérifier si nous avons une session d'authentification valide
if not self._check_auth_session():
_logger.error("Pas de session d'authentification valide pour récupérer les données des règles de pare-feu")
return False
# Construire l'URL de base en fonction du type de contrôleur (standard ou UDM Pro)
base_url = f"https://{self.host}:{self.port}"
# Déterminer si nous avons affaire à un UDM Pro
is_udm_pro = self.auth_session_id and self.auth_session_id.is_udm_pro
# Construire l'endpoint en fonction du type de contrôleur
if is_udm_pro:
# Pour UDM Pro, ajouter le préfixe /proxy/network
endpoint = f"/proxy/network/api/s/{self.site_id}/rest/firewallrule"
else:
# Pour les contrôleurs standard
endpoint = f"/api/s/{self.site_id}/rest/firewallrule"
_logger.info(f"Récupération des règles de pare-feu depuis l'endpoint: {endpoint}")
# Préparer les en-têtes de la requête
headers = {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
# Ajouter le token d'authentification si disponible (pour UDM Pro)
if is_udm_pro and self.auth_session_id.token:
headers['Authorization'] = f"Bearer {self.auth_session_id.token}"
# Préparer les cookies pour l'authentification
cookies = self._get_auth_cookies()
if not cookies:
_logger.error("Impossible de récupérer les cookies d'authentification")
return False
# Effectuer la requête GET pour récupérer les données des règles de pare-feu
response = requests.get(
f"{base_url}{endpoint}",
headers=headers,
cookies=cookies,
verify=self.verify_ssl,
timeout=self.timeout
)
# Vérifier si la requête a réussi
if response.status_code != 200:
error_msg = f"Erreur lors de la récupération des règles de pare-feu: {response.status_code} - {response.text}"
_logger.error(error_msg)
self._update_api_log(api_log, {'message': error_msg, 'status': 'error', 'response': response.text})
return False
# Analyser la réponse JSON
response_data = response.json()
# Extraire les données des règles de pare-feu
firewall_data = response_data.get('data', [])
# Mettre à jour le log API avec le succès
self._update_api_log(api_log, {
'message': f"Succès: {len(firewall_data)} règles de pare-feu récupérées",
'status': 'success',
'response': json.dumps(response_data)
})
return firewall_data
except Exception as e:
# Log the error
error_msg = f"Erreur lors de la récupération des règles de pare-feu: {str(e)}"
_logger.error(error_msg)
self._update_api_log(api_log, {'message': error_msg, 'status': 'error'})
return False
def _get_controller_get_port_forward_data(self):
"""Get port forwarding data from UniFi Controller API
This method directly implements the port forwarding data retrieval logic for the Controller API type.
Returns:
list: Port forwarding data from Controller API
"""
# Create a log entry for this API call
api_log = self._create_api_log('get_port_forward_data', 'Getting port forwarding data from Controller API', 'outgoing')
try:
# Vérifier si nous avons une session d'authentification valide
if not self._check_auth_session():
_logger.error("Pas de session d'authentification valide pour récupérer les données de redirection de port")
return False
# Construire l'URL de base en fonction du type de contrôleur (standard ou UDM Pro)
base_url = f"https://{self.host}:{self.port}"
# Déterminer si nous avons affaire à un UDM Pro
is_udm_pro = self.auth_session_id and self.auth_session_id.is_udm_pro
# Construire l'endpoint en fonction du type de contrôleur
if is_udm_pro:
# Pour UDM Pro, ajouter le préfixe /proxy/network
endpoint = f"/proxy/network/api/s/{self.site_id}/rest/portforward"
else:
# Pour les contrôleurs standard
endpoint = f"/api/s/{self.site_id}/rest/portforward"
_logger.info(f"Récupération des redirections de port depuis l'endpoint: {endpoint}")
# Préparer les en-têtes de la requête
headers = {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
# Ajouter le token d'authentification si disponible (pour UDM Pro)
if is_udm_pro and self.auth_session_id.token:
headers['Authorization'] = f"Bearer {self.auth_session_id.token}"
# Préparer les cookies pour l'authentification
cookies = self._get_auth_cookies()
if not cookies:
_logger.error("Impossible de récupérer les cookies d'authentification")
return False
# Effectuer la requête GET pour récupérer les données de redirection de port
response = requests.get(
f"{base_url}{endpoint}",
headers=headers,
cookies=cookies,
verify=self.verify_ssl,
timeout=self.timeout
)
# Vérifier si la requête a réussi
if response.status_code != 200:
error_msg = f"Erreur lors de la récupération des redirections de port: {response.status_code} - {response.text}"
_logger.error(error_msg)
self._update_api_log(api_log, {'message': error_msg, 'status': 'error', 'response': response.text})
return False
# Analyser la réponse JSON
response_data = response.json()
# Extraire les données des redirections de port
port_forward_data = response_data.get('data', [])
# Mettre à jour le log API avec le succès
self._update_api_log(api_log, {
'message': f"Succès: {len(port_forward_data)} redirections de port récupérées",
'status': 'success',
'response': json.dumps(response_data)
})
return port_forward_data
except Exception as e:
# Log the error
error_msg = f"Erreur lors de la récupération des redirections de port: {str(e)}"
_logger.error(error_msg)
self._update_api_log(api_log, {'message': error_msg, 'status': 'error'})
return False
def _get_controller_get_dns_data(self):
"""Get DNS data from UniFi Controller API
This method directly implements the DNS data retrieval logic for the Controller API type.
Returns:
list: DNS data from Controller API
"""
# Create a log entry for this API call
api_log = self._create_api_log('get_dns_data', 'Getting DNS data from Controller API', 'outgoing')
try:
# Vérifier si nous avons une session d'authentification valide
if not self._check_auth_session():
_logger.error("Pas de session d'authentification valide pour récupérer les données DNS")
return False
# Construire l'URL de base en fonction du type de contrôleur (standard ou UDM Pro)
base_url = f"https://{self.host}:{self.port}"
# Déterminer si nous avons affaire à un UDM Pro
is_udm_pro = self.auth_session_id and self.auth_session_id.is_udm_pro
# Construire l'endpoint en fonction du type de contrôleur
if is_udm_pro:
# Pour UDM Pro, ajouter le préfixe /proxy/network
endpoint = f"/proxy/network/api/s/{self.site_id}/rest/setting"
else:
# Pour les contrôleurs standard
endpoint = f"/api/s/{self.site_id}/rest/setting"
_logger.info(f"Récupération des entrées DNS depuis l'endpoint: {endpoint}")
# Préparer les en-têtes de la requête
headers = {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
# Ajouter le token d'authentification si disponible (pour UDM Pro)
if is_udm_pro and self.auth_session_id.token:
headers['Authorization'] = f"Bearer {self.auth_session_id.token}"
# Préparer les cookies pour l'authentification
cookies = self._get_auth_cookies()
if not cookies:
_logger.error("Impossible de récupérer les cookies d'authentification")
return False
# Effectuer la requête GET pour récupérer les données DNS
response = requests.get(
f"{base_url}{endpoint}",
headers=headers,
cookies=cookies,
verify=self.verify_ssl,
timeout=self.timeout
)
# Vérifier si la requête a réussi
if response.status_code != 200:
error_msg = f"Erreur lors de la récupération des entrées DNS: {response.status_code} - {response.text}"
_logger.error(error_msg)
self._update_api_log(api_log, {'message': error_msg, 'status': 'error', 'response': response.text})
return False
# Analyser la réponse JSON
response_data = response.json()
# Extraire les données DNS des paramètres du site
settings_data = response_data.get('data', [])
# Rechercher les paramètres DNS dans les données de configuration
dns_entries = []
for setting in settings_data:
if setting.get('key') == 'networks':
networks = setting.get('values', [])
for network in networks:
# Extraire les serveurs DNS configurés dans chaque réseau
dns_servers = network.get('dns_servers', [])
if dns_servers:
for i, dns_server in enumerate(dns_servers):
if dns_server and dns_server.strip():
dns_entries.append({
'hostname': f"dns-server-{network.get('name', '')}-{i+1}",
'ip_address': dns_server,
'description': f"DNS Server {i+1} for network {network.get('name', '')}",
'enabled': True,
'unifi_id': f"{network.get('_id', '')}-dns-{i}",
'entry_type': 'server'
})
# Extraire les entrées DNS statiques configurées dans chaque réseau
static_dns = network.get('static_dns', [])
if static_dns:
for i, entry in enumerate(static_dns):
dns_entries.append({
'hostname': entry.get('name', f"static-dns-{i}"),
'ip_address': entry.get('ip', ''),
'description': f"Static DNS entry for {entry.get('name', '')}",
'enabled': True,
'unifi_id': f"{network.get('_id', '')}-static-dns-{i}",
'entry_type': 'static'
})
# Si aucune entrée DNS n'est trouvée, essayer de récupérer les paramètres DNS généraux
if not dns_entries:
for setting in settings_data:
if setting.get('key') == 'dns':
dns_config = setting.get('values', {})
servers = dns_config.get('servers', [])
for i, server in enumerate(servers):
if server and server.strip():
dns_entries.append({
'hostname': f"global-dns-server-{i+1}",
'ip_address': server,
'description': f"Global DNS Server {i+1}",
'enabled': True,
'unifi_id': f"global-dns-{i}",
'entry_type': 'server'
})
formatted_dns_data = dns_entries
# Mettre à jour le log API avec le succès, même si aucune entrée n'est trouvée
message = f"Succès: {len(formatted_dns_data)} entrées DNS récupérées"
if not formatted_dns_data:
message = "Succès: Aucune entrée DNS trouvée dans la configuration"
self._update_api_log(api_log, {
'message': message,
'status': 'success',
'response': json.dumps(response_data)
})
# Retourner les données formatées, même si c'est une liste vide
return formatted_dns_data
except Exception as e:
# Log the error
error_msg = f"Erreur lors de la récupération des entrées DNS: {str(e)}"
_logger.error(error_msg)
self._update_api_log(api_log, {'message': error_msg, 'status': 'error'})
return False
def _get_controller_get_system_info_data(self):
"""Get system information data from UniFi Controller API
This method directly implements the system information data retrieval logic for the Controller API type.
Returns:
list: System information data from Controller API
"""
# Create a log entry for this API call
api_log = self._create_api_log('get_system_info_data', 'Getting system information data from Controller API', 'outgoing')
try:
# Vérifier si nous avons une session d'authentification valide
if not self._check_auth_session():
_logger.error("Pas de session d'authentification valide pour récupérer les données d'information système")
return False
# Construire l'URL de base en fonction du type de contrôleur (standard ou UDM Pro)
base_url = f"https://{self.host}:{self.port}"
# Déterminer si nous avons affaire à un UDM Pro
is_udm_pro = self.auth_session_id and self.auth_session_id.is_udm_pro
# Construire l'endpoint en fonction du type de contrôleur
if is_udm_pro:
# Pour UDM Pro, ajouter le préfixe /proxy/network
endpoint = f"/proxy/network/api/s/{self.site_id}/stat/device"
else:
# Pour les contrôleurs standard
endpoint = f"/api/s/{self.site_id}/stat/device"
_logger.info(f"Récupération des informations système depuis l'endpoint: {endpoint}")
# Préparer les en-têtes de la requête
headers = {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
# Ajouter le token d'authentification si disponible (pour UDM Pro)
if is_udm_pro and self.auth_session_id.token:
headers['Authorization'] = f"Bearer {self.auth_session_id.token}"
# Préparer les cookies pour l'authentification
cookies = self._get_auth_cookies()
if not cookies:
_logger.error("Impossible de récupérer les cookies d'authentification")
return False
# Effectuer la requête GET pour récupérer les données d'information système
response = requests.get(
f"{base_url}{endpoint}",
headers=headers,
cookies=cookies,
verify=self.verify_ssl,
timeout=self.timeout
)
# Vérifier si la requête a réussi
if response.status_code != 200:
error_msg = f"Erreur lors de la récupération des informations système: {response.status_code} - {response.text}"
_logger.error(error_msg)
self._update_api_log(api_log, {'message': error_msg, 'status': 'error', 'response': response.text})
return False
# Analyser la réponse JSON
response_data = response.json()
# Extraire les données d'information système
devices = response_data.get('data', [])
# Transformer les données des appareils en format d'information système
system_info_data = []
for device in devices:
# Extraire les informations pertinentes pour le système
system_info = {
'hostname': device.get('name') or device.get('hostname', 'Unknown'),
'version': device.get('version', 'Unknown'),
'model': device.get('model', 'Unknown'),
'uptime': device.get('uptime', 0),
'serial': device.get('serial', 'Unknown'),
'mac_address': device.get('mac', 'Unknown'),
'device_id': device.get('_id', ''),
'ip_address': device.get('ip', 'Unknown'),
'cpu_usage': device.get('system_stats', {}).get('cpu', 0),
'memory_usage': device.get('system_stats', {}).get('mem', 0),
'temperature': device.get('general_temperature', 0)
}
system_info_data.append(system_info)
# Mettre à jour le log API avec le succès
self._update_api_log(api_log, {
'message': f"Succès: {len(system_info_data)} informations système récupérées",
'status': 'success',
'response': json.dumps(response_data)
})
return system_info_data
except Exception as e:
# Log the error
error_msg = f"Erreur lors de la récupération des informations système: {str(e)}"
_logger.error(error_msg)
self._update_api_log(api_log, {'message': error_msg, 'status': 'error'})
return False
def _get_controller_get_vlan_data(self):
"""Get VLAN data from UniFi Controller API
This method directly implements the VLAN data retrieval logic for the Controller API type.
Returns:
list: VLAN data from Controller API
"""
# Create a log entry for this API call
api_log = self._create_api_log('get_vlan_data', 'Getting VLAN data from Controller API', 'outgoing')
try:
# Vérifier si nous avons une session d'authentification valide
if not self._check_auth_session():
_logger.error("Pas de session d'authentification valide pour récupérer les données des VLANs")
return False
# Construire l'URL de base en fonction du type de contrôleur (standard ou UDM Pro)
base_url = f"https://{self.host}:{self.port}"
# Déterminer si nous avons affaire à un UDM Pro
is_udm_pro = self.auth_session_id and self.auth_session_id.is_udm_pro
# Construire l'endpoint en fonction du type de contrôleur
if is_udm_pro:
# Pour UDM Pro, ajouter le préfixe /proxy/network
endpoint = f"/proxy/network/api/s/{self.site_id}/rest/networkconf"
else:
# Pour les contrôleurs standard
endpoint = f"/api/s/{self.site_id}/rest/networkconf"
_logger.info(f"Récupération des VLANs depuis l'endpoint: {endpoint}")
# Préparer les en-têtes de la requête
headers = {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
# Ajouter le token d'authentification si disponible (pour UDM Pro)
if is_udm_pro and self.auth_session_id.token:
headers['Authorization'] = f"Bearer {self.auth_session_id.token}"
# Préparer les cookies pour l'authentification
cookies = self._get_auth_cookies()
if not cookies:
_logger.error("Impossible de récupérer les cookies d'authentification")
return False
# Effectuer la requête GET pour récupérer les données des VLANs
response = requests.get(
f"{base_url}{endpoint}",
headers=headers,
cookies=cookies,
verify=self.verify_ssl,
timeout=self.timeout
)
# Vérifier si la requête a réussi
if response.status_code != 200:
error_msg = f"Erreur lors de la récupération des VLANs: {response.status_code} - {response.text}"
_logger.error(error_msg)
self._update_api_log(api_log, {'message': error_msg, 'status': 'error', 'response': response.text})
return False
# Analyser la réponse JSON
response_data = response.json()
# Extraire les données des VLANs
vlan_data = []
for network in response_data.get('data', []):
# Filtrer uniquement les réseaux qui ont un VLAN ID
if 'vlan' in network and network.get('vlan') not in [None, 0]:
# Transformer les données du réseau en format VLAN
vlan = {
'vlan_id': network.get('vlan'),
'name': network.get('name', f"VLAN {network.get('vlan')}"),
'purpose': network.get('purpose', 'corporate'),
'enabled': network.get('enabled', True),
'_id': network.get('_id'),
'subnet': network.get('ip_subnet'),
'created_at': network.get('created', fields.Datetime.now().strftime('%Y-%m-%d %H:%M:%S')),
'updated_at': network.get('updated', fields.Datetime.now().strftime('%Y-%m-%d %H:%M:%S'))
}
vlan_data.append(vlan)
# Mettre à jour le log API avec le succès
self._update_api_log(api_log, {
'message': f"Succès: {len(vlan_data)} VLANs récupérés",
'status': 'success',
'response': json.dumps(response_data)
})
return vlan_data
except Exception as e:
# Log the error
error_msg = f"Erreur lors de la récupération des VLANs: {str(e)}"
_logger.error(error_msg)
self._update_api_log(api_log, {'message': error_msg, 'status': 'error'})
return False
def _get_controller_network_data(self):
"""Get network data from UniFi Controller API
This method directly implements the network data retrieval logic for the Controller API type.
It was previously in the unifi.site.controller model, but has been integrated here.
Returns:
list: Network data from Controller API
"""
# Create a log entry for this API call
api_log = self._create_api_log('get_network_data', 'Getting network data from Controller API', 'outgoing')
try:
# Vérifier si nous avons une session d'authentification valide
if not self._check_auth_session():
_logger.error("Pas de session d'authentification valide pour récupérer les données des réseaux")
return False
# Construire l'URL de base en fonction du type de contrôleur (standard ou UDM Pro)
base_url = f"https://{self.host}:{self.port}"
# Déterminer si nous avons affaire à un UDM Pro
is_udm_pro = self.auth_session_id and self.auth_session_id.is_udm_pro
# Construire l'endpoint en fonction du type de contrôleur
if is_udm_pro:
# Pour UDM Pro, ajouter le préfixe /proxy/network
endpoint = f"/proxy/network/api/s/{self.site_id}/rest/networkconf"
else:
# Pour les contrôleurs standard
endpoint = f"/api/s/{self.site_id}/rest/networkconf"
_logger.info(f"Récupération des réseaux depuis l'endpoint: {endpoint}")
# Préparer les en-têtes de la requête
headers = {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
# Ajouter le token d'authentification si disponible (pour UDM Pro)
if is_udm_pro and self.auth_session_id.token:
headers['Authorization'] = f"Bearer {self.auth_session_id.token}"
# Préparer les cookies pour l'authentification
cookies = self._get_auth_cookies()
if not cookies:
_logger.error("Impossible de récupérer les cookies d'authentification")
return False
# Effectuer la requête HTTP
url = f"{base_url}{endpoint}"
_logger.info(f"Envoi de la requête GET à {url}")
response = requests.get(
url,
headers=headers,
cookies=cookies,
verify=self.verify_ssl
)
# Vérifier le code de statut de la réponse
if response.status_code != 200:
_logger.error(f"Erreur lors de la récupération des données des réseaux: {response.status_code} - {response.text}")
self._update_api_log(api_log, {
'status_code': response.status_code,
'response_body': response.text,
'message': f'Error: HTTP {response.status_code}',
'status': 'error'
})
return False
# Analyser la réponse JSON
try:
result = response.json()
_logger.info(f"Réponse reçue: {type(result)}")
# Enregistrer les détails de la réponse dans le log API
self._update_api_log(api_log, {
'status_code': response.status_code,
'response_body': response.text,
'message': 'Success: Retrieved network data',
'status': 'success'
})
# La plupart des API UniFi renvoient les données dans une clé 'data'
if isinstance(result, dict) and 'data' in result:
return result['data']
return result
except json.JSONDecodeError as e:
_logger.error(f"Erreur lors de l'analyse de la réponse JSON: {str(e)}")
_logger.error(f"Contenu de la réponse: {response.text}")
self._update_api_log(api_log, {
'status_code': response.status_code,
'response_body': response.text,
'message': f'Error: Invalid JSON response - {str(e)}',
'status': 'error'
})
return False
except Exception as e:
# Log the error
_logger.exception(f"Erreur lors de la récupération des données des réseaux: {str(e)}")
self._update_api_log(api_log, {
'message': f'Error: {str(e)}',
'status': 'error'
})
return False
def _get_site_manager_network_data(self):
"""Get network data from UniFi Site Manager API
This method directly implements the network data retrieval logic for the Site Manager API type.
It was previously in the unifi.site.manager model, but has been integrated here.
Returns:
list: Network data from Site Manager API
"""
# Create a log entry for this API call
api_log = self._create_api_log('get_network_data', 'Getting network data from Site Manager API', 'outgoing')
try:
# Implement the Site Manager-specific API call logic here
# Make the API request and process the response
# For now, this is a placeholder
result = []
# Log the successful API call
self._update_api_log(api_log, {'message': 'Success: Retrieved network data', 'status': 'success'})
return result
except Exception as e:
# Log the error
self._update_api_log(api_log, {'message': f'Error: {str(e)}', 'status': 'error'})
_logger.error("Error getting network data from Site Manager API: %s", str(e))
return False
def _delegate_api_method(self, method_name):
"""Délègue l'appel d'une méthode à l'API appropriée
Cette méthode générique permet d'appeler la méthode interne appropriée
en fonction du type d'API configuré.
Args:
method_name: Nom de la méthode à appeler (sans le préfixe)
Returns:
Le résultat de la méthode appelée, ou False si le type d'API n'est pas pris en charge
"""
self.ensure_one()
# Déterminer le type d'API à utiliser
if self.api_type == 'controller':
# Utiliser l'implémentation Controller
controller_method = f"_get_controller_{method_name}"
if hasattr(self, controller_method):
return getattr(self, controller_method)()
else:
_logger.error(f"Méthode {controller_method} non implémentée")
return False
elif self.api_type == 'site_manager':
# Utiliser l'implémentation Site Manager
site_manager_method = f"_get_site_manager_{method_name}"
if hasattr(self, site_manager_method):
return getattr(self, site_manager_method)()
else:
_logger.error(f"Méthode {site_manager_method} non implémentée")
return False
else:
# Type d'API non pris en charge
_logger.error("Type d'API non pris en charge: %s", self.api_type)
return False
def get_vlan_data(self):
"""Récupère les données des VLANs du site
Cette méthode utilise l'API appropriée en fonction du type de site
pour obtenir les informations sur les VLANs.
Returns:
list: Liste des données de tous les VLANs
"""
return self._delegate_api_method('get_vlan_data')
def get_network_data(self):
"""Récupère les données des réseaux du site
Cette méthode utilise l'API appropriée en fonction du type de site
pour obtenir les informations sur les réseaux.
Returns:
list: Liste des données de tous les réseaux
"""
return self._delegate_api_method('get_network_data')
def get_user_data(self):
"""Récupère les données des utilisateurs du site
Cette méthode utilise l'API appropriée en fonction du type de site
pour obtenir les informations sur les utilisateurs.
Returns:
list: Liste des données de tous les utilisateurs
"""
return self._delegate_api_method('get_user_data')
def get_firewall_data(self):
"""Récupère les données des règles de pare-feu du site
Cette méthode utilise l'API appropriée en fonction du type de site
pour obtenir les informations sur les règles de pare-feu.
Returns:
list: Liste des données de toutes les règles de pare-feu
"""
return self._delegate_api_method('get_firewall_data')
def get_port_forward_data(self):
"""Récupère les données des redirections de port du site
Cette méthode utilise l'API appropriée en fonction du type de site
pour obtenir les informations sur les redirections de port.
Returns:
list: Liste des données de toutes les redirections de port
"""
return self._delegate_api_method('get_port_forward_data')
def get_system_info_data(self):
"""Récupère les données d'information système du site
Cette méthode utilise l'API appropriée en fonction du type de site
pour obtenir les informations système.
Returns:
dict: Données d'information système
"""
return self._delegate_api_method('get_system_info_data')
def action_import_site(self):
"""Lance directement l'import pour le site sélectionné
Cette méthode est appelée lorsque l'utilisateur clique sur le bouton
'Importer' dans la vue liste des sites UniFi. Elle déclenche
immédiatement le processus d'importation pour le site sélectionné.
Returns:
dict: Notification de succès ou d'échec
"""
self.ensure_one()
# Dans la version refactorisée, nous n'avons plus besoin de vérifier si des anciens modèles
# sont associés puisque toute la logique est maintenant intégrée dans ce modèle
# Vérifier si le site a déjà été configuré pour une API
if not self.api_type:
# Si aucun contrôleur ou gestionnaire de site n'est associé, ouvrir l'assistant d'importation
return {
'name': _('Import UniFi Site'),
'type': 'ir.actions.act_window',
'res_model': 'unifi.site.import.wizard',
'view_mode': 'form',
'target': 'new',
'context': {'default_name': self.name, 'default_site_id': self.site_id, 'default_api_type': self.api_type}
}
# Tester la connexion
connection_success = False
if self.api_type == 'controller':
connection_success = self._test_controller_connection()
elif self.api_type == 'site_manager':
connection_success = self._test_site_manager_connection()
if connection_success:
# Déclencher la synchronisation
self.action_sync_now()
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'title': _('Success'),
'message': _('Connection successful! Synchronization started for site %s.') % self.name,
'sticky': False,
'type': 'success',
}
}
else:
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'title': _('Error'),
'message': _('Failed to connect to site %s. Please check your connection settings.') % self.name,
'sticky': True,
'type': 'danger',
}
}