unifi to test
This commit is contained in:
parent
ec757e1883
commit
5bfcaa37c2
29 changed files with 3424 additions and 0 deletions
4
unifi_integration/__init__.py
Normal file
4
unifi_integration/__init__.py
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import models
|
||||
from . import controllers
|
||||
46
unifi_integration/__manifest__.py
Normal file
46
unifi_integration/__manifest__.py
Normal 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',
|
||||
],
|
||||
},
|
||||
}
|
||||
4
unifi_integration/controllers/__init__.py
Normal file
4
unifi_integration/controllers/__init__.py
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Import du module principal des contrôleurs
|
||||
from . import main # pylint: disable=relative-beyond-top-level
|
||||
691
unifi_integration/controllers/main.py
Normal file
691
unifi_integration/controllers/main.py
Normal 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
|
||||
9
unifi_integration/models/__init__.py
Normal file
9
unifi_integration/models/__init__.py
Normal 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
|
||||
542
unifi_integration/models/udm_config.py
Normal file
542
unifi_integration/models/udm_config.py
Normal 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()
|
||||
42
unifi_integration/models/udm_device.py
Normal file
42
unifi_integration/models/udm_device.py
Normal 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'
|
||||
56
unifi_integration/models/udm_firewall.py
Normal file
56
unifi_integration/models/udm_firewall.py
Normal 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'
|
||||
64
unifi_integration/models/udm_network.py
Normal file
64
unifi_integration/models/udm_network.py
Normal 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)
|
||||
27
unifi_integration/models/udm_settings.py
Normal file
27
unifi_integration/models/udm_settings.py
Normal 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
|
||||
41
unifi_integration/models/udm_system_info.py
Normal file
41
unifi_integration/models/udm_system_info.py
Normal 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)
|
||||
23
unifi_integration/models/udm_user.py
Normal file
23
unifi_integration/models/udm_user.py
Normal 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
|
||||
22
unifi_integration/security/ir.model.access.csv
Normal file
22
unifi_integration/security/ir.model.access.csv
Normal 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.
|
54
unifi_integration/security/udm_pro_security.xml
Normal file
54
unifi_integration/security/udm_pro_security.xml
Normal 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>
|
||||
1
unifi_integration/static/description/icon.png
Normal file
1
unifi_integration/static/description/icon.png
Normal file
|
|
@ -0,0 +1 @@
|
|||
|
||||
70
unifi_integration/static/src/css/udm_pro.css
Normal file
70
unifi_integration/static/src/css/udm_pro.css
Normal 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;
|
||||
}
|
||||
389
unifi_integration/views/templates.xml
Normal file
389
unifi_integration/views/templates.xml
Normal 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&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>
|
||||
418
unifi_integration/views/udm_config_views.xml
Normal file
418
unifi_integration/views/udm_config_views.xml
Normal 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>
|
||||
138
unifi_integration/views/udm_configuration_views.xml
Normal file
138
unifi_integration/views/udm_configuration_views.xml
Normal 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>
|
||||
79
unifi_integration/views/udm_dashboard_metric_views.xml
Normal file
79
unifi_integration/views/udm_dashboard_metric_views.xml
Normal 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>
|
||||
37
unifi_integration/views/udm_device_views.xml
Normal file
37
unifi_integration/views/udm_device_views.xml
Normal 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>
|
||||
41
unifi_integration/views/udm_firewall_views.xml
Normal file
41
unifi_integration/views/udm_firewall_views.xml
Normal 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>
|
||||
171
unifi_integration/views/udm_menu_views.xml
Normal file
171
unifi_integration/views/udm_menu_views.xml
Normal 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>
|
||||
38
unifi_integration/views/udm_network_views.xml
Normal file
38
unifi_integration/views/udm_network_views.xml
Normal 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>
|
||||
29
unifi_integration/views/udm_settings_views.xml
Normal file
29
unifi_integration/views/udm_settings_views.xml
Normal 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>
|
||||
281
unifi_integration/views/udm_site_views.xml
Normal file
281
unifi_integration/views/udm_site_views.xml
Normal 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>
|
||||
33
unifi_integration/views/udm_system_info_views.xml
Normal file
33
unifi_integration/views/udm_system_info_views.xml
Normal 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>
|
||||
35
unifi_integration/views/udm_user_views.xml
Normal file
35
unifi_integration/views/udm_user_views.xml
Normal 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>
|
||||
39
unifi_integration/views/udm_vlan_views.xml
Normal file
39
unifi_integration/views/udm_vlan_views.xml
Normal 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>
|
||||
Loading…
Reference in a new issue