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