unifi to test

This commit is contained in:
xtremxpert 2025-03-10 10:09:39 -04:00
parent ec757e1883
commit 5bfcaa37c2
29 changed files with 3424 additions and 0 deletions

View file

@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
from . import models
from . import controllers

View file

@ -0,0 +1,46 @@
# -*- coding: utf-8 -*-
{
'name': 'Unifi Integration',
'version': '1.0',
'category': 'Network/Documentation',
'summary': 'Store and manage Unifi configurations',
'description': """
Unifi Integration
=================
This module allows you to:
* Connect to Unifi devices and retrieve configurations
* Store configuration history in the database
* Generate documentation of network setup
* Compare configurations over time
""",
'author': 'Your Company',
'website': 'https://www.bemade.org',
'depends': ['base', 'web', 'website'],
'data': [
'security/udm_pro_security.xml',
'security/ir.model.access.csv',
'views/udm_configuration_views.xml',
'views/udm_system_info_views.xml',
'views/udm_network_views.xml',
'views/udm_vlan_views.xml',
'views/udm_device_views.xml',
'views/udm_user_views.xml',
'views/udm_settings_views.xml',
'views/udm_firewall_views.xml',
'views/udm_menu_views.xml',
'views/templates.xml',
],
'demo': [],
'installable': True,
'application': True,
'auto_install': False,
'license': 'LGPL-3',
'external_dependencies': {
'python': ['requests'],
},
'assets': {
'web.assets_backend': [
'udm_pro_docs/static/src/css/udm_pro.css',
],
},
}

View file

@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
# Import du module principal des contrôleurs
from . import main # pylint: disable=relative-beyond-top-level

View file

@ -0,0 +1,691 @@
# -*- coding: utf-8 -*-
# pylint: disable=import-error
from odoo import http, _ # IDE peut signaler une erreur, mais fonctionne dans l'environnement Odoo
from odoo.http import request # IDE peut signaler une erreur, mais fonctionne dans l'environnement Odoo
# pylint: enable=import-error
import logging
import json # Nécessaire pour parser les réponses API et utilisé dans les méthodes de traitement
import requests
from requests.exceptions import RequestException, ConnectionError
from urllib3.exceptions import InsecureRequestWarning
from datetime import datetime
# Supprimer les avertissements pour les connexions non sécurisées
try:
# Pour les versions plus récentes de requests
import urllib3
urllib3.disable_warnings(category=InsecureRequestWarning)
except ImportError:
# Pour les versions plus anciennes de requests
try:
# Désactiver l'avertissement de sécurité de pylint pour cette ligne spécifique
# pylint: disable=no-member
requests.packages.urllib3.disable_warnings()
# pylint: enable=no-member
except AttributeError:
# Si ni l'un ni l'autre ne fonctionne, nous ignorons silencieusement
pass
_logger = logging.getLogger(__name__)
class UdmProController(http.Controller):
"""Contrôleur pour les fonctionnalités liées à UDM Pro dans Odoo"""
@http.route('/udm_pro/advanced_options', type='http', auth='user', website=True)
def advanced_options_form(self):
"""Affiche le formulaire des options avancées pour l'API UDM Pro"""
return request.render('udm_pro_docs.advanced_options_form', {
'default_site': 'default',
'fixed_only': True,
'lowercase_hostnames': True,
})
@http.route('/udm_pro/restart_device', type='http', auth='user', website=True)
def restart_device_form(self):
"""Affiche le formulaire pour redémarrer un appareil UDM Pro"""
if not request.env.user.has_group('udm_pro_docs.group_udm_pro_manager'):
return request.render('udm_pro_docs.access_denied', {
'error_message': _("You don't have permission to restart devices.")
})
# Récupérer les configurations UDM Pro enregistrées pour le formulaire
configs = request.env['udm.configuration'].sudo().search([])
return request.render('udm_pro_docs.restart_device_form', {
'configs': configs
})
@http.route('/udm_pro/restart_device', type='http', auth='user', website=True, methods=['POST'])
def restart_device(self, **post):
"""Traite la demande de redémarrage d'un appareil UDM Pro"""
if not request.env.user.has_group('udm_pro_docs.group_udm_pro_manager'):
return request.render('udm_pro_docs.access_denied', {
'error_message': _("You don't have permission to restart devices.")
})
config_id = int(post.get('config_id'))
mac_address = post.get('mac_address')
if not mac_address:
return request.render('udm_pro_docs.restart_device_form', {
'error_message': _("Please provide the MAC address of the device to restart."),
'configs': request.env['udm.configuration'].sudo().search([])
})
try:
# Récupérer la configuration
config = request.env['udm.configuration'].sudo().browse(config_id)
if not config.exists():
raise ValueError(_("Configuration not found"))
# Initialiser le client UDM Pro
client = UdmProClient(
host=config.host,
username=config.username,
password=config.password,
port=config.port or 443
)
# Authentifier le client
if not client.login():
return request.render('udm_pro_docs.restart_device_form', {
'error_message': _("Authentication failed. Please check the configuration credentials."),
'configs': request.env['udm.configuration'].sudo().search([])
})
# Redémarrer l'appareil
success = client.restart_device(mac_address)
if not success:
return request.render('udm_pro_docs.restart_device_form', {
'error_message': _("Failed to restart the device. Please check logs for details."),
'configs': request.env['udm.configuration'].sudo().search([])
})
return request.render('udm_pro_docs.restart_success', {
'mac_address': mac_address
})
except (ConnectionError, RequestException) as e:
_logger.error("Error during UDM Pro device restart: %s", str(e))
return request.render('udm_pro_docs.restart_device_form', {
'error_message': _("Error connecting to UDM Pro: %s") % str(e),
'configs': request.env['udm.configuration'].sudo().search([])
})
except Exception as e: # pylint: disable=broad-except
_logger.exception("Unexpected error during UDM Pro device restart")
return request.render('udm_pro_docs.restart_device_form', {
'error_message': _("An unexpected error occurred: %s") % str(e),
'configs': request.env['udm.configuration'].sudo().search([])
})
@http.route('/udm_pro/generate_hosts', type='http', auth='user', website=True)
def generate_hosts_form(self):
"""Affiche le formulaire pour générer un fichier hosts depuis UDM Pro"""
# Récupérer les configurations UDM Pro enregistrées pour le formulaire
configs = request.env['udm.configuration'].sudo().search([])
return request.render('udm_pro_docs.generate_hosts_form', {
'configs': configs,
'fixed_only': True,
'lowercase_hostnames': True
})
@http.route('/udm_pro/generate_hosts', type='http', auth='user', website=True, methods=['POST'])
def generate_hosts(self, **post):
"""Génère un fichier hosts à partir des clients réseau UDM Pro"""
config_id = int(post.get('config_id'))
fixed_only = post.get('fixed_only') == 'on'
lowercase_hostnames = post.get('lowercase_hostnames') == 'on'
try:
# Récupérer la configuration
config = request.env['udm.configuration'].sudo().browse(config_id)
if not config.exists():
raise ValueError(_("Configuration not found"))
# Initialiser le client UDM Pro avec les options avancées
client = UdmProClient(
host=config.host,
username=config.username,
password=config.password,
port=config.port or 443,
fixed_only=fixed_only,
lowercase_hostnames=lowercase_hostnames
)
# Authentifier le client
if not client.login():
return request.render('udm_pro_docs.generate_hosts_form', {
'error_message': _("Authentication failed. Please check the configuration credentials."),
'configs': request.env['udm.configuration'].sudo().search([]),
'fixed_only': fixed_only,
'lowercase_hostnames': lowercase_hostnames
})
# Générer le fichier hosts
hosts_content = client.generate_hosts_file()
# Retourner le contenu sous forme de fichier à télécharger
response = request.make_response(hosts_content)
response.headers['Content-Type'] = 'text/plain'
response.headers['Content-Disposition'] = 'attachment; filename=udm_hosts.txt'
return response
except (ConnectionError, RequestException) as e:
_logger.error("Error during UDM Pro hosts file generation: %s", str(e))
return request.render('udm_pro_docs.generate_hosts_form', {
'error_message': _("Error connecting to UDM Pro: %s") % str(e),
'configs': request.env['udm.configuration'].sudo().search([]),
'fixed_only': fixed_only,
'lowercase_hostnames': lowercase_hostnames
})
except Exception as e: # pylint: disable=broad-except
_logger.exception("Unexpected error during UDM Pro hosts file generation")
return request.render('udm_pro_docs.generate_hosts_form', {
'error_message': _("An unexpected error occurred: %s") % str(e),
'configs': request.env['udm.configuration'].sudo().search([]),
'fixed_only': fixed_only,
'lowercase_hostnames': lowercase_hostnames
})
@http.route('/udm_pro/network_clients', type='http', auth='user', website=True)
def network_clients_form(self):
"""Affiche le formulaire pour consulter les clients réseau UDM Pro"""
# Récupérer les configurations UDM Pro enregistrées pour le formulaire
configs = request.env['udm.configuration'].sudo().search([])
return request.render('udm_pro_docs.network_clients_form', {
'configs': configs,
'fixed_only': False,
'lowercase_hostnames': True
})
@http.route('/udm_pro/network_clients', type='http', auth='user', website=True, methods=['POST'])
def get_network_clients(self, **post):
"""Récupère et affiche la liste des clients réseau UDM Pro"""
config_id = int(post.get('config_id'))
fixed_only = post.get('fixed_only') == 'on'
lowercase_hostnames = post.get('lowercase_hostnames') == 'on'
try:
# Récupérer la configuration
config = request.env['udm.configuration'].sudo().browse(config_id)
if not config.exists():
raise ValueError(_("Configuration not found"))
# Initialiser le client UDM Pro avec les options avancées
client = UdmProClient(
host=config.host,
username=config.username,
password=config.password,
port=config.port or 443,
fixed_only=fixed_only,
lowercase_hostnames=lowercase_hostnames
)
# Authentifier le client
if not client.login():
return request.render('udm_pro_docs.network_clients_form', {
'error_message': _("Authentication failed. Please check the configuration credentials."),
'configs': request.env['udm.configuration'].sudo().search([]),
'fixed_only': fixed_only,
'lowercase_hostnames': lowercase_hostnames
})
# Récupérer les clients réseau
network_clients = client.get_network_clients()
return request.render('udm_pro_docs.network_clients_result', {
'clients': network_clients,
'config': config
})
except (ConnectionError, RequestException) as e:
_logger.error("Error retrieving UDM Pro network clients: %s", str(e))
return request.render('udm_pro_docs.network_clients_form', {
'error_message': _("Error connecting to UDM Pro: %s") % str(e),
'configs': request.env['udm.configuration'].sudo().search([]),
'fixed_only': fixed_only,
'lowercase_hostnames': lowercase_hostnames
})
except ValueError as e:
_logger.error("Value error retrieving UDM Pro network clients: %s", str(e))
return request.render('udm_pro_docs.network_clients_form', {
'error_message': _("Configuration error: %s") % str(e),
'configs': request.env['udm.configuration'].sudo().search([]),
'fixed_only': fixed_only,
'lowercase_hostnames': lowercase_hostnames
})
except (AttributeError, KeyError) as e:
_logger.error("Data format error retrieving UDM Pro network clients: %s", str(e))
return request.render('udm_pro_docs.network_clients_form', {
'error_message': _("Data error: %s") % str(e),
'configs': request.env['udm.configuration'].sudo().search([]),
'fixed_only': fixed_only,
'lowercase_hostnames': lowercase_hostnames
})
except Exception as e: # pylint: disable=broad-except
# Conserver cette exception générique comme dernier recours, avec un avertissement explicite pour pylint
_logger.exception("Unexpected error retrieving UDM Pro network clients")
return request.render('udm_pro_docs.network_clients_form', {
'error_message': _("An unexpected error occurred: %s") % str(e),
'configs': request.env['udm.configuration'].sudo().search([]),
'fixed_only': fixed_only,
'lowercase_hostnames': lowercase_hostnames
})
@http.route('/udm_pro/import_config', type='http', auth='user', website=True)
def import_config_form(self):
"""Affiche le formulaire d'importation de configuration UDM Pro"""
return request.render('udm_pro_docs.import_config_form', {})
@http.route('/udm_pro/import_config', type='http', auth='user', website=True, methods=['POST'])
def import_config(self, **post):
"""Importe une configuration UDM Pro depuis l'appareil"""
if not request.env.user.has_group('udm_pro_docs.group_udm_pro_manager'):
return request.render('udm_pro_docs.access_denied', {
'error_message': _("You don't have permission to import configurations.")
})
host = post.get('host')
username = post.get('username')
password = post.get('password')
port = int(post.get('port') or 443)
if not all([host, username, password]):
return request.render('udm_pro_docs.import_config_form', {
'error_message': _("Please provide all required fields."),
'host': host,
'username': username,
'port': port
})
try:
# Utiliser le client API pour récupérer la configuration
client = UdmProClient(host, username, password, port)
if not client.login():
return request.render('udm_pro_docs.import_config_form', {
'error_message': _("Authentication failed. Please check your credentials."),
'host': host,
'username': username,
'port': port
})
config_data = client.get_full_configuration()
# Importer la configuration dans Odoo
config_id = request.env['udm.configuration'].sudo().import_configuration(config_data)
return request.redirect('/web#id=%s&model=udm.configuration&view_type=form' % config_id)
except (ConnectionError, RequestException) as e:
_logger.error("Error during UDM Pro configuration import: %s", str(e))
return request.render('udm_pro_docs.import_config_form', {
'error_message': _("Error connecting to UDM Pro: %s") % str(e),
'host': host,
'username': username,
'port': port
})
except (ValueError, TypeError, AttributeError) as e:
_logger.error("Data processing error during UDM Pro configuration import: %s", str(e))
return request.render('udm_pro_docs.import_config_form', {
'error_message': _("Error processing data: %s") % str(e),
'host': host,
'username': username,
'port': port
})
except Exception as e: # pylint: disable=broad-except
_logger.exception("Unexpected error during UDM Pro configuration import")
return request.render('udm_pro_docs.import_config_form', {
'error_message': _("An unexpected error occurred. Please check server logs."),
'host': host,
'username': username,
'port': port
})
class UdmProClient:
"""Client pour interagir avec l'API UDM Pro."""
# Points d'accès de l'API
API_LOGIN_ENDPOINT = '/api/auth/login'
API_SYSTEM_INFO_ENDPOINT = '/api/system'
API_NETWORK_ENDPOINT = '/api/networks'
API_DEVICES_ENDPOINT = '/api/devices'
API_USERS_ENDPOINT = '/api/users'
API_SETTINGS_ENDPOINT = '/api/settings'
API_FIREWALL_ENDPOINT = '/api/firewall'
# Nouveaux endpoints inspirés du client Go
API_ACTIVE_CLIENTS_ENDPOINT = '/proxy/network/api/s/{site}/stat/sta'
API_CONFIGURED_CLIENTS_ENDPOINT = '/proxy/network/api/s/{site}/list/user'
API_DEVICE_RESTART_ENDPOINT = '/proxy/network/api/s/{site}/cmd/devmgr'
def __init__(self, host, username, password, port=443, verify_ssl=False, site='default', fixed_only=True, lowercase_hostnames=True, debug=False):
"""
Initialise le client API UDM Pro.
Args:
host (str): Adresse IP ou nom d'hôte de l'UDM Pro
username (str): Nom d'utilisateur pour l'API
password (str): Mot de passe pour l'API
port (int): Port pour la connexion (par défaut 443)
verify_ssl (bool): Vérifier le certificat SSL (par défaut False)
site (str): Identifiant du site pour l'API UniFi (par défaut 'default')
fixed_only (bool): Ne considérer que les clients avec adresse IP fixe (par défaut True)
lowercase_hostnames (bool): Convertir les noms d'hôtes en minuscules (par défaut True)
debug (bool): Activer le mode débogage pour les requêtes HTTP (par défaut False)
"""
self.host = host
self.username = username
self.password = password
self.port = port
self.verify_ssl = verify_ssl
self.site = site
self.fixed_only = fixed_only
self.lowercase_hostnames = lowercase_hostnames
self.debug = debug
self.base_url = f"https://{host}:{port}"
self.token = None
self.csrf_token = None
self.session = requests.Session()
self.session.verify = verify_ssl
def _get_auth_headers(self):
"""Retourne les en-têtes d'authentification."""
if not self.token:
raise ValueError("Non authentifié. Appelez login() d'abord.")
headers = {
"Authorization": f"Bearer {self.token}",
"Content-Type": "application/json"
}
# Ajouter le token CSRF si disponible
if self.csrf_token:
headers["X-Csrf-Token"] = self.csrf_token
return headers
def login(self):
"""
Authentifie le client auprès de l'API UDM Pro.
Returns:
bool: True si l'authentification a réussi, False sinon
"""
try:
login_url = f"{self.base_url}{self.API_LOGIN_ENDPOINT}"
payload = {
"username": self.username,
"password": self.password
}
_logger.debug("Tentative de connexion à %s", login_url)
response = self.session.post(login_url, json=payload)
response.raise_for_status()
# Capture du token CSRF s'il existe
csrf_token = response.headers.get('X-Csrf-Token')
if csrf_token:
self.csrf_token = csrf_token
_logger.debug("Token CSRF capturé: %s", csrf_token)
data = response.json()
if not data.get('token'):
_logger.error("Aucun token n'a été retourné par l'API")
return False
self.token = data['token']
_logger.debug("Authentification réussie")
return True
except RequestException as e:
_logger.error("Erreur d'authentification: %s", str(e))
return False
def _make_api_request(self, method, endpoint, params=None, data=None, retry=True):
"""
Effectue une requête API.
Args:
method (str): Méthode HTTP (GET, POST, etc.)
endpoint (str): Point d'accès API
params (dict): Paramètres de requête
data (dict): Données à envoyer dans le corps de la requête
retry (bool): Réessayer en cas d'erreur d'authentification
Returns:
dict: Réponse JSON de l'API
"""
if not self.token and retry:
_logger.debug("Non authentifié, tentative d'authentification")
if not self.login():
raise ConnectionError("Impossible de s'authentifier à l'API UDM Pro")
url = f"{self.base_url}{endpoint}"
try:
_logger.debug("Requête %s vers %s", method, url)
headers = self._get_auth_headers()
response = self.session.request(
method=method,
url=url,
headers=headers,
params=params,
json=data
)
# Capture du token CSRF s'il existe dans la réponse
csrf_token = response.headers.get('X-Csrf-Token')
if csrf_token and csrf_token != self.csrf_token:
self.csrf_token = csrf_token
_logger.debug("Token CSRF mis à jour: %s", csrf_token)
response.raise_for_status()
return response.json()
except RequestException as e:
if response.status_code == 401 or response.status_code == 403:
if retry:
_logger.debug("Token expiré, nouvelle tentative d'authentification")
self.token = None
return self._make_api_request(method, endpoint, params, data, retry=False)
_logger.error("Erreur API (%s %s): %s", method, url, str(e))
raise
def get_system_info(self):
"""
Récupère les informations système de l'UDM Pro.
Returns:
dict: Informations système
"""
return self._make_api_request('GET', self.API_SYSTEM_INFO_ENDPOINT)
def get_networks(self):
"""
Récupère la configuration des réseaux.
Returns:
dict: Configuration des réseaux
"""
return self._make_api_request('GET', self.API_NETWORK_ENDPOINT)
def get_devices(self):
"""
Récupère la liste des périphériques.
Returns:
dict: Liste des périphériques
"""
return self._make_api_request('GET', self.API_DEVICES_ENDPOINT)
def get_users(self):
"""
Récupère la liste des utilisateurs.
Returns:
dict: Liste des utilisateurs
"""
return self._make_api_request('GET', self.API_USERS_ENDPOINT)
def get_settings(self):
"""
Récupère les paramètres généraux.
Returns:
dict: Paramètres généraux
"""
return self._make_api_request('GET', self.API_SETTINGS_ENDPOINT)
def get_firewall_rules(self):
"""
Récupère les règles de pare-feu.
Returns:
dict: Règles de pare-feu
"""
return self._make_api_request('GET', self.API_FIREWALL_ENDPOINT)
def get_active_clients(self):
"""
Récupère la liste des clients actuellement connectés.
Returns:
list: Liste des clients actifs
"""
endpoint = self.API_ACTIVE_CLIENTS_ENDPOINT.format(site=self.site)
response = self._make_api_request('GET', endpoint)
if not response or 'data' not in response:
return []
return response.get('data', [])
def get_configured_clients(self):
"""
Récupère la liste des clients configurés statiquement.
Returns:
list: Liste des clients configurés
"""
endpoint = self.API_CONFIGURED_CLIENTS_ENDPOINT.format(site=self.site)
response = self._make_api_request('GET', endpoint)
if not response or 'data' not in response:
return []
return response.get('data', [])
def restart_device(self, mac_address):
"""
Redémarre un appareil géré par l'UDM Pro (ex: point d'accès WiFi).
Nécessite des permissions de niveau 'admin du site'.
Args:
mac_address (str): Adresse MAC de l'appareil à redémarrer
Returns:
bool: True si l'opération a réussi, False sinon
"""
endpoint = self.API_DEVICE_RESTART_ENDPOINT.format(site=self.site)
payload = {
'mac': mac_address,
'reboot_type': 'soft',
'cmd': 'restart'
}
try:
response = self._make_api_request('POST', endpoint, data=payload)
if response and response.get('meta', {}).get('rc') == 'ok':
return True
return False
except Exception as e: # pylint: disable=broad-except
_logger.error("Erreur lors du redémarrage de l'appareil %s: %s", mac_address, str(e))
return False
def get_network_clients(self):
"""
Récupère tous les clients réseau (actifs et/ou configurés selon les paramètres).
Returns:
list: Liste des clients réseau
"""
clients = []
# Toujours inclure les clients configurés statiquement
configured_clients = self.get_configured_clients()
clients.extend(configured_clients)
# Inclure les clients actifs si fixed_only est False
if not self.fixed_only:
active_clients = self.get_active_clients()
clients.extend(active_clients)
# Traitement des noms d'hôtes si nécessaire
if self.lowercase_hostnames:
for client in clients:
if client.get('hostname'):
client['hostname'] = client['hostname'].lower()
if client.get('name'):
client['name'] = client['name'].lower()
return clients
def generate_hosts_file(self):
"""
Génère un fichier hosts à partir des clients réseau.
Returns:
str: Contenu du fichier hosts
"""
clients = self.get_network_clients()
hosts_content = "# UDM Pro Generated Hosts File\n"
hosts_content += "# Generated on {}\n\n".format(datetime.now().strftime('%Y-%m-%d %H:%M:%S'))
for client in clients:
name = client.get('name') or client.get('hostname')
ip = client.get('fixed_ip') or client.get('ip')
mac = client.get('mac', '')
if name and ip:
hosts_content += "{:16s} {:30s} # {}\n".format(ip, name, mac)
return hosts_content
def get_full_configuration(self):
"""
Récupère la configuration complète de l'UDM Pro.
Returns:
dict: Configuration complète de l'UDM Pro
"""
# S'authentifier d'abord
if not self.token and not self.login():
raise ConnectionError("Échec de l'authentification pour la récupération de la configuration complète")
try:
# Récupérer chaque partie de la configuration
system_info = self.get_system_info()
networks = self.get_networks()
devices = self.get_devices()
users = self.get_users()
settings = self.get_settings()
firewall = self.get_firewall_rules()
# Récupérer également les clients réseau (nouvelle fonctionnalité)
network_clients = self.get_network_clients()
# Combiner toutes les parties dans un seul dictionnaire
return {
'system_info': system_info,
'networks': networks,
'devices': devices,
'users': users,
'settings': settings,
'firewall': firewall,
'network_clients': network_clients
}
except RequestException as e:
_logger.error("Erreur lors de la récupération de la configuration complète: %s", str(e))
raise

View file

@ -0,0 +1,9 @@
# -*- coding: utf-8 -*-
from . import udm_config
from . import udm_system_info
from . import udm_network
from . import udm_device
from . import udm_user
from . import udm_settings
from . import udm_firewall

View file

@ -0,0 +1,542 @@
# -*- coding: utf-8 -*-
# Ces importations fonctionneront dans un environnement Odoo, même si votre IDE les signale comme non trouvées
# pylint: disable=import-error
from odoo import models, fields, api, _
from odoo.exceptions import UserError
# pylint: enable=import-error
import logging
import json
from datetime import datetime
import random # Pour générer des données de test/démonstration
_logger = logging.getLogger(__name__)
class UdmSite(models.Model):
"""Représente un site UniFi géré par un ou plusieurs UDM Pro"""
_name = 'udm.site'
_description = 'UDM Site'
_order = 'name'
name = fields.Char(string='Name', required=True)
site_id = fields.Char(string='Site ID', help="Identifiant du site dans UniFi (généralement 'default' sauf si configuré autrement)", default='default')
description = fields.Text(string='Description')
address = fields.Text(string='Physical Address')
active = fields.Boolean(string='Active', default=True)
# Relations
configuration_ids = fields.One2many('udm.configuration', 'site_id', string='Configurations')
dashboard_ids = fields.One2many('udm.dashboard.metric', 'site_id', string='Dashboard Metrics')
# Compteurs
config_count = fields.Integer(compute='_compute_counts', string='Configuration Count')
device_count = fields.Integer(compute='_compute_device_count', string='Total Devices')
client_count = fields.Integer(compute='_compute_client_count', string='Connected Clients')
@api.depends('configuration_ids')
def _compute_counts(self):
for record in self:
record.config_count = len(record.configuration_ids)
@api.depends('configuration_ids.device_ids')
def _compute_device_count(self):
for record in self:
count = 0
for config in record.configuration_ids:
count += len(config.device_ids)
record.device_count = count
@api.depends('dashboard_ids')
def _compute_client_count(self):
for record in self:
client_metric = record.dashboard_ids.filtered(lambda m: m.metric_type == 'clients_count')
if client_metric and len(client_metric) > 0:
record.client_count = int(client_metric[0].current_value)
else:
record.client_count = 0
def action_view_configurations(self):
self.ensure_one()
return {
'name': _('Configurations'),
'view_mode': 'tree,form',
'res_model': 'udm.configuration',
'domain': [('site_id', '=', self.id)],
'type': 'ir.actions.act_window',
}
def action_view_dashboard(self):
self.ensure_one()
return {
'name': _('Site Dashboard'),
'view_mode': 'dashboard,form',
'res_model': 'udm.site',
'res_id': self.id,
'type': 'ir.actions.act_window',
}
def action_refresh_metrics(self):
"""Rafraîchit les métriques du tableau de bord pour ce site"""
self.ensure_one()
configs = self.configuration_ids.filtered(lambda c: c.active)
if not configs:
raise UserError(_('No active UDM Pro configuration found for this site'))
# Dans une implémentation réelle, vous appelleriez l'API UDM Pro ici
# Pour le moment, nous simulons les métriques
self._generate_sample_metrics()
return {
'type': 'ir.actions.client',
'tag': 'reload',
}
def _generate_sample_metrics(self):
"""Génère des métriques de démonstration pour le tableau de bord"""
# Supprimer les anciennes métriques
self.dashboard_ids.unlink()
# Types de métriques à générer
metric_types = [
'bandwidth_usage', 'cpu_usage', 'memory_usage', 'clients_count',
'wan_status', 'threat_count', 'device_status'
]
# Générer les nouvelles métriques
metrics_vals = []
now = fields.Datetime.now()
for metric_type in metric_types:
# Générer la valeur actuelle
current_value = ''
max_value = ''
history = ''
if metric_type == 'bandwidth_usage':
current_value = str(random.uniform(5, 200)) # Mbps
max_value = '1000'
history = self._generate_history_data(50, 250, 24)
elif metric_type in ['cpu_usage', 'memory_usage']:
current_value = str(random.uniform(10, 90)) # Pourcentage
max_value = '100'
history = self._generate_history_data(10, 90, 24)
elif metric_type == 'clients_count':
current_value = str(random.randint(5, 50))
max_value = '100'
history = self._generate_history_data(5, 60, 24, integer=True)
elif metric_type == 'threat_count':
current_value = str(random.randint(0, 10))
max_value = '100'
history = self._generate_history_data(0, 15, 24, integer=True)
elif metric_type == 'wan_status':
current_value = random.choice(['up', 'up']) # Principalement en ligne
max_value = ''
history = ''
elif metric_type == 'device_status':
total = random.randint(5, 20)
offline = random.randint(0, 2)
current_value = f"{total-offline}/{total}"
max_value = str(total)
history = ''
metrics_vals.append({
'site_id': self.id,
'metric_type': metric_type,
'current_value': current_value,
'max_value': max_value,
'history_data': history,
'last_update': now,
})
# Créer les métriques
for vals in metrics_vals:
self.env['udm.dashboard.metric'].create(vals)
def _generate_history_data(self, min_val, max_val, points, integer=False):
"""Génère des données historiques pour les graphiques"""
data = []
for _ in range(points): # Utilisation de _ pour indiquer une variable non utilisée
if integer:
value = random.randint(min_val, max_val)
else:
value = random.uniform(min_val, max_val)
value = round(value, 2)
data.append(value)
return json.dumps(data)
class UdmDashboardMetric(models.Model):
"""Stocke les métriques pour le tableau de bord UDM Pro"""
_name = 'udm.dashboard.metric'
_description = 'UDM Dashboard Metric'
_order = 'last_update desc'
site_id = fields.Many2one('udm.site', string='Site', required=True, ondelete='cascade')
metric_type = fields.Selection([
('bandwidth_usage', 'Bandwidth Usage'),
('cpu_usage', 'CPU Usage'),
('memory_usage', 'Memory Usage'),
('clients_count', 'Connected Clients'),
('wan_status', 'WAN Status'),
('threat_count', 'Security Threats'),
('device_status', 'Device Status'),
], string='Metric Type', required=True)
current_value = fields.Char(string='Current Value', required=True)
max_value = fields.Char(string='Maximum Value')
history_data = fields.Text(string='Historical Data', help="Données JSON pour les graphiques d'historique")
last_update = fields.Datetime(string='Last Update', default=fields.Datetime.now)
# Champs calculés pour l'affichage
formatted_value = fields.Char(compute='_compute_formatted_value', string='Formatted Value')
status = fields.Selection([
('normal', 'Normal'),
('warning', 'Warning'),
('critical', 'Critical'),
], compute='_compute_status', string='Status')
@api.depends('current_value', 'metric_type')
def _compute_formatted_value(self):
for record in self:
if record.metric_type == 'bandwidth_usage':
try:
value = float(record.current_value)
if value >= 1000:
record.formatted_value = f"{value/1000:.2f} Gbps"
else:
record.formatted_value = f"{value:.2f} Mbps"
except (ValueError, TypeError):
record.formatted_value = record.current_value
elif record.metric_type in ['cpu_usage', 'memory_usage']:
try:
value = float(record.current_value)
record.formatted_value = f"{value:.1f}%"
except (ValueError, TypeError):
record.formatted_value = record.current_value
elif record.metric_type == 'wan_status':
if record.current_value == 'up':
record.formatted_value = _('Online')
else:
record.formatted_value = _('Offline')
else:
record.formatted_value = record.current_value
@api.depends('current_value', 'max_value', 'metric_type')
def _compute_status(self):
for record in self:
if record.metric_type in ['cpu_usage', 'memory_usage']:
try:
value = float(record.current_value)
if value > 90:
record.status = 'critical'
elif value > 70:
record.status = 'warning'
else:
record.status = 'normal'
except (ValueError, TypeError):
record.status = 'normal'
elif record.metric_type == 'bandwidth_usage':
try:
value = float(record.current_value)
max_value = float(record.max_value) if record.max_value else 1000
if value > max_value * 0.9:
record.status = 'critical'
elif value > max_value * 0.7:
record.status = 'warning'
else:
record.status = 'normal'
except (ValueError, TypeError):
record.status = 'normal'
elif record.metric_type == 'threat_count':
try:
value = int(record.current_value)
if value > 5:
record.status = 'critical'
elif value > 0:
record.status = 'warning'
else:
record.status = 'normal'
except (ValueError, TypeError):
record.status = 'normal'
elif record.metric_type == 'wan_status':
if record.current_value == 'up':
record.status = 'normal'
else:
record.status = 'critical'
else:
record.status = 'normal'
class UdmConfiguration(models.Model):
"""Configuration complète d'un UDM Pro stockée dans Odoo"""
_name = 'udm.configuration'
_description = 'UDM Pro Configuration'
_order = 'timestamp desc'
name = fields.Char(string='Name', compute='_compute_name', store=True)
timestamp = fields.Datetime(string='Timestamp', default=fields.Datetime.now, required=True)
raw_data = fields.Text(string='Raw Data', help="Les données brutes de configuration en format JSON")
active = fields.Boolean(string='Active', default=True, help="Indique si cette configuration est actuellement active")
# Connexion à l'UDM Pro
host = fields.Char(string='Host', help="Adresse IP ou nom d'hôte de l'UDM Pro")
port = fields.Integer(string='Port', default=443)
username = fields.Char(string='Username')
password = fields.Char(string='Password')
# Relations
site_id = fields.Many2one('udm.site', string='Site', ondelete='restrict')
system_info_id = fields.Many2one('udm.system.info', string='System Info', ondelete='cascade')
network_ids = fields.One2many('udm.network', 'config_id', string='Networks')
vlan_ids = fields.One2many('udm.vlan', 'config_id', string='VLANs')
device_ids = fields.One2many('udm.device', 'config_id', string='Devices')
user_ids = fields.One2many('udm.user', 'config_id', string='Users')
settings_id = fields.Many2one('udm.settings', string='Settings', ondelete='cascade')
firewall_rule_ids = fields.One2many('udm.firewall.rule', 'config_id', string='Firewall Rules')
# Statistiques
network_count = fields.Integer(compute='_compute_counts', string='Network Count')
device_count = fields.Integer(compute='_compute_counts', string='Device Count')
user_count = fields.Integer(compute='_compute_counts', string='User Count')
firewall_rule_count = fields.Integer(compute='_compute_counts', string='Firewall Rule Count')
@api.depends('timestamp', 'system_info_id.hostname')
def _compute_name(self):
for record in self:
hostname = record.system_info_id.hostname or 'Unknown'
timestamp = record.timestamp.strftime('%Y-%m-%d %H:%M:%S') if record.timestamp else ''
record.name = f"{hostname} ({timestamp})"
@api.depends('network_ids', 'device_ids', 'user_ids', 'firewall_rule_ids')
def _compute_counts(self):
for record in self:
record.network_count = len(record.network_ids)
record.device_count = len(record.device_ids)
record.user_count = len(record.user_ids)
record.firewall_rule_count = len(record.firewall_rule_ids)
def action_view_networks(self):
self.ensure_one()
return {
'name': _('Networks'),
'view_mode': 'tree,form',
'res_model': 'udm.network',
'domain': [('config_id', '=', self.id)],
'type': 'ir.actions.act_window',
}
def action_view_devices(self):
self.ensure_one()
return {
'name': _('Devices'),
'view_mode': 'tree,form',
'res_model': 'udm.device',
'domain': [('config_id', '=', self.id)],
'type': 'ir.actions.act_window',
}
def action_view_users(self):
self.ensure_one()
return {
'name': _('Users'),
'view_mode': 'tree,form',
'res_model': 'udm.user',
'domain': [('config_id', '=', self.id)],
'type': 'ir.actions.act_window',
}
def action_view_firewall_rules(self):
self.ensure_one()
return {
'name': _('Firewall Rules'),
'view_mode': 'tree,form',
'res_model': 'udm.firewall.rule',
'domain': [('config_id', '=', self.id)],
'type': 'ir.actions.act_window',
}
@api.model
def import_configuration(self, config_data):
"""
Importe une configuration UDM Pro complète dans Odoo
Args:
config_data (dict): Données de configuration brutes de l'API
Returns:
int: ID de la configuration créée
"""
if not config_data:
raise UserError(_("No configuration data provided"))
# Créer la configuration principale
vals = {
'timestamp': datetime.now(),
'raw_data': json.dumps(config_data, indent=2, ensure_ascii=False),
}
config = self.create(vals)
# Créer les informations système
system_info_data = config_data.get('system_info', {})
if system_info_data:
system_info = self.env['udm.system.info'].create({
'config_id': config.id,
'hostname': system_info_data.get('hostname', ''),
'version': system_info_data.get('version', ''),
'model': system_info_data.get('model', ''),
'uptime': system_info_data.get('uptime', 0),
'serial': system_info_data.get('serialNumber', ''),
'mac_address': system_info_data.get('macAddress', ''),
'raw_data': json.dumps(system_info_data, indent=2, ensure_ascii=False),
})
config.system_info_id = system_info.id
# Créer les réseaux
networks_data = config_data.get('networks', {}).get('networks', [])
for network_data in networks_data:
if isinstance(network_data, dict):
self.env['udm.network'].create({
'config_id': config.id,
'name': network_data.get('name', ''),
'purpose': network_data.get('purpose', ''),
'subnet': network_data.get('subnet', ''),
'vlan_id_number': network_data.get('vlanId'),
'dhcp_enabled': network_data.get('dhcpEnabled', False),
'dhcp_start': network_data.get('dhcpStart', ''),
'dhcp_stop': network_data.get('dhcpStop', ''),
'domain_name': network_data.get('domainName', ''),
'raw_data': json.dumps(network_data, indent=2, ensure_ascii=False),
})
# Créer les VLANs
vlans_data = config_data.get('networks', {}).get('vlans', [])
for vlan_data in vlans_data:
if isinstance(vlan_data, dict):
self.env['udm.vlan'].create({
'config_id': config.id,
'vlan_id': vlan_data.get('id', 0),
'name': vlan_data.get('name', ''),
'raw_data': json.dumps(vlan_data, indent=2, ensure_ascii=False),
})
# Créer les périphériques
devices_data = config_data.get('devices', {}).get('devices', [])
for device_data in devices_data:
if isinstance(device_data, dict):
self.env['udm.device'].create({
'config_id': config.id,
'name': device_data.get('name', ''),
'mac': device_data.get('mac', ''),
'ip': device_data.get('ip', ''),
'device_type': device_data.get('type', ''),
'model': device_data.get('model', ''),
'last_seen': datetime.fromtimestamp(device_data.get('lastSeen', 0)),
'raw_data': json.dumps(device_data, indent=2, ensure_ascii=False),
})
# Créer les utilisateurs
users_data = config_data.get('users', {}).get('users', [])
for user_data in users_data:
if isinstance(user_data, dict):
self.env['udm.user'].create({
'config_id': config.id,
'name': user_data.get('name', ''),
'email': user_data.get('email', ''),
'role': user_data.get('role', ''),
'enabled': user_data.get('enabled', True),
'raw_data': json.dumps(user_data, indent=2, ensure_ascii=False),
})
# Créer les paramètres
settings_data = config_data.get('settings', {})
if settings_data:
settings = self.env['udm.settings'].create({
'config_id': config.id,
'timezone': settings_data.get('timezone', ''),
'ntp_servers': ','.join(settings_data.get('ntpServers', [])),
'dns_servers': ','.join(settings_data.get('dnsServers', [])),
'raw_data': json.dumps(settings_data, indent=2, ensure_ascii=False),
})
config.settings_id = settings.id
# Créer les règles de pare-feu
firewall_data = config_data.get('firewall', {}).get('rules', [])
for rule_data in firewall_data:
if isinstance(rule_data, dict):
self.env['udm.firewall.rule'].create({
'config_id': config.id,
'name': rule_data.get('name', ''),
'description': rule_data.get('description', ''),
'action': rule_data.get('action', 'drop'),
'protocol': rule_data.get('protocol', ''),
'source': rule_data.get('source', ''),
'destination': rule_data.get('destination', ''),
'enabled': rule_data.get('enabled', True),
'raw_data': json.dumps(rule_data, indent=2, ensure_ascii=False),
})
# Journaliser l'importation réussie
_logger.info('Successfully imported UDM Pro configuration: %s', config.name)
return config.id
def action_compare_with(self):
"""Ouvre un assistant pour comparer cette configuration avec une autre"""
self.ensure_one()
return {
'name': _('Compare Configurations'),
'type': 'ir.actions.act_window',
'res_model': 'udm.configuration.compare.wizard',
'view_mode': 'form',
'target': 'new',
'context': {
'default_source_config_id': self.id,
},
}
def action_duplicate(self):
"""Duplique cette configuration"""
self.ensure_one()
# Créer une nouvelle configuration en copiant les données brutes
new_config = self.copy({
'timestamp': datetime.now(),
'name': _('%s (Copy)') % self.name,
})
return {
'name': _('Duplicated Configuration'),
'type': 'ir.actions.act_window',
'res_model': 'udm.configuration',
'res_id': new_config.id,
'view_mode': 'form',
}
def action_generate_report(self):
"""Génère un rapport PDF de cette configuration"""
self.ensure_one()
return self.env.ref('udm_pro_docs.action_report_udm_configuration').report_action(self)
def action_view_dashboard(self):
"""Affiche le tableau de bord pour le site de cette configuration"""
self.ensure_one()
if not self.site_id:
raise UserError(_('This configuration is not associated with any site. Please set a site first.'))
return {
'name': _('Site Dashboard'),
'view_mode': 'dashboard,form',
'res_model': 'udm.site',
'res_id': self.site_id.id,
'type': 'ir.actions.act_window',
}
def action_update_dashboard_metrics(self):
"""Met à jour les métriques du tableau de bord pour le site de cette configuration"""
self.ensure_one()
if not self.site_id:
raise UserError(_('This configuration is not associated with any site. Please set a site first.'))
# Appeler la méthode de rafraîchissement des métriques du site
return self.site_id.action_refresh_metrics()

View file

@ -0,0 +1,42 @@
# -*- coding: utf-8 -*-
from odoo import models, fields, api, _
class UdmDevice(models.Model):
"""Représentation d'un périphérique dans l'UDM Pro"""
_name = 'udm.device'
_description = 'UDM Pro Device'
config_id = fields.Many2one('udm.configuration', string='Configuration', ondelete='cascade')
name = fields.Char(string='Name')
mac = fields.Char(string='MAC Address')
ip = fields.Char(string='IP Address')
device_type = fields.Char(string='Device Type')
model = fields.Char(string='Model')
last_seen = fields.Datetime(string='Last Seen')
raw_data = fields.Text(string='Raw Data')
# Champs calculés
status = fields.Selection([
('online', 'Online'),
('offline', 'Offline'),
('unknown', 'Unknown')
], string='Status', compute='_compute_status', store=True)
@api.depends('last_seen')
def _compute_status(self):
# Calcul du statut basé sur la dernière fois où le périphérique a été vu
# Ceci est un exemple simplifié
for record in self:
if not record.last_seen:
record.status = 'unknown'
continue
from datetime import datetime, timedelta
now = fields.Datetime.now()
time_diff = now - record.last_seen
if time_diff <= timedelta(minutes=10):
record.status = 'online'
else:
record.status = 'offline'

View file

@ -0,0 +1,56 @@
# -*- coding: utf-8 -*-
from odoo import models, fields, api, _
class UdmFirewallRule(models.Model):
"""Représentation d'une règle de pare-feu dans l'UDM Pro"""
_name = 'udm.firewall.rule'
_description = 'UDM Pro Firewall Rule'
config_id = fields.Many2one('udm.configuration', string='Configuration', ondelete='cascade')
name = fields.Char(string='Name', required=True)
enabled = fields.Boolean(string='Enabled', default=True)
action = fields.Selection([
('accept', 'Accept'),
('drop', 'Drop'),
('reject', 'Reject')
], string='Action')
protocol = fields.Selection([
('tcp', 'TCP'),
('udp', 'UDP'),
('icmp', 'ICMP'),
('all', 'All')
], string='Protocol')
src_address = fields.Char(string='Source Address')
dst_address = fields.Char(string='Destination Address')
src_port = fields.Char(string='Source Port')
dst_port = fields.Char(string='Destination Port')
raw_data = fields.Text(string='Raw Data')
# Champs calculés
rule_summary = fields.Char(string='Rule Summary', compute='_compute_rule_summary')
@api.depends('action', 'protocol', 'src_address', 'dst_address', 'src_port', 'dst_port')
def _compute_rule_summary(self):
for record in self:
parts = []
if record.action:
parts.append(record.action.upper())
if record.protocol:
parts.append(record.protocol.upper())
if record.src_address:
src = f"from {record.src_address}"
if record.src_port:
src += f":{record.src_port}"
parts.append(src)
if record.dst_address:
dst = f"to {record.dst_address}"
if record.dst_port:
dst += f":{record.dst_port}"
parts.append(dst)
record.rule_summary = ' '.join(parts) if parts else 'No details'

View file

@ -0,0 +1,64 @@
# -*- coding: utf-8 -*-
from odoo import models, fields, api, _
class UdmNetwork(models.Model):
"""Représentation d'un réseau dans l'UDM Pro"""
_name = 'udm.network'
_description = 'UDM Pro Network'
config_id = fields.Many2one('udm.configuration', string='Configuration', ondelete='cascade')
name = fields.Char(string='Name', required=True)
purpose = fields.Char(string='Purpose')
subnet = fields.Char(string='Subnet')
vlan_id_number = fields.Integer(string='VLAN ID')
vlan_id = fields.Many2one('udm.vlan', string='VLAN', compute='_compute_vlan_id', store=True)
dhcp_enabled = fields.Boolean(string='DHCP Enabled', default=False)
dhcp_start = fields.Char(string='DHCP Start')
dhcp_stop = fields.Char(string='DHCP Stop')
domain_name = fields.Char(string='Domain Name')
raw_data = fields.Text(string='Raw Data')
# Statistiques
device_count = fields.Integer(string='Device Count', compute='_compute_device_count')
@api.depends('vlan_id_number', 'config_id')
def _compute_vlan_id(self):
for record in self:
if not record.vlan_id_number or not record.config_id:
record.vlan_id = False
continue
vlan = self.env['udm.vlan'].search([
('config_id', '=', record.config_id.id),
('vlan_id', '=', record.vlan_id_number)
], limit=1)
record.vlan_id = vlan.id if vlan else False
def _compute_device_count(self):
for record in self:
# Cette fonction serait plus précise si les périphériques étaient liés aux réseaux
# Pour l'instant, c'est juste un exemple
record.device_count = 0
class UdmVLAN(models.Model):
"""Représentation d'un VLAN dans l'UDM Pro"""
_name = 'udm.vlan'
_description = 'UDM Pro VLAN'
config_id = fields.Many2one('udm.configuration', string='Configuration', ondelete='cascade')
vlan_id = fields.Integer(string='VLAN ID', required=True)
name = fields.Char(string='Name', required=True)
enabled = fields.Boolean(string='Enabled', default=True)
raw_data = fields.Text(string='Raw Data')
# Relations inverses
network_ids = fields.One2many('udm.network', 'vlan_id', string='Networks')
network_count = fields.Integer(compute='_compute_network_count')
@api.depends('network_ids')
def _compute_network_count(self):
for record in self:
record.network_count = len(record.network_ids)

View file

@ -0,0 +1,27 @@
# -*- coding: utf-8 -*-
from odoo import models, fields, api, _
class UdmSettings(models.Model):
"""Configuration générale de l'UDM Pro"""
_name = 'udm.settings'
_description = 'UDM Pro Settings'
config_id = fields.Many2one('udm.configuration', string='Configuration', ondelete='cascade')
timezone = fields.Char(string='Timezone')
ntp_servers = fields.Char(string='NTP Servers')
dns_servers = fields.Char(string='DNS Servers')
raw_data = fields.Text(string='Raw Data')
# Champs calculés
ntp_server_list = fields.Many2many('ir.model.data', string='NTP Server List',
compute='_compute_server_lists')
dns_server_list = fields.Many2many('ir.model.data', string='DNS Server List',
compute='_compute_server_lists')
@api.depends('ntp_servers', 'dns_servers')
def _compute_server_lists(self):
for record in self:
# Conversion des chaînes de caractères en listes pour l'affichage dans l'interface
record.ntp_server_list = False # Ceci est un champ technique pour l'UI
record.dns_server_list = False # Ceci est un champ technique pour l'UI

View file

@ -0,0 +1,41 @@
# -*- coding: utf-8 -*-
from odoo import models, fields, api, _
class UdmSystemInfo(models.Model):
"""Informations système de l'UDM Pro"""
_name = 'udm.system.info'
_description = 'UDM Pro System Information'
config_id = fields.Many2one('udm.configuration', string='Configuration', ondelete='cascade')
hostname = fields.Char(string='Hostname')
version = fields.Char(string='Firmware Version')
model = fields.Char(string='Model')
uptime = fields.Integer(string='Uptime (seconds)')
uptime_human = fields.Char(string='Uptime', compute='_compute_uptime_human')
serial = fields.Char(string='Serial Number')
mac_address = fields.Char(string='MAC Address')
raw_data = fields.Text(string='Raw Data')
@api.depends('uptime')
def _compute_uptime_human(self):
for record in self:
if not record.uptime:
record.uptime_human = 'Unknown'
continue
days, remainder = divmod(record.uptime, 86400)
hours, remainder = divmod(remainder, 3600)
minutes, seconds = divmod(remainder, 60)
parts = []
if days:
parts.append(f"{days} day{'s' if days != 1 else ''}")
if hours:
parts.append(f"{hours} hour{'s' if hours != 1 else ''}")
if minutes:
parts.append(f"{minutes} minute{'s' if minutes != 1 else ''}")
if seconds or not parts:
parts.append(f"{seconds} second{'s' if seconds != 1 else ''}")
record.uptime_human = ', '.join(parts)

View file

@ -0,0 +1,23 @@
# -*- coding: utf-8 -*-
from odoo import models, fields, api, _
class UdmUser(models.Model):
"""Représentation d'un utilisateur dans l'UDM Pro"""
_name = 'udm.user'
_description = 'UDM Pro User'
config_id = fields.Many2one('udm.configuration', string='Configuration', ondelete='cascade')
name = fields.Char(string='Name', required=True)
email = fields.Char(string='Email')
role = fields.Char(string='Role')
enabled = fields.Boolean(string='Enabled', default=True)
raw_data = fields.Text(string='Raw Data')
# Champs calculés pour des statistiques ou filtrage
is_admin = fields.Boolean(string='Is Admin', compute='_compute_is_admin', store=True)
@api.depends('role')
def _compute_is_admin(self):
for record in self:
record.is_admin = record.role and 'admin' in record.role.lower() or False

View file

@ -0,0 +1,22 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_udm_configuration_user,udm.configuration.user,model_udm_configuration,udm_pro_docs.group_udm_pro_user,1,0,0,0
access_udm_configuration_manager,udm.configuration.manager,model_udm_configuration,udm_pro_docs.group_udm_pro_manager,1,1,1,1
access_udm_system_info_user,udm.system.info.user,model_udm_system_info,udm_pro_docs.group_udm_pro_user,1,0,0,0
access_udm_system_info_manager,udm.system.info.manager,model_udm_system_info,udm_pro_docs.group_udm_pro_manager,1,1,1,1
access_udm_network_user,udm.network.user,model_udm_network,udm_pro_docs.group_udm_pro_user,1,0,0,0
access_udm_network_manager,udm.network.manager,model_udm_network,udm_pro_docs.group_udm_pro_manager,1,1,1,1
access_udm_vlan_user,udm.vlan.user,model_udm_vlan,udm_pro_docs.group_udm_pro_user,1,0,0,0
access_udm_vlan_manager,udm.vlan.manager,model_udm_vlan,udm_pro_docs.group_udm_pro_manager,1,1,1,1
access_udm_device_user,udm.device.user,model_udm_device,udm_pro_docs.group_udm_pro_user,1,0,0,0
access_udm_device_manager,udm.device.manager,model_udm_device,udm_pro_docs.group_udm_pro_manager,1,1,1,1
access_udm_user_user,udm.user.user,model_udm_user,udm_pro_docs.group_udm_pro_user,1,0,0,0
access_udm_user_manager,udm.user.manager,model_udm_user,udm_pro_docs.group_udm_pro_manager,1,1,1,1
access_udm_settings_user,udm.settings.user,model_udm_settings,udm_pro_docs.group_udm_pro_user,1,0,0,0
access_udm_settings_manager,udm.settings.manager,model_udm_settings,udm_pro_docs.group_udm_pro_manager,1,1,1,1
access_udm_firewall_rule_user,udm.firewall.rule.user,model_udm_firewall_rule,udm_pro_docs.group_udm_pro_user,1,0,0,0
access_udm_firewall_rule_manager,udm.firewall.rule.manager,model_udm_firewall_rule,udm_pro_docs.group_udm_pro_manager,1,1,1,1
# Nouveaux modèles pour le tableau de bord et la gestion multi-sites
access_udm_site_user,udm.site.user,model_udm_site,udm_pro_docs.group_udm_pro_user,1,0,0,0
access_udm_site_manager,udm.site.manager,model_udm_site,udm_pro_docs.group_udm_pro_manager,1,1,1,1
access_udm_dashboard_metric_user,udm.dashboard.metric.user,model_udm_dashboard_metric,udm_pro_docs.group_udm_pro_user,1,0,0,0
access_udm_dashboard_metric_manager,udm.dashboard.metric.manager,model_udm_dashboard_metric,udm_pro_docs.group_udm_pro_manager,1,1,1,1
Can't render this file because it has a wrong number of fields in line 18.

View file

@ -0,0 +1,54 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="0">
<!-- UDM Pro Documentation Security Groups -->
<record id="module_category_udm_pro" model="ir.module.category">
<field name="name">UDM Pro Documentation</field>
<field name="description">Manage UDM Pro configurations and documentation</field>
<field name="sequence">50</field>
</record>
<!-- User Group: can only view configurations -->
<record id="group_udm_pro_user" model="res.groups">
<field name="name">User</field>
<field name="category_id" ref="module_category_udm_pro"/>
<field name="implied_ids" eval="[(4, ref('base.group_user'))]"/>
</record>
<!-- Manager Group: can create, edit, and import configurations -->
<record id="group_udm_pro_manager" model="res.groups">
<field name="name">Manager</field>
<field name="category_id" ref="module_category_udm_pro"/>
<field name="implied_ids" eval="[(4, ref('group_udm_pro_user'))]"/>
<field name="users" eval="[(4, ref('base.user_root')), (4, ref('base.user_admin'))]"/>
</record>
</data>
<data noupdate="1">
<!-- Record Rules -->
<!-- Users can only view records -->
<record id="rule_udm_configuration_user" model="ir.rule">
<field name="name">UDM Pro Configuration User Access</field>
<field name="model_id" ref="model_udm_configuration"/>
<field name="domain_force">[(1, '=', 1)]</field>
<field name="groups" eval="[(4, ref('group_udm_pro_user'))]"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="False"/>
<field name="perm_create" eval="False"/>
<field name="perm_unlink" eval="False"/>
</record>
<!-- Managers can create, edit, and delete records -->
<record id="rule_udm_configuration_manager" model="ir.rule">
<field name="name">UDM Pro Configuration Manager Access</field>
<field name="model_id" ref="model_udm_configuration"/>
<field name="domain_force">[(1, '=', 1)]</field>
<field name="groups" eval="[(4, ref('group_udm_pro_manager'))]"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_unlink" eval="True"/>
</record>
</data>
</odoo>

View file

@ -0,0 +1 @@

View file

@ -0,0 +1,70 @@
/* UDM Pro Documentation Module CSS */
.o_udm_pro_config_header {
background-color: #f5f5f5;
padding: 16px;
margin-bottom: 16px;
border-radius: 4px;
}
.o_udm_pro_status_online {
color: #28a745;
font-weight: bold;
}
.o_udm_pro_status_offline {
color: #dc3545;
font-weight: bold;
}
.o_udm_pro_network_card {
border: 1px solid #ddd;
border-radius: 4px;
padding: 16px;
margin-bottom: 16px;
transition: all 0.3s ease;
}
.o_udm_pro_network_card:hover {
box-shadow: 0 5px 15px rgba(0,0,0,0.1);
}
.o_udm_pro_vlan_tag {
display: inline-block;
padding: 4px 8px;
background-color: #6c757d;
color: white;
border-radius: 4px;
margin-right: 8px;
margin-bottom: 8px;
font-size: 0.85rem;
}
.o_udm_pro_device_count {
font-size: 1.5rem;
font-weight: bold;
color: #007bff;
}
.o_udm_pro_firewall_rule {
padding: 12px;
border-left: 4px solid transparent;
margin-bottom: 8px;
transition: all 0.2s ease;
}
.o_udm_pro_firewall_rule.accept {
border-left-color: #28a745;
}
.o_udm_pro_firewall_rule.drop {
border-left-color: #dc3545;
}
.o_udm_pro_firewall_rule.reject {
border-left-color: #fd7e14;
}
.o_udm_pro_import_button {
margin-top: 16px;
}

View file

@ -0,0 +1,389 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Import Configuration Form Template -->
<template id="import_config_form" name="Import UDM Pro Configuration">
<t t-call="website.layout">
<div class="container mt-4">
<div class="row">
<div class="col-lg-8 offset-lg-2">
<div class="card">
<div class="card-header bg-primary">
<h3 class="text-white">Import UDM Pro Configuration</h3>
</div>
<div class="card-body">
<div t-if="error_message" class="alert alert-danger" role="alert">
<t t-esc="error_message"/>
</div>
<form method="post" action="/udm_pro/import_config">
<div class="form-group mb-3">
<label for="host">UDM Pro Host *</label>
<input type="text" class="form-control" name="host" id="host" required="required"
t-att-value="host if host else ''" placeholder="IP address or hostname"/>
</div>
<div class="form-group mb-3">
<label for="port">Port</label>
<input type="number" class="form-control" name="port" id="port"
t-att-value="port if port else '443'" placeholder="443"/>
</div>
<div class="form-group mb-3">
<label for="username">Username *</label>
<input type="text" class="form-control" name="username" id="username" required="required"
t-att-value="username if username else ''" placeholder="Username"/>
</div>
<div class="form-group mb-3">
<label for="password">Password *</label>
<input type="password" class="form-control" name="password" id="password" required="required"
placeholder="Password"/>
</div>
<div class="d-grid">
<button type="submit" class="btn btn-primary">Import Configuration</button>
</div>
</form>
</div>
<div class="card-footer">
<p class="text-muted">* Required fields</p>
<p class="text-muted small">This will connect to your UDM Pro device and import the current configuration into Odoo.</p>
</div>
</div>
</div>
</div>
</div>
</t>
</template>
<!-- Access Denied Template -->
<template id="access_denied" name="Access Denied">
<t t-call="website.layout">
<div class="container mt-4">
<div class="row">
<div class="col-lg-8 offset-lg-2">
<div class="card">
<div class="card-header bg-danger">
<h3 class="text-white">Access Denied</h3>
</div>
<div class="card-body">
<div class="alert alert-danger" role="alert">
<t t-esc="error_message"/>
</div>
<div class="d-grid">
<a href="/web" class="btn btn-primary">Return to Dashboard</a>
</div>
</div>
</div>
</div>
</div>
</div>
</t>
</template>
<!-- Advanced Options Form Template -->
<template id="advanced_options_form" name="Advanced UDM Pro Options">
<t t-call="website.layout">
<div class="container mt-4">
<div class="row">
<div class="col-lg-8 offset-lg-2">
<div class="card">
<div class="card-header bg-primary">
<h3 class="text-white">Advanced UDM Pro Options</h3>
</div>
<div class="card-body">
<div t-if="error_message" class="alert alert-danger" role="alert">
<t t-esc="error_message"/>
</div>
<form method="post" action="/udm_pro/import_config">
<div class="form-group mb-3">
<label for="site">UniFi Site ID</label>
<input type="text" class="form-control" name="site" id="site"
t-att-value="default_site" placeholder="default"/>
<small class="form-text text-muted">Identifier used by UDM Pro to target devices. Usually 'default' unless specifically configured otherwise.</small>
</div>
<div class="form-check mb-3">
<input type="checkbox" class="form-check-input" name="fixed_only" id="fixed_only" t-att-checked="fixed_only"/>
<label class="form-check-label" for="fixed_only">Fixed IPs Only</label>
<small class="form-text text-muted d-block">When checked, only considers clients with fixed IP addresses.</small>
</div>
<div class="form-check mb-3">
<input type="checkbox" class="form-check-input" name="lowercase_hostnames" id="lowercase_hostnames" t-att-checked="lowercase_hostnames"/>
<label class="form-check-label" for="lowercase_hostnames">Lowercase Hostnames</label>
<small class="form-text text-muted d-block">When checked, converts all hostnames to lowercase.</small>
</div>
<div class="d-grid">
<button type="submit" class="btn btn-primary">Apply Options</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</t>
</template>
<!-- Restart Device Form Template -->
<template id="restart_device_form" name="Restart UDM Pro Device">
<t t-call="website.layout">
<div class="container mt-4">
<div class="row">
<div class="col-lg-8 offset-lg-2">
<div class="card">
<div class="card-header bg-warning">
<h3 class="text-white">Restart UDM Pro Device</h3>
</div>
<div class="card-body">
<div t-if="error_message" class="alert alert-danger" role="alert">
<t t-esc="error_message"/>
</div>
<div class="alert alert-warning" role="alert">
<i class="fa fa-exclamation-triangle"></i> Warning: This will restart the selected device. Make sure you understand the impact before proceeding.
</div>
<form method="post" action="/udm_pro/restart_device">
<div class="form-group mb-3">
<label for="config_id">Select UDM Pro Configuration *</label>
<select class="form-control" name="config_id" id="config_id" required="required">
<option value="">-- Select Configuration --</option>
<t t-foreach="configs" t-as="config">
<option t-att-value="config.id">
<t t-esc="config.name"/> (<t t-esc="config.host"/>)
</option>
</t>
</select>
</div>
<div class="form-group mb-3">
<label for="mac_address">Device MAC Address *</label>
<input type="text" class="form-control" name="mac_address" id="mac_address" required="required"
placeholder="Example: 9C:0E:CC:8E:4B:C7"/>
<small class="form-text text-muted">The MAC address of the device you want to restart (e.g., a wireless access point).</small>
</div>
<div class="d-grid gap-2">
<button type="submit" class="btn btn-warning">Restart Device</button>
<a href="/web" class="btn btn-secondary">Cancel</a>
</div>
</form>
</div>
<div class="card-footer">
<p class="text-muted">* Required fields</p>
<p class="text-muted small">This operation requires 'admin' level permissions on the UDM Pro.</p>
</div>
</div>
</div>
</div>
</div>
</t>
</template>
<!-- Restart Success Template -->
<template id="restart_success" name="Device Restart Success">
<t t-call="website.layout">
<div class="container mt-4">
<div class="row">
<div class="col-lg-8 offset-lg-2">
<div class="card">
<div class="card-header bg-success">
<h3 class="text-white">Device Restart Successful</h3>
</div>
<div class="card-body">
<div class="alert alert-success" role="alert">
<p><i class="fa fa-check-circle"></i> The device restart command was successfully sent to the UDM Pro.</p>
<p>MAC Address: <strong><t t-esc="mac_address"/></strong></p>
</div>
<div class="alert alert-info" role="alert">
<p>Please note that it may take a few minutes for the device to fully restart and come back online.</p>
</div>
<div class="d-grid gap-2">
<a href="/udm_pro/restart_device" class="btn btn-primary">Restart Another Device</a>
<a href="/web" class="btn btn-secondary">Return to Dashboard</a>
</div>
</div>
</div>
</div>
</div>
</div>
</t>
</template>
<!-- Generate Hosts File Form Template -->
<template id="generate_hosts_form" name="Generate Hosts File from UDM Pro">
<t t-call="website.layout">
<div class="container mt-4">
<div class="row">
<div class="col-lg-8 offset-lg-2">
<div class="card">
<div class="card-header bg-primary">
<h3 class="text-white">Generate Hosts File from UDM Pro</h3>
</div>
<div class="card-body">
<div t-if="error_message" class="alert alert-danger" role="alert">
<t t-esc="error_message"/>
</div>
<form method="post" action="/udm_pro/generate_hosts">
<div class="form-group mb-3">
<label for="config_id">Select UDM Pro Configuration *</label>
<select class="form-control" name="config_id" id="config_id" required="required">
<option value="">-- Select Configuration --</option>
<t t-foreach="configs" t-as="config">
<option t-att-value="config.id">
<t t-esc="config.name"/> (<t t-esc="config.host"/>)
</option>
</t>
</select>
</div>
<div class="form-check mb-3">
<input type="checkbox" class="form-check-input" name="fixed_only" id="fixed_only" t-att-checked="fixed_only"/>
<label class="form-check-label" for="fixed_only">Fixed IPs Only</label>
<small class="form-text text-muted d-block">When checked, only includes clients with fixed IP addresses in the hosts file.</small>
</div>
<div class="form-check mb-3">
<input type="checkbox" class="form-check-input" name="lowercase_hostnames" id="lowercase_hostnames" t-att-checked="lowercase_hostnames"/>
<label class="form-check-label" for="lowercase_hostnames">Lowercase Hostnames</label>
<small class="form-text text-muted d-block">When checked, converts all hostnames to lowercase in the hosts file.</small>
</div>
<div class="d-grid gap-2">
<button type="submit" class="btn btn-primary">Generate and Download Hosts File</button>
<a href="/web" class="btn btn-secondary">Cancel</a>
</div>
</form>
</div>
<div class="card-footer">
<p class="text-muted">* Required fields</p>
<p class="text-muted small">This will generate a hosts file based on the clients connected to your UDM Pro.</p>
</div>
</div>
</div>
</div>
</div>
</t>
</template>
<!-- Network Clients Form Template -->
<template id="network_clients_form" name="View UDM Pro Network Clients">
<t t-call="website.layout">
<div class="container mt-4">
<div class="row">
<div class="col-lg-8 offset-lg-2">
<div class="card">
<div class="card-header bg-primary">
<h3 class="text-white">View UDM Pro Network Clients</h3>
</div>
<div class="card-body">
<div t-if="error_message" class="alert alert-danger" role="alert">
<t t-esc="error_message"/>
</div>
<form method="post" action="/udm_pro/network_clients">
<div class="form-group mb-3">
<label for="config_id">Select UDM Pro Configuration *</label>
<select class="form-control" name="config_id" id="config_id" required="required">
<option value="">-- Select Configuration --</option>
<t t-foreach="configs" t-as="config">
<option t-att-value="config.id">
<t t-esc="config.name"/> (<t t-esc="config.host"/>)
</option>
</t>
</select>
</div>
<div class="form-check mb-3">
<input type="checkbox" class="form-check-input" name="fixed_only" id="fixed_only" t-att-checked="fixed_only"/>
<label class="form-check-label" for="fixed_only">Fixed IPs Only</label>
<small class="form-text text-muted d-block">When checked, only shows clients with fixed IP addresses.</small>
</div>
<div class="form-check mb-3">
<input type="checkbox" class="form-check-input" name="lowercase_hostnames" id="lowercase_hostnames" t-att-checked="lowercase_hostnames"/>
<label class="form-check-label" for="lowercase_hostnames">Lowercase Hostnames</label>
<small class="form-text text-muted d-block">When checked, converts all hostnames to lowercase.</small>
</div>
<div class="d-grid gap-2">
<button type="submit" class="btn btn-primary">View Network Clients</button>
<a href="/web" class="btn btn-secondary">Cancel</a>
</div>
</form>
</div>
<div class="card-footer">
<p class="text-muted">* Required fields</p>
<p class="text-muted small">This will show all clients connected to your UDM Pro network.</p>
</div>
</div>
</div>
</div>
</div>
</t>
</template>
<!-- Network Clients Results Template -->
<template id="network_clients_result" name="UDM Pro Network Clients Results">
<t t-call="website.layout">
<div class="container mt-4">
<div class="row">
<div class="col-12">
<div class="card">
<div class="card-header bg-primary">
<div class="d-flex justify-content-between align-items-center">
<h3 class="text-white mb-0">Network Clients for <t t-esc="config.name"/> (<t t-esc="config.host"/>)</h3>
<a href="/udm_pro/network_clients" class="btn btn-light btn-sm">Back to Search</a>
</div>
</div>
<div class="card-body">
<div t-if="not clients" class="alert alert-info" role="alert">
No network clients found matching your criteria.
</div>
<div t-if="clients" class="table-responsive">
<table class="table table-striped table-hover">
<thead>
<tr>
<th>Name/Hostname</th>
<th>MAC Address</th>
<th>IP Address</th>
<th>Type</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<t t-foreach="clients" t-as="client">
<tr>
<td>
<strong t-esc="client.get('name') or client.get('hostname') or 'Unknown'"/>
</td>
<td t-esc="client.get('mac', 'N/A')"/>
<td>
<t t-if="client.get('fixed_ip')">
<span class="badge bg-success"><t t-esc="client.get('fixed_ip')"/> (Fixed)</span>
</t>
<t t-elif="client.get('ip')">
<span class="badge bg-secondary"><t t-esc="client.get('ip')"/> (Dynamic)</span>
</t>
<t t-else="">
<span class="badge bg-warning">No IP</span>
</t>
</td>
<td>
<t t-if="client.get('fixed_ip')">
<span class="badge bg-primary">Static</span>
</t>
<t t-else="">
<span class="badge bg-info">Dynamic</span>
</t>
</td>
<td>
<a t-if="client.get('mac')"
t-att-href="'/udm_pro/restart_device?config_id=%s&amp;mac_address=%s' % (config.id, client.get('mac'))"
class="btn btn-sm btn-warning">
<i class="fa fa-refresh"></i> Restart
</a>
</td>
</tr>
</t>
</tbody>
</table>
</div>
<div class="d-grid gap-2 col-md-6 mx-auto mt-4">
<a href="/udm_pro/generate_hosts?config_id={config.id}" class="btn btn-success">
<i class="fa fa-file-text-o"></i> Generate Hosts File
</a>
<a href="/web" class="btn btn-secondary">Return to Dashboard</a>
</div>
</div>
</div>
</div>
</div>
</div>
</t>
</template>
</odoo>

View file

@ -0,0 +1,418 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- UDM Configuration Tree View -->
<record id="view_udm_configuration_tree" model="ir.ui.view">
<field name="name">udm.configuration.tree</field>
<field name="model">udm.configuration</field>
<field name="arch" type="xml">
<tree string="UDM Pro Configurations">
<field name="name"/>
<field name="timestamp"/>
<field name="site_id"/>
<field name="active"/>
<field name="system_info_id" optional="show"/>
<field name="network_count" optional="show"/>
<field name="device_count" optional="show"/>
<field name="user_count" optional="show"/>
<field name="firewall_rule_count" optional="show"/>
</tree>
</field>
</record>
<!-- UDM Configuration Form View -->
<record id="view_udm_configuration_form" model="ir.ui.view">
<field name="name">udm.configuration.form</field>
<field name="model">udm.configuration</field>
<field name="arch" type="xml">
<form string="UDM Pro Configuration">
<sheet>
<div class="oe_button_box" name="button_box">
<button name="action_view_networks" type="object" class="oe_stat_button" icon="fa-diagram-project">
<field name="network_count" widget="statinfo" string="Networks"/>
</button>
<button name="action_view_devices" type="object" class="oe_stat_button" icon="fa-desktop">
<field name="device_count" widget="statinfo" string="Devices"/>
</button>
<button name="action_view_users" type="object" class="oe_stat_button" icon="fa-users">
<field name="user_count" widget="statinfo" string="Users"/>
</button>
<button name="action_view_firewall_rules" type="object" class="oe_stat_button" icon="fa-shield-halved">
<field name="firewall_rule_count" widget="statinfo" string="Firewall Rules"/>
</button>
<button name="action_view_dashboard" type="object" class="oe_stat_button" icon="fa-tachometer-alt">
<span>Dashboard</span>
</button>
</div>
<div class="oe_title">
<h1>
<field name="name"/>
</h1>
</div>
<group>
<group>
<field name="timestamp"/>
<field name="site_id"/>
<field name="active"/>
<field name="system_info_id"/>
<field name="settings_id"/>
</group>
<group>
<field name="host"/>
<field name="port"/>
<field name="username"/>
<field name="password" password="True"/>
</group>
</group>
<notebook>
<page string="Networks" name="networks">
<field name="network_ids" nolabel="1">
<tree>
<field name="name"/>
<field name="purpose"/>
<field name="subnet"/>
<field name="vlan_id"/>
<field name="dhcp_enabled"/>
</tree>
</field>
</page>
<page string="VLANs" name="vlans">
<field name="vlan_ids" nolabel="1">
<tree>
<field name="vlan_id"/>
<field name="name"/>
<field name="enabled"/>
</tree>
</field>
</page>
<page string="Devices" name="devices">
<field name="device_ids" nolabel="1">
<tree>
<field name="name"/>
<field name="mac"/>
<field name="ip"/>
<field name="device_type"/>
<field name="model" optional="show"/>
<field name="last_seen"/>
<field name="status" widget="badge" decoration-success="status == 'online'" decoration-danger="status == 'offline'" decoration-info="status == 'unknown'"/>
</tree>
</field>
</page>
<page string="Users" name="users">
<field name="user_ids" nolabel="1">
<tree>
<field name="name"/>
<field name="email"/>
<field name="role"/>
<field name="enabled"/>
<field name="is_admin"/>
</tree>
</field>
</page>
<page string="Firewall Rules" name="firewall_rules">
<field name="firewall_rule_ids" nolabel="1">
<tree>
<field name="name"/>
<field name="enabled"/>
<field name="action"/>
<field name="protocol"/>
<field name="src_address"/>
<field name="dst_address"/>
<field name="rule_summary" optional="show"/>
</tree>
</field>
</page>
<page string="Raw Data" name="raw_data">
<field name="raw_data" widget="ace" options="{'mode': 'json'}" help="Données JSON brutes de la configuration"/>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
<!-- Configuration Search View -->
<record id="view_udm_configuration_search" model="ir.ui.view">
<field name="name">udm.configuration.search</field>
<field name="model">udm.configuration</field>
<field name="arch" type="xml">
<search string="Search UDM Pro Configurations">
<field name="name"/>
<field name="system_info_id"/>
<field name="timestamp"/>
<filter string="Last 7 Days" name="last_7_days" domain="[('timestamp','>=', (context_today() - relativedelta(days=7)))]"/>
<filter string="Last 30 Days" name="last_30_days" domain="[('timestamp','>=', (context_today() - relativedelta(days=30)))]"/>
<group expand="0" string="Group By">
<filter string="System Info" name="system_info" context="{'group_by': 'system_info_id'}"/>
<filter string="Month" name="month" context="{'group_by': 'timestamp:month'}"/>
</group>
</search>
</field>
</record>
<!-- System Info Form View -->
<record id="view_udm_system_info_form" model="ir.ui.view">
<field name="name">udm.system.info.form</field>
<field name="model">udm.system.info</field>
<field name="arch" type="xml">
<form string="UDM Pro System Information">
<sheet>
<group>
<group>
<field name="hostname"/>
<field name="version"/>
<field name="model"/>
<field name="serial"/>
</group>
<group>
<field name="mac_address"/>
<field name="uptime" invisibility="always"/>
<field name="uptime_human"/>
<field name="config_id"/>
</group>
</group>
<notebook>
<page string="Raw Data" name="raw_data">
<field name="raw_data" widget="ace" options="{'mode': 'json'}" help="Données JSON brutes de la configuration"/>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
<!-- Network Form View -->
<record id="view_udm_network_form" model="ir.ui.view">
<field name="name">udm.network.form</field>
<field name="model">udm.network</field>
<field name="arch" type="xml">
<form string="UDM Pro Network">
<sheet>
<group>
<group>
<field name="name"/>
<field name="purpose"/>
<field name="subnet"/>
</group>
<group>
<field name="vlan_id_number"/>
<field name="vlan_id"/>
<field name="dhcp_enabled"/>
<field name="domain_name"/>
</group>
</group>
<group invisible="[('dhcp_enabled', '=', False)]">
<group>
<field name="dhcp_start"/>
<field name="dhcp_stop"/>
</group>
</group>
<notebook>
<page string="Raw Data" name="raw_data">
<field name="raw_data" widget="ace" options="{'mode': 'json'}" help="Données JSON brutes de la configuration"/>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
<!-- VLAN Form View -->
<record id="view_udm_vlan_form" model="ir.ui.view">
<field name="name">udm.vlan.form</field>
<field name="model">udm.vlan</field>
<field name="arch" type="xml">
<form string="UDM Pro VLAN">
<sheet>
<group>
<group>
<field name="vlan_id"/>
<field name="name"/>
</group>
<group>
<field name="enabled"/>
<field name="config_id"/>
<field name="network_count"/>
</group>
</group>
<notebook>
<page string="Networks" name="networks">
<field name="network_ids" nolabel="1">
<tree>
<field name="name"/>
<field name="purpose"/>
<field name="subnet"/>
</tree>
</field>
</page>
<page string="Raw Data" name="raw_data">
<field name="raw_data" widget="ace" options="{'mode': 'json'}" help="Données JSON brutes de la configuration"/>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
<!-- Device Form View -->
<record id="view_udm_device_form" model="ir.ui.view">
<field name="name">udm.device.form</field>
<field name="model">udm.device</field>
<field name="arch" type="xml">
<form string="UDM Pro Device">
<sheet>
<div class="oe_title">
<h1>
<field name="name"/>
</h1>
</div>
<group>
<group>
<field name="mac"/>
<field name="ip"/>
<field name="device_type"/>
</group>
<group>
<field name="model"/>
<field name="last_seen"/>
<field name="status" widget="badge" decoration-success="status == 'online'" decoration-danger="status == 'offline'" decoration-info="status == 'unknown'"/>
<field name="config_id"/>
</group>
</group>
<notebook>
<page string="Raw Data" name="raw_data">
<field name="raw_data" widget="ace" options="{'mode': 'json'}" help="Données JSON brutes de la configuration"/>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
<!-- User Form View -->
<record id="view_udm_user_form" model="ir.ui.view">
<field name="name">udm.user.form</field>
<field name="model">udm.user</field>
<field name="arch" type="xml">
<form string="UDM Pro User">
<sheet>
<div class="oe_title">
<h1>
<field name="name"/>
</h1>
</div>
<group>
<group>
<field name="email"/>
<field name="role"/>
</group>
<group>
<field name="enabled"/>
<field name="is_admin"/>
<field name="config_id"/>
</group>
</group>
<notebook>
<page string="Raw Data" name="raw_data">
<field name="raw_data" widget="ace" options="{'mode': 'json'}" help="Données JSON brutes de la configuration"/>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
<!-- Settings Form View -->
<record id="view_udm_settings_form" model="ir.ui.view">
<field name="name">udm.settings.form</field>
<field name="model">udm.settings</field>
<field name="arch" type="xml">
<form string="UDM Pro Settings">
<sheet>
<group>
<group>
<field name="timezone"/>
<field name="ntp_servers"/>
</group>
<group>
<field name="dns_servers"/>
<field name="config_id"/>
</group>
</group>
<notebook>
<page string="Raw Data" name="raw_data">
<field name="raw_data" widget="ace" options="{'mode': 'json'}" help="Données JSON brutes de la configuration"/>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
<!-- Firewall Rule Form View -->
<record id="view_udm_firewall_rule_form" model="ir.ui.view">
<field name="name">udm.firewall.rule.form</field>
<field name="model">udm.firewall.rule</field>
<field name="arch" type="xml">
<form string="UDM Pro Firewall Rule">
<sheet>
<div class="oe_title">
<h1>
<field name="name"/>
</h1>
</div>
<group>
<group>
<field name="enabled"/>
<field name="action"/>
<field name="protocol"/>
</group>
<group>
<field name="src_address"/>
<field name="dst_address"/>
<field name="src_port"/>
<field name="dst_port"/>
</group>
</group>
<group>
<field name="rule_summary" readonly="1" force_save="1" help="Résumé de la règle en langage naturel"/>
<field name="config_id"/>
</group>
<notebook>
<page string="Raw Data" name="raw_data">
<field name="raw_data" widget="ace" options="{'mode': 'json'}" help="Données JSON brutes de la configuration"/>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
<!-- Les vues relatives au modèle udm.site ont été déplacées vers udm_site_views.xml -->
<!-- La vue tableau de bord du site a été déplacée vers udm_site_views.xml -->
<!-- Les vues de métriques du tableau de bord ont été déplacées vers udm_dashboard_metric_views.xml -->
<!-- Cette vue d'extension n'est plus nécessaire car les champs ont été intégrés directement à la vue principale -->
<!-- Action pour les configurations UDM Pro -->
<record id="action_udm_configuration" model="ir.actions.act_window">
<field name="name">Configurations</field>
<field name="res_model">udm.configuration</field>
<field name="view_mode">tree,form</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
Import your first UDM Pro configuration
</p>
<p>
Import and manage UDM Pro device configurations.
</p>
</field>
</record>
<!-- Menu principal -->
<menuitem id="menu_udm_pro_docs_root" name="UDM Pro" sequence="90" web_icon="udm_pro_docs,static/description/icon.png"/>
<!-- Sous-menus -->
<menuitem id="menu_udm_configuration" name="Configurations" parent="menu_udm_pro_docs_root" sequence="20" action="action_udm_configuration"/>
</odoo>

View file

@ -0,0 +1,138 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Configuration Tree View -->
<record id="view_udm_configuration_tree" model="ir.ui.view">
<field name="name">udm.configuration.tree</field>
<field name="model">udm.configuration</field>
<field name="arch" type="xml">
<tree string="UDM Pro Configurations">
<field name="name"/>
<field name="timestamp"/>
<field name="system_info_id" optional="show"/>
<field name="network_count" optional="show"/>
<field name="device_count" optional="show"/>
<field name="user_count" optional="show"/>
<field name="firewall_rule_count" optional="show"/>
</tree>
</field>
</record>
<!-- Configuration Form View -->
<record id="view_udm_configuration_form" model="ir.ui.view">
<field name="name">udm.configuration.form</field>
<field name="model">udm.configuration</field>
<field name="arch" type="xml">
<form string="UDM Pro Configuration">
<sheet>
<div class="oe_button_box" name="button_box">
<button name="action_view_networks" type="object" class="oe_stat_button" icon="fa-diagram-project">
<field name="network_count" widget="statinfo" string="Networks"/>
</button>
<button name="action_view_devices" type="object" class="oe_stat_button" icon="fa-desktop">
<field name="device_count" widget="statinfo" string="Devices"/>
</button>
<button name="action_view_users" type="object" class="oe_stat_button" icon="fa-users">
<field name="user_count" widget="statinfo" string="Users"/>
</button>
<button name="action_view_firewall_rules" type="object" class="oe_stat_button" icon="fa-shield-halved">
<field name="firewall_rule_count" widget="statinfo" string="Firewall Rules"/>
</button>
</div>
<div class="oe_title">
<h1>
<field name="name"/>
</h1>
</div>
<group>
<group>
<field name="timestamp"/>
<field name="system_info_id"/>
<field name="settings_id"/>
</group>
</group>
<notebook>
<page string="Networks" name="networks">
<field name="network_ids" nolabel="1">
<tree>
<field name="name"/>
<field name="purpose"/>
<field name="subnet"/>
<field name="vlan_id"/>
<field name="dhcp_enabled"/>
</tree>
</field>
</page>
<page string="VLANs" name="vlans">
<field name="vlan_ids" nolabel="1">
<tree>
<field name="vlan_id"/>
<field name="name"/>
<field name="enabled"/>
</tree>
</field>
</page>
<page string="Devices" name="devices">
<field name="device_ids" nolabel="1">
<tree>
<field name="name"/>
<field name="mac"/>
<field name="ip"/>
<field name="device_type"/>
<field name="model" optional="show"/>
<field name="last_seen"/>
<field name="status" widget="badge" decoration-success="status == 'online'" decoration-danger="status == 'offline'" decoration-info="status == 'unknown'"/>
</tree>
</field>
</page>
<page string="Users" name="users">
<field name="user_ids" nolabel="1">
<tree>
<field name="name"/>
<field name="email"/>
<field name="role"/>
<field name="enabled"/>
<field name="is_admin"/>
</tree>
</field>
</page>
<page string="Firewall Rules" name="firewall_rules">
<field name="firewall_rule_ids" nolabel="1">
<tree>
<field name="name"/>
<field name="enabled"/>
<field name="action"/>
<field name="protocol"/>
<field name="src_address"/>
<field name="dst_address"/>
<field name="rule_summary" optional="show"/>
</tree>
</field>
</page>
<page string="Raw Data" name="raw_data">
<field name="raw_data" widget="ace" options="{'mode': 'json'}" help="Données JSON brutes de la configuration"/>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
<!-- Configuration Search View -->
<record id="view_udm_configuration_search" model="ir.ui.view">
<field name="name">udm.configuration.search</field>
<field name="model">udm.configuration</field>
<field name="arch" type="xml">
<search string="Search UDM Pro Configurations">
<field name="name"/>
<field name="system_info_id"/>
<field name="timestamp"/>
<filter string="Last 7 Days" name="last_7_days" domain="[('timestamp','>=', (context_today() - relativedelta(days=7)))]"/>
<filter string="Last 30 Days" name="last_30_days" domain="[('timestamp','>=', (context_today() - relativedelta(days=30)))]"/>
<group expand="0" string="Group By">
<filter string="System Info" name="system_info" context="{'group_by': 'system_info_id'}"/>
<filter string="Month" name="month" context="{'group_by': 'timestamp:month'}"/>
</group>
</search>
</field>
</record>
</odoo>

View file

@ -0,0 +1,79 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Dashboard Metric Tree View -->
<record id="view_udm_dashboard_metric_tree" model="ir.ui.view">
<field name="name">udm.dashboard.metric.tree</field>
<field name="model">udm.dashboard.metric</field>
<field name="arch" type="xml">
<tree string="UDM Pro Dashboard Metrics">
<field name="name"/>
<field name="value"/>
<field name="unit"/>
<field name="status" widget="badge" decoration-success="status == 'normal'" decoration-warning="status == 'warning'" decoration-danger="status == 'danger'"/>
<field name="last_update"/>
<field name="site_id"/>
</tree>
</field>
</record>
<!-- Dashboard Metric Form View -->
<record id="view_udm_dashboard_metric_form" model="ir.ui.view">
<field name="name">udm.dashboard.metric.form</field>
<field name="model">udm.dashboard.metric</field>
<field name="arch" type="xml">
<form string="UDM Pro Dashboard Metric">
<sheet>
<group>
<group>
<field name="name"/>
<field name="display_name"/>
<field name="value"/>
<field name="unit"/>
</group>
<group>
<field name="status" widget="badge" decoration-success="status == 'normal'" decoration-warning="status == 'warning'" decoration-danger="status == 'danger'"/>
<field name="description"/>
<field name="last_update"/>
<field name="site_id"/>
</group>
</group>
</sheet>
</form>
</field>
</record>
<!-- Dashboard Metric Search View -->
<record id="view_udm_dashboard_metric_search" model="ir.ui.view">
<field name="name">udm.dashboard.metric.search</field>
<field name="model">udm.dashboard.metric</field>
<field name="arch" type="xml">
<search string="Search Dashboard Metrics">
<field name="name"/>
<field name="value"/>
<field name="site_id"/>
<filter string="Normal Status" name="status_normal" domain="[('status', '=', 'normal')]"/>
<filter string="Warning Status" name="status_warning" domain="[('status', '=', 'warning')]"/>
<filter string="Danger Status" name="status_danger" domain="[('status', '=', 'danger')]"/>
<group expand="0" string="Group By">
<filter string="Site" name="group_by_site" domain="[]" context="{'group_by': 'site_id'}"/>
<filter string="Status" name="group_by_status" domain="[]" context="{'group_by': 'status'}"/>
</group>
</search>
</field>
</record>
<!-- Dashboard Metric Action -->
<record id="action_udm_dashboard_metric" model="ir.actions.act_window">
<field name="name">Dashboard Metrics</field>
<field name="res_model">udm.dashboard.metric</field>
<field name="view_mode">tree,form</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
No dashboard metrics found
</p>
<p>
Dashboard metrics are automatically created and updated when you refresh site metrics.
</p>
</field>
</record>
</odoo>

View file

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Device Form View -->
<record id="view_udm_device_form" model="ir.ui.view">
<field name="name">udm.device.form</field>
<field name="model">udm.device</field>
<field name="arch" type="xml">
<form string="UDM Pro Device">
<sheet>
<div class="oe_title">
<h1>
<field name="name"/>
</h1>
</div>
<group>
<group>
<field name="mac"/>
<field name="ip"/>
<field name="device_type"/>
</group>
<group>
<field name="model"/>
<field name="last_seen"/>
<field name="status" widget="badge" decoration-success="status == 'online'" decoration-danger="status == 'offline'" decoration-info="status == 'unknown'"/>
<field name="config_id"/>
</group>
</group>
<notebook>
<page string="Raw Data" name="raw_data">
<field name="raw_data" widget="ace" options="{'mode': 'json'}" help="Données JSON brutes de la configuration"/>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
</odoo>

View file

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Firewall Rule Form View -->
<record id="view_udm_firewall_rule_form" model="ir.ui.view">
<field name="name">udm.firewall.rule.form</field>
<field name="model">udm.firewall.rule</field>
<field name="arch" type="xml">
<form string="UDM Pro Firewall Rule">
<sheet>
<div class="oe_title">
<h1>
<field name="name"/>
</h1>
</div>
<group>
<group>
<field name="enabled"/>
<field name="action"/>
<field name="protocol"/>
</group>
<group>
<field name="src_address"/>
<field name="dst_address"/>
<field name="src_port"/>
<field name="dst_port"/>
</group>
</group>
<group>
<field name="rule_summary" readonly="1" force_save="1" help="Résumé de la règle en langage naturel"/>
<field name="config_id"/>
</group>
<notebook>
<page string="Raw Data" name="raw_data">
<field name="raw_data" widget="ace" options="{'mode': 'json'}" help="Données JSON brutes de la configuration"/>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
</odoo>

View file

@ -0,0 +1,171 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- UDM Pro Documentation Main Menu -->
<menuitem id="menu_udm_pro_root"
name="UDM Pro"
web_icon="udm_pro_docs,static/description/icon.png"
sequence="90"/>
<!-- UDM Pro Main Submenu: Configurations -->
<menuitem id="menu_udm_pro_main"
name="Configurations"
parent="menu_udm_pro_root"
sequence="10"/>
<!-- Import Configuration Menu -->
<menuitem id="menu_udm_import_config"
name="Import Configuration"
parent="menu_udm_pro_main"
action="udm_pro_docs.action_udm_pro_import"
sequence="5"
groups="udm_pro_docs.group_udm_pro_manager"/>
<!-- Configurations Menu Action -->
<record id="action_udm_configuration" model="ir.actions.act_window">
<field name="name">Configurations</field>
<field name="res_model">udm.configuration</field>
<field name="view_mode">tree,form</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
Create your first UDM Pro configuration
</p>
<p>
You can import configurations directly from your UDM Pro device
or manually create them for documentation purposes.
</p>
</field>
</record>
<menuitem id="menu_udm_configuration"
name="Configurations"
parent="menu_udm_pro_main"
action="action_udm_configuration"
sequence="10"/>
<!-- Network Components Menu -->
<menuitem id="menu_udm_pro_network"
name="Network"
parent="menu_udm_pro_root"
sequence="20"/>
<!-- Networks Menu Action -->
<record id="action_udm_network" model="ir.actions.act_window">
<field name="name">Networks</field>
<field name="res_model">udm.network</field>
<field name="view_mode">tree,form</field>
<field name="context">{'search_default_group_by_config_id': 1}</field>
</record>
<menuitem id="menu_udm_network"
name="Networks"
parent="menu_udm_pro_network"
action="action_udm_network"
sequence="10"/>
<!-- VLANs Menu Action -->
<record id="action_udm_vlan" model="ir.actions.act_window">
<field name="name">VLANs</field>
<field name="res_model">udm.vlan</field>
<field name="view_mode">tree,form</field>
<field name="context">{'search_default_group_by_config_id': 1}</field>
</record>
<menuitem id="menu_udm_vlan"
name="VLANs"
parent="menu_udm_pro_network"
action="action_udm_vlan"
sequence="20"/>
<!-- Devices Menu -->
<menuitem id="menu_udm_pro_devices"
name="Devices"
parent="menu_udm_pro_root"
sequence="30"/>
<!-- Devices Menu Action -->
<record id="action_udm_device" model="ir.actions.act_window">
<field name="name">Devices</field>
<field name="res_model">udm.device</field>
<field name="view_mode">tree,form</field>
<field name="context">{'search_default_group_by_config_id': 1}</field>
</record>
<menuitem id="menu_udm_device"
name="Devices"
parent="menu_udm_pro_devices"
action="action_udm_device"
sequence="10"/>
<!-- Security Menu -->
<menuitem id="menu_udm_pro_security"
name="Security"
parent="menu_udm_pro_root"
sequence="40"/>
<!-- UDM Users Menu Action -->
<record id="action_udm_user" model="ir.actions.act_window">
<field name="name">Users</field>
<field name="res_model">udm.user</field>
<field name="view_mode">tree,form</field>
<field name="context">{'search_default_group_by_config_id': 1}</field>
</record>
<menuitem id="menu_udm_user"
name="Users"
parent="menu_udm_pro_security"
action="action_udm_user"
sequence="10"/>
<!-- Firewall Rules Menu Action -->
<record id="action_udm_firewall_rule" model="ir.actions.act_window">
<field name="name">Firewall Rules</field>
<field name="res_model">udm.firewall.rule</field>
<field name="view_mode">tree,form</field>
<field name="context">{'search_default_group_by_config_id': 1}</field>
</record>
<menuitem id="menu_udm_firewall_rule"
name="Firewall Rules"
parent="menu_udm_pro_security"
action="action_udm_firewall_rule"
sequence="20"/>
<!-- Settings Menu -->
<menuitem id="menu_udm_pro_settings"
name="Settings"
parent="menu_udm_pro_root"
sequence="50"/>
<!-- System Info Menu Action -->
<record id="action_udm_system_info" model="ir.actions.act_window">
<field name="name">System Info</field>
<field name="res_model">udm.system.info</field>
<field name="view_mode">tree,form</field>
</record>
<menuitem id="menu_udm_system_info"
name="System Info"
parent="menu_udm_pro_settings"
action="action_udm_system_info"
sequence="10"/>
<!-- UDM Settings Menu Action -->
<record id="action_udm_settings" model="ir.actions.act_window">
<field name="name">UDM Settings</field>
<field name="res_model">udm.settings</field>
<field name="view_mode">tree,form</field>
</record>
<menuitem id="menu_udm_settings"
name="UDM Settings"
parent="menu_udm_pro_settings"
action="action_udm_settings"
sequence="20"/>
<!-- Import URL Action -->
<record id="action_udm_pro_import" model="ir.actions.act_url">
<field name="name">Import UDM Pro Configuration</field>
<field name="url">/udm_pro/import_config</field>
<field name="target">self</field>
</record>
</odoo>

View file

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Network Form View -->
<record id="view_udm_network_form" model="ir.ui.view">
<field name="name">udm.network.form</field>
<field name="model">udm.network</field>
<field name="arch" type="xml">
<form string="UDM Pro Network">
<sheet>
<group>
<group>
<field name="name"/>
<field name="purpose"/>
<field name="subnet"/>
</group>
<group>
<field name="vlan_id_number"/>
<field name="vlan_id"/>
<field name="dhcp_enabled"/>
<field name="domain_name"/>
</group>
</group>
<group invisible="[('dhcp_enabled', '=', False)]">
<group>
<field name="dhcp_start"/>
<field name="dhcp_stop"/>
</group>
</group>
<notebook>
<page string="Raw Data" name="raw_data">
<field name="raw_data" widget="ace" options="{'mode': 'json'}" help="Données JSON brutes de la configuration"/>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
</odoo>

View file

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Settings Form View -->
<record id="view_udm_settings_form" model="ir.ui.view">
<field name="name">udm.settings.form</field>
<field name="model">udm.settings</field>
<field name="arch" type="xml">
<form string="UDM Pro Settings">
<sheet>
<group>
<group>
<field name="timezone"/>
<field name="ntp_servers"/>
</group>
<group>
<field name="dns_servers"/>
<field name="config_id"/>
</group>
</group>
<notebook>
<page string="Raw Data" name="raw_data">
<field name="raw_data" widget="ace" options="{'mode': 'json'}" help="Données JSON brutes de la configuration"/>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
</odoo>

View file

@ -0,0 +1,281 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- UDM Site Tree View -->
<record id="view_udm_site_tree" model="ir.ui.view">
<field name="name">udm.site.tree</field>
<field name="model">udm.site</field>
<field name="arch" type="xml">
<tree string="UDM Pro Sites">
<field name="name"/>
<field name="site_id"/>
<field name="configuration_count"/>
<field name="active"/>
</tree>
</field>
</record>
<!-- UDM Site Form View -->
<record id="view_udm_site_form" model="ir.ui.view">
<field name="name">udm.site.form</field>
<field name="model">udm.site</field>
<field name="arch" type="xml">
<form string="UDM Pro Site">
<header>
<button name="action_refresh_metrics" string="Refresh Metrics" type="object" class="btn-primary"/>
<button name="action_view_dashboard" string="View Dashboard" type="object" class="btn-secondary"/>
</header>
<sheet>
<div class="oe_button_box" name="button_box">
<button name="action_view_configurations" type="object" class="oe_stat_button" icon="fa-cog">
<field name="configuration_count" widget="statinfo" string="Configurations"/>
</button>
</div>
<div class="oe_title">
<h1>
<field name="name"/>
</h1>
</div>
<group>
<group>
<field name="site_id"/>
<field name="active"/>
</group>
<group>
<field name="create_date" readonly="1"/>
<field name="last_refresh" readonly="1"/>
</group>
</group>
<notebook>
<page string="Configurations" name="configurations">
<field name="configuration_ids">
<tree>
<field name="name"/>
<field name="timestamp"/>
<field name="active"/>
</tree>
</field>
</page>
<page string="Dashboard Metrics" name="dashboard_metrics">
<field name="dashboard_ids">
<tree>
<field name="name"/>
<field name="value"/>
<field name="unit"/>
<field name="status"/>
<field name="last_update"/>
</tree>
</field>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
<!-- UDM Site Dashboard View -->
<record id="view_udm_site_dashboard" model="ir.ui.view">
<field name="name">udm.site.dashboard</field>
<field name="model">udm.site</field>
<field name="mode">primary</field>
<field name="arch" type="xml">
<dashboard string="UDM Pro Dashboard" sample="1">
<div class="d-flex flex-column">
<!-- Dashboard Header -->
<div class="d-flex justify-content-between align-items-center pb-2 mb-3 border-bottom">
<div class="d-flex flex-column">
<h2><field name="name"/> Dashboard</h2>
<div class="text-muted">
Site ID: <field name="site_id"/>
</div>
</div>
<div>
<button name="action_refresh_metrics" string="Refresh Metrics" type="object" class="btn btn-primary"/>
</div>
</div>
<!-- Dashboard Metrics -->
<div class="row mb-4">
<!-- CPU Usage -->
<div class="col-md-6 col-lg-4 mb-3">
<div class="card h-100">
<div class="card-header d-flex justify-content-between align-items-center bg-light">
<h6 class="m-0">CPU Usage</h6>
<i class="fa fa-microchip"></i>
</div>
<div class="card-body d-flex flex-column align-items-center justify-content-center text-center">
<t t-set="cpu_metric" t-value="dashboard_ids.filtered(lambda m: m.name == 'cpu_usage')"/>
<h3 class="mb-0" t-att-class="cpu_metric and cpu_metric.status == 'warning' and 'text-warning' or cpu_metric.status == 'danger' and 'text-danger' or ''">
<t t-esc="cpu_metric and cpu_metric.value or '0'"/>
<small><t t-esc="cpu_metric and cpu_metric.unit or '%'"/></small>
</h3>
<div class="progress w-75 mt-2">
<div class="progress-bar" role="progressbar"
t-att-style="'width: ' + (cpu_metric and str(cpu_metric.value) or '0') + '%;'"
t-att-class="cpu_metric and cpu_metric.status == 'warning' and 'bg-warning' or cpu_metric.status == 'danger' and 'bg-danger' or 'bg-success'"
t-att-aria-valuenow="cpu_metric and cpu_metric.value or 0" aria-valuemin="0" aria-valuemax="100">
</div>
</div>
</div>
</div>
</div>
<!-- Memory Usage -->
<div class="col-md-6 col-lg-4 mb-3">
<div class="card h-100">
<div class="card-header d-flex justify-content-between align-items-center bg-light">
<h6 class="m-0">Memory Usage</h6>
<i class="fa fa-memory"></i>
</div>
<div class="card-body d-flex flex-column align-items-center justify-content-center text-center">
<t t-set="memory_metric" t-value="dashboard_ids.filtered(lambda m: m.name == 'memory_usage')"/>
<h3 class="mb-0" t-att-class="memory_metric and memory_metric.status == 'warning' and 'text-warning' or memory_metric.status == 'danger' and 'text-danger' or ''">
<t t-esc="memory_metric and memory_metric.value or '0'"/>
<small><t t-esc="memory_metric and memory_metric.unit or '%'"/></small>
</h3>
<div class="progress w-75 mt-2">
<div class="progress-bar" role="progressbar"
t-att-style="'width: ' + (memory_metric and str(memory_metric.value) or '0') + '%;'"
t-att-class="memory_metric and memory_metric.status == 'warning' and 'bg-warning' or memory_metric.status == 'danger' and 'bg-danger' or 'bg-success'"
t-att-aria-valuenow="memory_metric and memory_metric.value or 0" aria-valuemin="0" aria-valuemax="100">
</div>
</div>
</div>
</div>
</div>
<!-- Network Bandwidth -->
<div class="col-md-6 col-lg-4 mb-3">
<div class="card h-100">
<div class="card-header d-flex justify-content-between align-items-center bg-light">
<h6 class="m-0">Network Bandwidth</h6>
<i class="fa fa-network-wired"></i>
</div>
<div class="card-body d-flex flex-column align-items-center justify-content-center text-center">
<t t-set="bandwidth_metric" t-value="dashboard_ids.filtered(lambda m: m.name == 'bandwidth_usage')"/>
<h3 class="mb-0" t-att-class="bandwidth_metric and bandwidth_metric.status == 'warning' and 'text-warning' or bandwidth_metric.status == 'danger' and 'text-danger' or ''">
<t t-esc="bandwidth_metric and bandwidth_metric.value or '0'"/>
<small><t t-esc="bandwidth_metric and bandwidth_metric.unit or 'Mbps'"/></small>
</h3>
<div class="text-muted">Current Network Load</div>
</div>
</div>
</div>
<!-- WAN Status -->
<div class="col-md-6 col-lg-4 mb-3">
<div class="card h-100">
<div class="card-header d-flex justify-content-between align-items-center bg-light">
<h6 class="m-0">WAN Status</h6>
<i class="fa fa-globe"></i>
</div>
<div class="card-body d-flex flex-column align-items-center justify-content-center text-center">
<t t-set="wan_metric" t-value="dashboard_ids.filtered(lambda m: m.name == 'wan_status')"/>
<t t-if="wan_metric and wan_metric.value == 'connected'">
<i class="fa fa-check-circle fa-3x text-success mb-2"></i>
<div>Connected</div>
<div class="text-muted small">Internet is available</div>
</t>
<t t-else="">
<i class="fa fa-times-circle fa-3x text-danger mb-2"></i>
<div>Disconnected</div>
<div class="text-muted small">Internet is unavailable</div>
</t>
</div>
</div>
</div>
<!-- Device Status -->
<div class="col-md-6 col-lg-4 mb-3">
<div class="card h-100">
<div class="card-header d-flex justify-content-between align-items-center bg-light">
<h6 class="m-0">Device Status</h6>
<i class="fa fa-wifi"></i>
</div>
<div class="card-body d-flex flex-column align-items-center justify-content-center text-center">
<t t-set="device_metric" t-value="dashboard_ids.filtered(lambda m: m.name == 'device_status')"/>
<h3 class="mb-0">
<t t-esc="device_metric and device_metric.value or '0'"/>
<small>online</small>
</h3>
<div class="text-muted">Out of <t t-esc="device_metric and device_metric.description or '0'"/> devices</div>
</div>
</div>
</div>
<!-- Security Threats -->
<div class="col-md-6 col-lg-4 mb-3">
<div class="card h-100">
<div class="card-header d-flex justify-content-between align-items-center bg-light">
<h6 class="m-0">Security Threats</h6>
<i class="fa fa-shield"></i>
</div>
<div class="card-body d-flex flex-column align-items-center justify-content-center text-center">
<t t-set="threat_metric" t-value="dashboard_ids.filtered(lambda m: m.name == 'security_threats')"/>
<h3 class="mb-0" t-att-class="threat_metric and int(threat_metric.value) > 0 and 'text-danger' or 'text-success'">
<t t-esc="threat_metric and threat_metric.value or '0'"/>
</h3>
<div t-att-class="threat_metric and int(threat_metric.value) > 0 and 'text-danger' or 'text-success'">
<t t-if="threat_metric and int(threat_metric.value) > 0">Threats Detected</t>
<t t-else="">No Threats</t>
</div>
</div>
</div>
</div>
</div>
<!-- Last Update Info -->
<div class="text-muted text-end">Last updated:
<t t-esc="dashboard_ids and dashboard_ids[0].last_update or ''"/>
</div>
</div>
</dashboard>
</field>
</record>
<!-- UDM Site Search View -->
<record id="view_udm_site_search" model="ir.ui.view">
<field name="name">udm.site.search</field>
<field name="model">udm.site</field>
<field name="arch" type="xml">
<search string="Search UDM Pro Sites">
<field name="name"/>
<field name="site_id"/>
<filter string="Active" name="active" domain="[('active', '=', True)]"/>
<filter string="Inactive" name="inactive" domain="[('active', '=', False)]"/>
</search>
</field>
</record>
<!-- UDM Site Action -->
<record id="action_udm_site" model="ir.actions.act_window">
<field name="name">UDM Sites</field>
<field name="res_model">udm.site</field>
<field name="view_mode">tree,form,dashboard</field>
<field name="context">{'search_default_active': 1}</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
Create a new UDM Pro Site
</p>
<p>
Create sites to organize your UDM Pro configurations by location or purpose.
</p>
</field>
</record>
<!-- Dashboard Action -->
<record id="action_udm_dashboard" model="ir.actions.act_window">
<field name="name">Dashboard</field>
<field name="res_model">udm.site</field>
<field name="view_mode">dashboard,form</field>
<field name="context">{'search_default_active': 1}</field>
<field name="domain">[('active', '=', True)]</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
No UDM Pro sites found
</p>
<p>
Create a site and configure UDM Pro to view the dashboard.
</p>
</field>
</record>
</odoo>

View file

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- System Info Form View -->
<record id="view_udm_system_info_form" model="ir.ui.view">
<field name="name">udm.system.info.form</field>
<field name="model">udm.system.info</field>
<field name="arch" type="xml">
<form string="UDM Pro System Information">
<sheet>
<group>
<group>
<field name="hostname"/>
<field name="version"/>
<field name="model"/>
<field name="serial"/>
</group>
<group>
<field name="mac_address"/>
<field name="uptime" invisible="1"/>
<field name="uptime_human"/>
<field name="config_id"/>
</group>
</group>
<notebook>
<page string="Raw Data" name="raw_data">
<field name="raw_data" widget="ace" options="{'mode': 'json'}" help="Données JSON brutes de la configuration"/>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
</odoo>

View file

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- User Form View -->
<record id="view_udm_user_form" model="ir.ui.view">
<field name="name">udm.user.form</field>
<field name="model">udm.user</field>
<field name="arch" type="xml">
<form string="UDM Pro User">
<sheet>
<div class="oe_title">
<h1>
<field name="name"/>
</h1>
</div>
<group>
<group>
<field name="email"/>
<field name="role"/>
</group>
<group>
<field name="enabled"/>
<field name="is_admin"/>
<field name="config_id"/>
</group>
</group>
<notebook>
<page string="Raw Data" name="raw_data">
<field name="raw_data" widget="ace" options="{'mode': 'json'}" help="Données JSON brutes de la configuration"/>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
</odoo>

View file

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- VLAN Form View -->
<record id="view_udm_vlan_form" model="ir.ui.view">
<field name="name">udm.vlan.form</field>
<field name="model">udm.vlan</field>
<field name="arch" type="xml">
<form string="UDM Pro VLAN">
<sheet>
<group>
<group>
<field name="vlan_id"/>
<field name="name"/>
</group>
<group>
<field name="enabled"/>
<field name="config_id"/>
<field name="network_count"/>
</group>
</group>
<notebook>
<page string="Networks" name="networks">
<field name="network_ids" nolabel="1">
<tree>
<field name="name"/>
<field name="purpose"/>
<field name="subnet"/>
</tree>
</field>
</page>
<page string="Raw Data" name="raw_data">
<field name="raw_data" widget="ace" options="{'mode': 'json'}" help="Données JSON brutes de la configuration"/>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
</odoo>