fml unifi
This commit is contained in:
parent
5bfcaa37c2
commit
b2183d8601
57 changed files with 5422 additions and 630 deletions
158
unifi_integration/README.md
Normal file
158
unifi_integration/README.md
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
# UniFi Integration for Odoo
|
||||
|
||||
Module d'intégration entre Odoo et les appareils UniFi Network (UDM Pro, UCG Max).
|
||||
|
||||
## API Integration Notes
|
||||
|
||||
### Authentication
|
||||
|
||||
The UniFi API requires specific endpoints and headers for authentication. Note that there are critical differences between UniFi controllers and the UDM Pro/UCG Max API:
|
||||
|
||||
```python
|
||||
# Python Example
|
||||
import requests
|
||||
from requests.packages.urllib3.exceptions import InsecureRequestWarning
|
||||
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
|
||||
|
||||
headers = {
|
||||
"Accept": "application/json",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
data = {
|
||||
'username': 'your_username',
|
||||
'password': 'your_password',
|
||||
'rememberMe': True
|
||||
}
|
||||
|
||||
session = requests.Session()
|
||||
response = session.post(
|
||||
'https://udmp:443/api/auth/login',
|
||||
headers=headers,
|
||||
json=data,
|
||||
verify=False
|
||||
)
|
||||
```
|
||||
|
||||
### API Endpoints
|
||||
|
||||
All API endpoints (except authentication) must be prefixed with `/proxy/network`. Common endpoints include:
|
||||
|
||||
#### System and Status
|
||||
- System Health: `/api/system/health`
|
||||
- System Info: `/proxy/network/api/s/{site}/stat/sysinfo`
|
||||
- Dashboard Health: `/proxy/network/api/s/{site}/stat/health`
|
||||
|
||||
#### Network Configuration
|
||||
- Networks: `/proxy/network/api/s/{site}/rest/networkconf`
|
||||
- Firewall Rules: `/proxy/network/api/s/{site}/rest/firewallrule`
|
||||
- Port Forwards: `/proxy/network/api/s/{site}/stat/portforward`
|
||||
- Routes: `/proxy/network/api/s/{site}/rest/routing`
|
||||
|
||||
#### Devices and Clients
|
||||
- All Devices: `/proxy/network/api/s/{site}/stat/device`
|
||||
- Basic Device Info: `/proxy/network/api/s/{site}/stat/device-basic`
|
||||
- Active Clients: `/proxy/network/api/s/{site}/stat/sta`
|
||||
- All Clients: `/proxy/network/api/s/{site}/stat/user`
|
||||
|
||||
### Headers Required
|
||||
|
||||
All API requests must include these headers:
|
||||
|
||||
```python
|
||||
headers = {
|
||||
"Accept": "application/json",
|
||||
"Content-Type": "application/json",
|
||||
"User-Agent": "Odoo UniFi Integration"
|
||||
}
|
||||
|
||||
# Add CSRF token for authenticated requests
|
||||
if csrf_token:
|
||||
headers["X-CSRF-Token"] = csrf_token
|
||||
```
|
||||
|
||||
### SSL Verification
|
||||
|
||||
The UDM Pro uses self-signed certificates. SSL verification must be disabled:
|
||||
|
||||
```python
|
||||
# Disable SSL verification warnings
|
||||
from requests.packages.urllib3.exceptions import InsecureRequestWarning
|
||||
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
|
||||
|
||||
# Skip certificate validation in requests
|
||||
verify=False
|
||||
```
|
||||
|
||||
## Module Features
|
||||
|
||||
### Network Configuration
|
||||
- Import and manage network settings
|
||||
- Configure port forwarding rules
|
||||
- Manage firewall rules and groups
|
||||
- Handle routing configuration
|
||||
|
||||
### Device Management
|
||||
- Monitor device status and health
|
||||
- Track active and configured clients
|
||||
- View detailed device statistics
|
||||
|
||||
### System Monitoring
|
||||
- Dashboard with health metrics
|
||||
- System performance statistics
|
||||
- Network usage and throughput
|
||||
|
||||
### Multi-Site Support
|
||||
- Manage multiple UniFi sites
|
||||
- Site-specific configurations
|
||||
- Independent metrics per site
|
||||
|
||||
## Installation
|
||||
|
||||
1. Install the module in your Odoo instance
|
||||
2. Configure your UDM Pro credentials in the UniFi Integration settings
|
||||
3. Import your network configuration
|
||||
|
||||
## Configuration
|
||||
|
||||
### Site Setup
|
||||
1. Go to UniFi Integration > Sites
|
||||
2. Create a new site with:
|
||||
- Name: Your site name
|
||||
- Site ID: Usually "default" unless configured otherwise
|
||||
- Description: Optional site description
|
||||
- Physical Address: Optional location information
|
||||
|
||||
### UDM Pro Configuration
|
||||
1. Add your UDM Pro configuration:
|
||||
- Host: IP address or hostname
|
||||
- Port: Usually 443 (HTTPS)
|
||||
- Username: Your UDM Pro username
|
||||
- Password: Your UDM Pro password
|
||||
- MFA Token: If two-factor authentication is enabled
|
||||
|
||||
### API Access
|
||||
1. Create a dedicated API user in UDM Pro
|
||||
2. Assign minimum required permissions
|
||||
3. Use secure network access (VPN or internal network)
|
||||
|
||||
## Security Considerations
|
||||
|
||||
### Authentication
|
||||
- Use dedicated API credentials
|
||||
- Enable two-factor authentication when possible
|
||||
- Regularly rotate passwords
|
||||
|
||||
### Network Security
|
||||
- Restrict API access to specific networks
|
||||
- Use VPN for remote access
|
||||
- Configure appropriate firewall rules
|
||||
|
||||
### Monitoring
|
||||
- Monitor API access logs
|
||||
- Track authentication failures
|
||||
- Review system alerts and notifications
|
||||
|
||||
### Data Protection
|
||||
- Store credentials securely
|
||||
- Encrypt sensitive configuration data
|
||||
- Regular security audits
|
||||
|
|
@ -2,3 +2,4 @@
|
|||
|
||||
from . import models
|
||||
from . import controllers
|
||||
from . import wizards
|
||||
|
|
|
|||
|
|
@ -27,8 +27,12 @@ This module allows you to:
|
|||
'views/udm_user_views.xml',
|
||||
'views/udm_settings_views.xml',
|
||||
'views/udm_firewall_views.xml',
|
||||
'views/udm_network_config_views.xml',
|
||||
'views/udm_menu_views.xml',
|
||||
'views/templates.xml',
|
||||
'views/udm_pro_docs_templates.xml',
|
||||
'wizards/udm_site_import_wizard_views.xml',
|
||||
'wizards/views/udm_mfa_wizard_views.xml',
|
||||
],
|
||||
'demo': [],
|
||||
'installable': True,
|
||||
|
|
@ -40,7 +44,7 @@ This module allows you to:
|
|||
},
|
||||
'assets': {
|
||||
'web.assets_backend': [
|
||||
'udm_pro_docs/static/src/css/udm_pro.css',
|
||||
'unifi_integration/static/src/css/udm_pro.css',
|
||||
],
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,41 +1,40 @@
|
|||
# -*- 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
|
||||
from odoo import http, _ # IDE may report an error, but works in Odoo environment
|
||||
from odoo.http import request # IDE may report an error, but works in Odoo environment
|
||||
# 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 json # Required to parse API responses and used in processing methods
|
||||
import requests
|
||||
from requests.exceptions import RequestException, ConnectionError
|
||||
import urllib3
|
||||
from urllib3.exceptions import InsecureRequestWarning
|
||||
from datetime import datetime
|
||||
|
||||
# Supprimer les avertissements pour les connexions non sécurisées
|
||||
# Remove warnings for insecure connections
|
||||
try:
|
||||
# Pour les versions plus récentes de requests
|
||||
# For newer versions of requests
|
||||
import urllib3
|
||||
urllib3.disable_warnings(category=InsecureRequestWarning)
|
||||
except ImportError:
|
||||
# Pour les versions plus anciennes de requests
|
||||
# For older versions of 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
|
||||
# Disable pylint security warning for this specific line
|
||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||
except AttributeError:
|
||||
# Si ni l'un ni l'autre ne fonctionne, nous ignorons silencieusement
|
||||
# If neither works, we silently ignore
|
||||
pass
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
class UdmProController(http.Controller):
|
||||
"""Contrôleur pour les fonctionnalités liées à UDM Pro dans Odoo"""
|
||||
"""Controller for UDM Pro related functionalities in 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', {
|
||||
"""Display the advanced options form for the UDM Pro API"""
|
||||
return request.render('unifi_integration.advanced_options_form', {
|
||||
'default_site': 'default',
|
||||
'fixed_only': True,
|
||||
'lowercase_hostnames': True,
|
||||
|
|
@ -43,37 +42,40 @@ class UdmProController(http.Controller):
|
|||
|
||||
@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', {
|
||||
"""Display the form to restart a UDM Pro device"""
|
||||
if not request.env.user.has_group('unifi_integration.group_udm_pro_manager'):
|
||||
return request.render('unifi_integration.access_denied', {
|
||||
'error_message': _("You don't have permission to restart devices.")
|
||||
})
|
||||
|
||||
# Récupérer les configurations UDM Pro enregistrées pour le formulaire
|
||||
# Get saved UDM Pro configurations for the form
|
||||
configs = request.env['udm.configuration'].sudo().search([])
|
||||
return request.render('udm_pro_docs.restart_device_form', {
|
||||
return request.render('unifi_integration.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', {
|
||||
"""Process the request to restart a UDM Pro device"""
|
||||
if not request.env.user.has_group('unifi_integration.group_udm_pro_manager'):
|
||||
return request.render('unifi_integration.access_denied', {
|
||||
'error_message': _("You don't have permission to restart devices.")
|
||||
})
|
||||
|
||||
config_id = int(post.get('config_id'))
|
||||
config_id_str = post.get('config_id')
|
||||
if not config_id_str:
|
||||
raise ValueError(_('Configuration ID is required'))
|
||||
config_id = int(config_id_str)
|
||||
mac_address = post.get('mac_address')
|
||||
|
||||
if not mac_address:
|
||||
return request.render('udm_pro_docs.restart_device_form', {
|
||||
return request.render('unifi_integration.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
|
||||
# Get the configuration
|
||||
config = request.env['udm.configuration'].sudo().browse(config_id)
|
||||
if not config.exists():
|
||||
raise ValueError(_("Configuration not found"))
|
||||
|
|
@ -86,44 +88,44 @@ class UdmProController(http.Controller):
|
|||
port=config.port or 443
|
||||
)
|
||||
|
||||
# Authentifier le client
|
||||
# Authenticate the client
|
||||
if not client.login():
|
||||
return request.render('udm_pro_docs.restart_device_form', {
|
||||
return request.render('unifi_integration.restart_device_form', {
|
||||
'error_message': _("Authentication failed. Please check the configuration credentials."),
|
||||
'configs': request.env['udm.configuration'].sudo().search([])
|
||||
})
|
||||
|
||||
# Redémarrer l'appareil
|
||||
# Restart the device
|
||||
success = client.restart_device(mac_address)
|
||||
if not success:
|
||||
return request.render('udm_pro_docs.restart_device_form', {
|
||||
return request.render('unifi_integration.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', {
|
||||
return request.render('unifi_integration.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', {
|
||||
return request.render('unifi_integration.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', {
|
||||
return request.render('unifi_integration.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
|
||||
"""Display the form to generate a hosts file from UDM Pro"""
|
||||
# Get saved UDM Pro configurations for the form
|
||||
configs = request.env['udm.configuration'].sudo().search([])
|
||||
return request.render('udm_pro_docs.generate_hosts_form', {
|
||||
return request.render('unifi_integration.generate_hosts_form', {
|
||||
'configs': configs,
|
||||
'fixed_only': True,
|
||||
'lowercase_hostnames': True
|
||||
|
|
@ -131,18 +133,21 @@ class UdmProController(http.Controller):
|
|||
|
||||
@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'))
|
||||
"""Generate a hosts file from UDM Pro network clients"""
|
||||
config_id_str = post.get('config_id')
|
||||
if not config_id_str:
|
||||
raise ValueError(_('Configuration ID is required'))
|
||||
config_id = int(config_id_str)
|
||||
fixed_only = post.get('fixed_only') == 'on'
|
||||
lowercase_hostnames = post.get('lowercase_hostnames') == 'on'
|
||||
|
||||
try:
|
||||
# Récupérer la configuration
|
||||
# Get the 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
|
||||
# Initialize the UDM Pro client with advanced options
|
||||
client = UdmProClient(
|
||||
host=config.host,
|
||||
username=config.username,
|
||||
|
|
@ -152,27 +157,34 @@ class UdmProController(http.Controller):
|
|||
lowercase_hostnames=lowercase_hostnames
|
||||
)
|
||||
|
||||
# Authentifier le client
|
||||
# Authenticate the client
|
||||
if not client.login():
|
||||
return request.render('udm_pro_docs.generate_hosts_form', {
|
||||
return request.render('unifi_integration.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
|
||||
# Generate the hosts file
|
||||
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
|
||||
# Return the content as a downloadable file
|
||||
# Create and configure the HTTP response
|
||||
try:
|
||||
response = request.make_response(hosts_content)
|
||||
if not response:
|
||||
raise ValueError(_('Failed to create response'))
|
||||
response.headers['Content-Type'] = 'text/plain'
|
||||
response.headers['Content-Disposition'] = 'attachment; filename=udm_hosts.txt'
|
||||
return response
|
||||
except (AttributeError, TypeError) as e:
|
||||
_logger.error('Error creating response: %s', str(e))
|
||||
raise ValueError(_('Failed to create 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', {
|
||||
return request.render('unifi_integration.generate_hosts_form', {
|
||||
'error_message': _("Error connecting to UDM Pro: %s") % str(e),
|
||||
'configs': request.env['udm.configuration'].sudo().search([]),
|
||||
'fixed_only': fixed_only,
|
||||
|
|
@ -180,7 +192,7 @@ class UdmProController(http.Controller):
|
|||
})
|
||||
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', {
|
||||
return request.render('unifi_integration.generate_hosts_form', {
|
||||
'error_message': _("An unexpected error occurred: %s") % str(e),
|
||||
'configs': request.env['udm.configuration'].sudo().search([]),
|
||||
'fixed_only': fixed_only,
|
||||
|
|
@ -189,10 +201,10 @@ class UdmProController(http.Controller):
|
|||
|
||||
@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
|
||||
"""Display the form to view UDM Pro network clients"""
|
||||
# Get saved UDM Pro configurations for the form
|
||||
configs = request.env['udm.configuration'].sudo().search([])
|
||||
return request.render('udm_pro_docs.network_clients_form', {
|
||||
return request.render('unifi_integration.network_clients_form', {
|
||||
'configs': configs,
|
||||
'fixed_only': False,
|
||||
'lowercase_hostnames': True
|
||||
|
|
@ -200,18 +212,21 @@ class UdmProController(http.Controller):
|
|||
|
||||
@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'))
|
||||
"""Get and display the list of UDM Pro network clients"""
|
||||
config_id_str = post.get('config_id')
|
||||
if not config_id_str:
|
||||
raise ValueError(_('Configuration ID is required'))
|
||||
config_id = int(config_id_str)
|
||||
fixed_only = post.get('fixed_only') == 'on'
|
||||
lowercase_hostnames = post.get('lowercase_hostnames') == 'on'
|
||||
|
||||
try:
|
||||
# Récupérer la configuration
|
||||
# Get the 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
|
||||
# Initialize the UDM Pro client with advanced options
|
||||
client = UdmProClient(
|
||||
host=config.host,
|
||||
username=config.username,
|
||||
|
|
@ -221,26 +236,26 @@ class UdmProController(http.Controller):
|
|||
lowercase_hostnames=lowercase_hostnames
|
||||
)
|
||||
|
||||
# Authentifier le client
|
||||
# Authenticate the client
|
||||
if not client.login():
|
||||
return request.render('udm_pro_docs.network_clients_form', {
|
||||
return request.render('unifi_integration.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
|
||||
# Get network clients
|
||||
network_clients = client.get_network_clients()
|
||||
|
||||
return request.render('udm_pro_docs.network_clients_result', {
|
||||
return request.render('unifi_integration.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', {
|
||||
return request.render('unifi_integration.network_clients_form', {
|
||||
'error_message': _("Error connecting to UDM Pro: %s") % str(e),
|
||||
'configs': request.env['udm.configuration'].sudo().search([]),
|
||||
'fixed_only': fixed_only,
|
||||
|
|
@ -248,7 +263,7 @@ class UdmProController(http.Controller):
|
|||
})
|
||||
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', {
|
||||
return request.render('unifi_integration.network_clients_form', {
|
||||
'error_message': _("Configuration error: %s") % str(e),
|
||||
'configs': request.env['udm.configuration'].sudo().search([]),
|
||||
'fixed_only': fixed_only,
|
||||
|
|
@ -256,16 +271,16 @@ class UdmProController(http.Controller):
|
|||
})
|
||||
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', {
|
||||
return request.render('unifi_integration.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
|
||||
# Keep this generic exception as a last resort, with an explicit warning for pylint
|
||||
_logger.exception("Unexpected error retrieving UDM Pro network clients")
|
||||
return request.render('udm_pro_docs.network_clients_form', {
|
||||
return request.render('unifi_integration.network_clients_form', {
|
||||
'error_message': _("An unexpected error occurred: %s") % str(e),
|
||||
'configs': request.env['udm.configuration'].sudo().search([]),
|
||||
'fixed_only': fixed_only,
|
||||
|
|
@ -274,24 +289,25 @@ class UdmProController(http.Controller):
|
|||
|
||||
@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', {})
|
||||
"""Display the UDM Pro configuration import form"""
|
||||
return request.render('unifi_integration.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', {
|
||||
"""Import UDM Pro configuration from the device"""
|
||||
if not request.env.user.has_group('unifi_integration.group_udm_pro_manager'):
|
||||
return request.render('unifi_integration.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)
|
||||
port_str = post.get('port')
|
||||
port = int(port_str) if port_str else 443
|
||||
|
||||
if not all([host, username, password]):
|
||||
return request.render('udm_pro_docs.import_config_form', {
|
||||
return request.render('unifi_integration.import_config_form', {
|
||||
'error_message': _("Please provide all required fields."),
|
||||
'host': host,
|
||||
'username': username,
|
||||
|
|
@ -299,10 +315,10 @@ class UdmProController(http.Controller):
|
|||
})
|
||||
|
||||
try:
|
||||
# Utiliser le client API pour récupérer la configuration
|
||||
# Use the API client to get the configuration
|
||||
client = UdmProClient(host, username, password, port)
|
||||
if not client.login():
|
||||
return request.render('udm_pro_docs.import_config_form', {
|
||||
return request.render('unifi_integration.import_config_form', {
|
||||
'error_message': _("Authentication failed. Please check your credentials."),
|
||||
'host': host,
|
||||
'username': username,
|
||||
|
|
@ -311,14 +327,14 @@ class UdmProController(http.Controller):
|
|||
|
||||
config_data = client.get_full_configuration()
|
||||
|
||||
# Importer la configuration dans Odoo
|
||||
# Import the configuration into 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', {
|
||||
return request.render('unifi_integration.import_config_form', {
|
||||
'error_message': _("Error connecting to UDM Pro: %s") % str(e),
|
||||
'host': host,
|
||||
'username': username,
|
||||
|
|
@ -326,7 +342,7 @@ class UdmProController(http.Controller):
|
|||
})
|
||||
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', {
|
||||
return request.render('unifi_integration.import_config_form', {
|
||||
'error_message': _("Error processing data: %s") % str(e),
|
||||
'host': host,
|
||||
'username': username,
|
||||
|
|
@ -334,7 +350,7 @@ class UdmProController(http.Controller):
|
|||
})
|
||||
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', {
|
||||
return request.render('unifi_integration.import_config_form', {
|
||||
'error_message': _("An unexpected error occurred. Please check server logs."),
|
||||
'host': host,
|
||||
'username': username,
|
||||
|
|
@ -343,9 +359,9 @@ class UdmProController(http.Controller):
|
|||
|
||||
|
||||
class UdmProClient:
|
||||
"""Client pour interagir avec l'API UDM Pro."""
|
||||
"""Client to interact with the UDM Pro API."""
|
||||
|
||||
# Points d'accès de l'API
|
||||
# API endpoints
|
||||
API_LOGIN_ENDPOINT = '/api/auth/login'
|
||||
API_SYSTEM_INFO_ENDPOINT = '/api/system'
|
||||
API_NETWORK_ENDPOINT = '/api/networks'
|
||||
|
|
@ -354,25 +370,25 @@ class UdmProClient:
|
|||
API_SETTINGS_ENDPOINT = '/api/settings'
|
||||
API_FIREWALL_ENDPOINT = '/api/firewall'
|
||||
|
||||
# Nouveaux endpoints inspirés du client Go
|
||||
# New endpoints inspired by the Go client
|
||||
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.
|
||||
Initialize the UDM Pro API client.
|
||||
|
||||
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)
|
||||
host (str): IP address or hostname of the UDM Pro
|
||||
username (str): Username for the API
|
||||
password (str): Password for the API
|
||||
port (int): Port for connection (default 443)
|
||||
verify_ssl (bool): Verify SSL certificate (default False)
|
||||
site (str): Site identifier for UniFi API (default 'default')
|
||||
fixed_only (bool): Only consider clients with fixed IP address (default True)
|
||||
lowercase_hostnames (bool): Convert hostnames to lowercase (default True)
|
||||
debug (bool): Enable debug mode for HTTP requests (default False)
|
||||
"""
|
||||
self.host = host
|
||||
self.username = username
|
||||
|
|
@ -390,16 +406,16 @@ class UdmProClient:
|
|||
self.session.verify = verify_ssl
|
||||
|
||||
def _get_auth_headers(self):
|
||||
"""Retourne les en-têtes d'authentification."""
|
||||
"""Return authentication headers."""
|
||||
if not self.token:
|
||||
raise ValueError("Non authentifié. Appelez login() d'abord.")
|
||||
raise ValueError("Not authenticated. Call login() first.")
|
||||
|
||||
headers = {
|
||||
"Authorization": f"Bearer {self.token}",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
|
||||
# Ajouter le token CSRF si disponible
|
||||
# Add CSRF token if available
|
||||
if self.csrf_token:
|
||||
headers["X-Csrf-Token"] = self.csrf_token
|
||||
|
||||
|
|
@ -407,10 +423,10 @@ class UdmProClient:
|
|||
|
||||
def login(self):
|
||||
"""
|
||||
Authentifie le client auprès de l'API UDM Pro.
|
||||
Authenticates the client with the UDM Pro API.
|
||||
|
||||
Returns:
|
||||
bool: True si l'authentification a réussi, False sinon
|
||||
bool: True if authentication succeeded, False otherwise
|
||||
"""
|
||||
try:
|
||||
login_url = f"{self.base_url}{self.API_LOGIN_ENDPOINT}"
|
||||
|
|
@ -419,139 +435,145 @@ class UdmProClient:
|
|||
"password": self.password
|
||||
}
|
||||
|
||||
_logger.debug("Tentative de connexion à %s", login_url)
|
||||
_logger.debug("Attempting to connect to %s", login_url)
|
||||
response = self.session.post(login_url, json=payload)
|
||||
response.raise_for_status()
|
||||
|
||||
# Capture du token CSRF s'il existe
|
||||
# Capture CSRF token if it exists
|
||||
csrf_token = response.headers.get('X-Csrf-Token')
|
||||
if csrf_token:
|
||||
self.csrf_token = csrf_token
|
||||
_logger.debug("Token CSRF capturé: %s", csrf_token)
|
||||
_logger.debug("CSRF token captured: %s", csrf_token)
|
||||
|
||||
data = response.json()
|
||||
if not data.get('token'):
|
||||
_logger.error("Aucun token n'a été retourné par l'API")
|
||||
_logger.error("No token was returned by the API")
|
||||
return False
|
||||
|
||||
self.token = data['token']
|
||||
_logger.debug("Authentification réussie")
|
||||
_logger.debug("Authentication successful")
|
||||
return True
|
||||
|
||||
except RequestException as e:
|
||||
_logger.error("Erreur d'authentification: %s", str(e))
|
||||
_logger.error("Authentication error: %s", str(e))
|
||||
return False
|
||||
|
||||
def _make_api_request(self, method, endpoint, params=None, data=None, retry=True):
|
||||
"""
|
||||
Effectue une requête API.
|
||||
Makes an API request.
|
||||
|
||||
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
|
||||
method (str): HTTP method (GET, POST, etc.)
|
||||
endpoint (str): API endpoint
|
||||
params (dict): Request parameters
|
||||
data (dict): Data to send in request body
|
||||
retry (bool): Retry on authentication error
|
||||
|
||||
Returns:
|
||||
dict: Réponse JSON de l'API
|
||||
dict: JSON response from API
|
||||
"""
|
||||
if not self.token and retry:
|
||||
_logger.debug("Non authentifié, tentative d'authentification")
|
||||
_logger.debug("Not authenticated, attempting authentication")
|
||||
if not self.login():
|
||||
raise ConnectionError("Impossible de s'authentifier à l'API UDM Pro")
|
||||
raise ConnectionError("Unable to authenticate with UDM Pro API")
|
||||
|
||||
url = f"{self.base_url}{endpoint}"
|
||||
try:
|
||||
_logger.debug("Requête %s vers %s", method, url)
|
||||
_logger.debug("Request %s to %s", method, url)
|
||||
headers = self._get_auth_headers()
|
||||
response = None
|
||||
|
||||
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))
|
||||
try:
|
||||
response = self.session.request(
|
||||
method=method,
|
||||
url=url,
|
||||
headers=headers,
|
||||
params=params,
|
||||
json=data
|
||||
)
|
||||
|
||||
# Capture CSRF token if it exists in the response
|
||||
csrf_token = response.headers.get('X-Csrf-Token')
|
||||
if csrf_token and csrf_token != self.csrf_token:
|
||||
self.csrf_token = csrf_token
|
||||
_logger.debug("CSRF token updated: %s", csrf_token)
|
||||
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
except RequestException as request_error:
|
||||
if response and (response.status_code == 401 or response.status_code == 403):
|
||||
if retry:
|
||||
_logger.debug("Token expired, attempting re-authentication")
|
||||
self.token = None
|
||||
return self._make_api_request(method, endpoint, params, data, retry=False)
|
||||
_logger.error("API error (%s %s): %s", method, url, str(request_error))
|
||||
raise
|
||||
|
||||
except Exception as e:
|
||||
_logger.error("Unexpected error in API request: %s", str(e))
|
||||
raise
|
||||
|
||||
def get_system_info(self):
|
||||
"""
|
||||
Récupère les informations système de l'UDM Pro.
|
||||
Gets system information from UDM Pro.
|
||||
|
||||
Returns:
|
||||
dict: Informations système
|
||||
dict: System information
|
||||
"""
|
||||
return self._make_api_request('GET', self.API_SYSTEM_INFO_ENDPOINT)
|
||||
|
||||
def get_networks(self):
|
||||
"""
|
||||
Récupère la configuration des réseaux.
|
||||
Gets network configuration.
|
||||
|
||||
Returns:
|
||||
dict: Configuration des réseaux
|
||||
dict: Network configuration
|
||||
"""
|
||||
return self._make_api_request('GET', self.API_NETWORK_ENDPOINT)
|
||||
|
||||
def get_devices(self):
|
||||
"""
|
||||
Récupère la liste des périphériques.
|
||||
Gets the list of devices.
|
||||
|
||||
Returns:
|
||||
dict: Liste des périphériques
|
||||
dict: List of devices
|
||||
"""
|
||||
return self._make_api_request('GET', self.API_DEVICES_ENDPOINT)
|
||||
|
||||
def get_users(self):
|
||||
"""
|
||||
Récupère la liste des utilisateurs.
|
||||
Gets the list of users.
|
||||
|
||||
Returns:
|
||||
dict: Liste des utilisateurs
|
||||
dict: List of users
|
||||
"""
|
||||
return self._make_api_request('GET', self.API_USERS_ENDPOINT)
|
||||
|
||||
def get_settings(self):
|
||||
"""
|
||||
Récupère les paramètres généraux.
|
||||
Gets general settings.
|
||||
|
||||
Returns:
|
||||
dict: Paramètres généraux
|
||||
dict: General settings
|
||||
"""
|
||||
return self._make_api_request('GET', self.API_SETTINGS_ENDPOINT)
|
||||
|
||||
def get_firewall_rules(self):
|
||||
"""
|
||||
Récupère les règles de pare-feu.
|
||||
Gets firewall rules.
|
||||
|
||||
Returns:
|
||||
dict: Règles de pare-feu
|
||||
dict: Firewall rules
|
||||
"""
|
||||
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.
|
||||
Gets the list of currently connected clients.
|
||||
|
||||
Returns:
|
||||
list: Liste des clients actifs
|
||||
list: List of active clients
|
||||
"""
|
||||
endpoint = self.API_ACTIVE_CLIENTS_ENDPOINT.format(site=self.site)
|
||||
response = self._make_api_request('GET', endpoint)
|
||||
|
|
@ -563,10 +585,10 @@ class UdmProClient:
|
|||
|
||||
def get_configured_clients(self):
|
||||
"""
|
||||
Récupère la liste des clients configurés statiquement.
|
||||
Gets the list of statically configured clients.
|
||||
|
||||
Returns:
|
||||
list: Liste des clients configurés
|
||||
list: List of configured clients
|
||||
"""
|
||||
endpoint = self.API_CONFIGURED_CLIENTS_ENDPOINT.format(site=self.site)
|
||||
response = self._make_api_request('GET', endpoint)
|
||||
|
|
@ -578,14 +600,14 @@ class UdmProClient:
|
|||
|
||||
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'.
|
||||
Restarts a device managed by UDM Pro (e.g. WiFi access point).
|
||||
Requires 'site admin' level permissions.
|
||||
|
||||
Args:
|
||||
mac_address (str): Adresse MAC de l'appareil à redémarrer
|
||||
mac_address (str): MAC address of device to restart
|
||||
|
||||
Returns:
|
||||
bool: True si l'opération a réussi, False sinon
|
||||
bool: True if operation succeeded, False otherwise
|
||||
"""
|
||||
endpoint = self.API_DEVICE_RESTART_ENDPOINT.format(site=self.site)
|
||||
payload = {
|
||||
|
|
@ -600,28 +622,28 @@ class UdmProClient:
|
|||
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))
|
||||
_logger.error("Error while restarting device %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).
|
||||
Gets all network clients (active and/or configured according to parameters).
|
||||
|
||||
Returns:
|
||||
list: Liste des clients réseau
|
||||
list: List of network clients
|
||||
"""
|
||||
clients = []
|
||||
|
||||
# Toujours inclure les clients configurés statiquement
|
||||
# Always include statically configured clients
|
||||
configured_clients = self.get_configured_clients()
|
||||
clients.extend(configured_clients)
|
||||
|
||||
# Inclure les clients actifs si fixed_only est False
|
||||
# Include active clients if fixed_only is 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
|
||||
# Process hostnames if needed
|
||||
if self.lowercase_hostnames:
|
||||
for client in clients:
|
||||
if client.get('hostname'):
|
||||
|
|
@ -633,10 +655,10 @@ class UdmProClient:
|
|||
|
||||
def generate_hosts_file(self):
|
||||
"""
|
||||
Génère un fichier hosts à partir des clients réseau.
|
||||
Generates a hosts file from network clients.
|
||||
|
||||
Returns:
|
||||
str: Contenu du fichier hosts
|
||||
str: Content of hosts file
|
||||
"""
|
||||
clients = self.get_network_clients()
|
||||
hosts_content = "# UDM Pro Generated Hosts File\n"
|
||||
|
|
@ -654,17 +676,17 @@ class UdmProClient:
|
|||
|
||||
def get_full_configuration(self):
|
||||
"""
|
||||
Récupère la configuration complète de l'UDM Pro.
|
||||
Gets complete UDM Pro configuration.
|
||||
|
||||
Returns:
|
||||
dict: Configuration complète de l'UDM Pro
|
||||
dict: Complete UDM Pro configuration
|
||||
"""
|
||||
# S'authentifier d'abord
|
||||
# Authenticate first
|
||||
if not self.token and not self.login():
|
||||
raise ConnectionError("Échec de l'authentification pour la récupération de la configuration complète")
|
||||
raise ConnectionError("Authentication failed while retrieving complete configuration")
|
||||
|
||||
try:
|
||||
# Récupérer chaque partie de la configuration
|
||||
# Get each part of the configuration
|
||||
system_info = self.get_system_info()
|
||||
networks = self.get_networks()
|
||||
devices = self.get_devices()
|
||||
|
|
@ -672,10 +694,10 @@ class UdmProClient:
|
|||
settings = self.get_settings()
|
||||
firewall = self.get_firewall_rules()
|
||||
|
||||
# Récupérer également les clients réseau (nouvelle fonctionnalité)
|
||||
# Also get network clients (new feature)
|
||||
network_clients = self.get_network_clients()
|
||||
|
||||
# Combiner toutes les parties dans un seul dictionnaire
|
||||
# Combine all parts into a single dictionary
|
||||
return {
|
||||
'system_info': system_info,
|
||||
'networks': networks,
|
||||
|
|
@ -687,5 +709,5 @@ class UdmProClient:
|
|||
}
|
||||
|
||||
except RequestException as e:
|
||||
_logger.error("Erreur lors de la récupération de la configuration complète: %s", str(e))
|
||||
_logger.error("Error while retrieving complete configuration: %s", str(e))
|
||||
raise
|
||||
|
|
|
|||
406
unifi_integration/examples/api.md
Normal file
406
unifi_integration/examples/api.md
Normal file
|
|
@ -0,0 +1,406 @@
|
|||
Documentation of API endpoints on the UniFi controller software. This is a reverse engineering project that is based on browser captures, jar dumps, and reviewing other software that has been written to work with the controller. It's received minimal testing.
|
||||
|
||||
There are two main types of calls. One would be the REST-like which provide get/post/put/delete where post is to the base and put/delete are often tied to the _id of the object that you are working with. The second major type of web interface provided is an agent/call based system where you pass a command to an agent to perform an action. Both use application/json formatting for all data transfer. When updating a specific object you must use PUT or else a new object will be created.
|
||||
|
||||
NOTE: All calls are relative to the base controller URL
|
||||
|
||||
Calls return both a web status as well as JSON formatted output. 200 codes indicate a successful call and other indicate errors. I am using the placeholder `{site}` for the site name which for many installations will be `default`.
|
||||
|
||||
# Login required
|
||||
{ "data" : [ ] , "meta" : { "msg" : "api.err.LoginRequired" , "rc" : "error"}}
|
||||
# Call was a success but returned no values
|
||||
{ "data" : [ ] , "meta" : { "rc" : "ok"}}
|
||||
# NOTE: If meta contains a count it's because the data values have been truncated
|
||||
'meta': {'count': 4818, 'rc': 'ok'} # from the api/s/{site}/stat/event endpoint
|
||||
UDM Pro and UCG Max API
|
||||
NOTE: There are two critical differences between Unifi controllers and the API of the UDM Pro and UCG Max:
|
||||
|
||||
The login endpoint is /api/auth/login
|
||||
All API endpoints need to be prefixed with /proxy/network (e.g. https://192.168.0.1/proxy/network/api/s/default/self)
|
||||
Examples
|
||||
curl
|
||||
# authenticate and save the cookie contents in local file cookie.txt with switch '-c'
|
||||
curl -k -X POST --data '{"username": "usr", "password": "$pw"}' --header 'Content-Type: application/json' -c cookie.txt https://udmp:443/api/auth/login
|
||||
# responds with json data
|
||||
|
||||
# pass the local file cookie.txt with switch '-b'
|
||||
curl -k -X GET -b cookie.txt https://udmp/proxy/network/api/s/default/self
|
||||
# responds with proper json
|
||||
python
|
||||
Controller Endpoints
|
||||
These are REST calls that can be made without a site context. I do not believe any updates ( PUT ) can be called on these endpoints.
|
||||
|
||||
Path Method Notes
|
||||
status GET Returns some very basic server information - This appears to be the only endpoint that can be reached without an authentication
|
||||
{ "data" : [ ] , "meta" : { "rc" : "ok" , "server_version" : "5.7.23" , "up" : true , "uuid" : "0e727580-ffff-ffff-ffff-403dcd5a7bd4"}}
|
||||
api/login POST requires dict of username, password, and optionally remember=true for long-running sessions. Returns 200 for success and a cookie that is your session. NOTE: On UDM Pros this is api/auth/login.
|
||||
api/logout POST destroys the sever side session id which will make future attempts with that cookie fail
|
||||
api/self GET Logged in user NOTE: On UDM Pros this is api/users/self.
|
||||
api/self/sites GET Get basic information for all sites on this controller
|
||||
api/stat/sites GET Same as above with an additional information on health and new alerts for each site
|
||||
api/stat/admin GET List administrators and permissions for all sites
|
||||
api/system/poweroff POST Turns off the UDM NOTE: X-CSRF-Token header required (from e.g. the login response) + Super Admin access rights
|
||||
api/system/reboot POST Reboot the UDM NOTE: X-CSRF-Token header required (from e.g. the login response) + Super Admin access rights
|
||||
Site Endpoints
|
||||
All commands are presumed to be prefixed with api/s/{site}
|
||||
|
||||
Path Method Notes
|
||||
stat/health GET Health status of the site
|
||||
self GET Logged in user
|
||||
stat/ccode GET List of country codes
|
||||
stat/current-channel GET List of all RF channels based on the site country code
|
||||
stat/sysinfo GET Some high-level information about the controller
|
||||
stat/event GET List site events by most recent first, 3000 result limit
|
||||
rest/event GET List site events by oldest, no limit? (api.err.NotFound per controller version 7.1.66)
|
||||
stat/alarm GET List alarms by most recent, 3000 result limit?
|
||||
rest/alarm GET List alarms by oldest, no limit? (api.err.NotFound per controller version 7.1.66)
|
||||
stat/sta GET List of all _active_ clients on the site
|
||||
rest/user GET/POST/PUT List of all configured/known clients on the site
|
||||
stat/device-basic GET List of site devices with only 'adopted', 'disabled', 'mac', 'state', 'type' keys, useful for filtering on type
|
||||
stat/device GET/POST Detailed list of all devices on site. (Controller only) Can be filtered by POSTing {"macs": ["mac1", ... ]}
|
||||
stat/device/{mac} GET (UDM only) Detailed list of device filtered by mac address
|
||||
rest/device/{_id} PUT Updates to devices get PUT here, why?
|
||||
rest/setting GET/PUT Detailed site settings, updating requires adding key and _id to path for PUT ../setting/{key}/{_id}
|
||||
stat/routing GET All active routes on the device
|
||||
rest/routing GET/PUT User defined routes (HTTP response 500 per controller version 7.1.66)
|
||||
rest/firewallrule GET/PUT User defined firewall rules. This does not show auto-generated rules
|
||||
rest/firewallgroup GET/PUT User defined firewall groups.
|
||||
rest/wlanconf GET/PUT List WLANs, edit current WLANs and create new WLANs
|
||||
rest/wlanconf/{_id} GET Get details of WLAN designated by '_id'
|
||||
rest/wlanconf/{_id} PUT Update configuration of current WLAN designated by '_id'
|
||||
rest/tag GET/PUT? Tagged macs (api.err.Invalid per controller version 7.1.66)
|
||||
stat/rogueap GET/POST Neighboring APs optional json post 'within' = seen in the last x hours
|
||||
stat/sitedpi GET/POST DPI stats requires type="by_app" or "by_cat"
|
||||
stat/stadpi GET/POST DPI stats requires type="by_app" or "by_cat" optionally filtered macs=[…, ]
|
||||
stat/dynamicdns GET DynamicDNS information and status like current ip, last changed, and status
|
||||
rest/dynamicdns GET/PUT DynamicDNS configuration
|
||||
rest/portconf GET Switch port profiles
|
||||
stat/spectrumscan GET Get RF scan results, can be for a specific mac by appending to endpoint
|
||||
rest/radiusprofile GET/POST/PUT Radius profiles
|
||||
rest/account GET/POST/PUT Radius accounts
|
||||
rest/portforward GET List all port forwards configured on the site
|
||||
stat/report/{interval}.{type} POST Intervals are '5minutes', 'hourly', and 'daily'. Report types are 'site', 'user', and 'ap'. Must specify attributes to be returned 'bytes', 'wan-tx_bytes', 'wan-rx_bytes', 'wlan_bytes', 'num_sta', 'lan-num_sta', 'wlan-num_sta', 'time', 'rx_bytes', 'tx_bytes'. Can be filtered with 'macs': […]
|
||||
stat/authorization POST JSON as "{"start": "START TIMESTAMP", "end": "END TIMESTAMP"}" and you will get the code that have been used between the Timestams NOTE: X-CSRF-Token header required (from e.g. the login response) for UDM
|
||||
stat/sdn GET Return values as if the site is connected with the Unifi Cloud or the SSO
|
||||
Callable commands
|
||||
Posting to the endpoint api/s/{site}/cmd/<manager> with the json {"cmd": "command"} you can invoke commands on the controller.
|
||||
|
||||
Manager Call Notes
|
||||
evtmgt archive-all-alarms
|
||||
sitemgr add-site desc = Descriptive name ( required ), name = shortname ( in the URL )
|
||||
sitemgr delete-site name = short name ( required )
|
||||
sitemgr update-site desc = Descriptive name ( required )
|
||||
sitemgr get-admins List all administrators and permission for this site
|
||||
sitemgr move-device mac = device mac ( required ), site_id = 24 digit id ( required )
|
||||
sitemgr delete-device mac = device mac ( required )
|
||||
stamgr block-sta mac = client mac ( required )
|
||||
stamgr unblock-sta mac = client mac ( required )
|
||||
stamgr kick-sta Disconnect: mac = client mac (required )
|
||||
stamgr forget-sta Forget a client ( controller 5.9.x only )
|
||||
stamgr unauthorize-guest Unauthorize a client device, mac = client mac (required)
|
||||
devmgr adopt mac = device mac ( required )
|
||||
devmgr restart mac = device mac ( required )
|
||||
devmgr force-provision mac = device mac ( required )
|
||||
devmgr power-cycle mac = switch mac ( required ), port_idx = PoE port to cycle ( required )
|
||||
devmgr speedtest Start a speed test
|
||||
devmgr speedtest-status get the current state of the speed test
|
||||
devmgr set-locate mac = device mac ( required ) blink unit to locate
|
||||
devmgr unset-locate mac = device mac ( required ) led to normal state
|
||||
devmgr upgrade mac = device mac ( required ) upgrade firmware
|
||||
devmgr upgrade-external mac = device mac ( required ), url = firmware URL ( required )
|
||||
devmgr migrate mac = device mac ( required ), inform_url = New Inform URL to push to device (required)
|
||||
devmgr cancel-migrate mac = device mac ( required )
|
||||
devmgr spectrum-scan mac = device mac ( ap only, required ) trigger RF scan
|
||||
backup list-backups list of autobackup files
|
||||
backup delete-backup filename ( required )
|
||||
system backup create a backup. This appears to backup to a fixed location in the filesystem
|
||||
stat clear-dpi resets the site wide DPI counters
|
||||
Data Tables
|
||||
This data was extracted from the javascript of the site.
|
||||
|
||||
Model Type SKU Name
|
||||
BZ2 uap UAP Access Point
|
||||
BZ2LR uap UAP-LR Access Point Long-Range
|
||||
S216150 usw US-16-150W Switch 16 PoE (150 W)
|
||||
S224250 usw US-24-250W Switch 24 PoE (250 W)
|
||||
S224500 usw US-24-500W Switch 24 PoE (500 W)
|
||||
S248500 usw US-48-500W Switch 48 PoE (500 W)
|
||||
S248750 usw US-48-750W Switch 48 PoE (750 W)
|
||||
S28150 usw US-8-150W Switch 8 PoE (150 W)
|
||||
U2HSR uap UAP-Outdoor+ Access Point Outdoor+
|
||||
U2IW uap UAP-IW Access Point In-Wall
|
||||
U2L48 uap UAP-LR Access Point Long-Range
|
||||
U2Lv2 uap UAP-LRv2 Access Point Long-Range
|
||||
U2M uap UAP-Mini Access Point Mini
|
||||
U2O uap UAP-Outdoor Access Point Outdoor
|
||||
U2S48 uap UAP Access Point
|
||||
U2Sv2 uap UAPv2 Access Point
|
||||
U5O uap UAP-Outdoor5 Access Point Outdoor 5
|
||||
U6ENT uap U6-Enterprise Access Point WiFi 6 Enterprise
|
||||
U6EXT uap U6-Extender Access Point WiFi 6 Extender
|
||||
U6IW uap U6-IW Access Point WiFi 6 In-Wall
|
||||
U6M uap U6-Mesh Access Point WiFi 6 Mesh
|
||||
U7E uap UAP-AC Access Point AC
|
||||
U7EDU uap UAP-AC-EDU Access Point AC EDU
|
||||
U7Ev2 uap UAP-AC Access Point AC
|
||||
U7HD uap UAP-AC-HD Access Point AC HD
|
||||
U7IW uap UAP-AC-IW Access Point AC In-Wall
|
||||
U7IWP uap UAP-AC-IW-Pro Access Point AC In-Wall Pro
|
||||
U7LR uap UAP-AC-LR Access Point AC Long-Range
|
||||
U7LT uap UAP-AC-Lite Access Point AC Lite
|
||||
U7MP uap UAP-AC-M-Pro Access Point AC Mesh Pro
|
||||
U7MSH uap UAP-AC-M Access Point AC Mesh
|
||||
U7NHD uap UAP-nanoHD Access Point nanoHD
|
||||
U7O uap UAP-AC-Outdoor Access Point AC Outdoor
|
||||
U7P uap UAP-AC-Pro Access Point AC Pro
|
||||
U7PG2 uap UAP-AC-Pro Access Point AC Pro
|
||||
U7SHD uap UAP-AC-SHD Access Point AC SHD
|
||||
UAE6 uap U6-Extender-EA Access Point WiFi 6 Extender
|
||||
UAIW6 uap U6-IW-EA Access Point WiFi 6 In-Wall
|
||||
UAL6 uap U6-Lite Access Point WiFi 6 Lite
|
||||
UALR6 uap U6-LR-EA Access Point WiFi 6 Long-Range
|
||||
UALR6v2 uap U6-LR Access Point WiFi 6 Long-Range
|
||||
UALR6v3 uap U6-LR Access Point WiFi 6 Long-Range
|
||||
UAM6 uap U6-Mesh-EA Access Point WiFi 6 Mesh
|
||||
UAP6 uap U6-LR Access Point WiFi 6 Long-Range
|
||||
UAP6MP uap U6-Pro Access Point WiFi 6 Pro
|
||||
UASXG uas UAS-XG Application Server XG
|
||||
UBB ubb UBB Building-to-Building Bridge
|
||||
UBBXG ubb UBB-XG Building-to-Building Bridge XG
|
||||
UCGMAX ucg UCG-MAX Cloud Gateway Max
|
||||
UCK uck UCK Cloud Key
|
||||
UCK-v2 uck UCK Cloud Key
|
||||
UCK-v3 uck UCK Cloud Key
|
||||
UCKG2 uck UCK-G2 Cloud Key Gen2
|
||||
UCKP uck UCK-G2-Plus Cloud Key Gen2 Plus
|
||||
UCMSH uap UAP-XG-Mesh Access Point Mesh XG
|
||||
UCXG uap UAP-XG Access Point XG
|
||||
UDC48X6 usw USW-Leaf Switch Leaf
|
||||
UDM udm UDM Dream Machine
|
||||
UDMB uap UAP-BeaconHD Access Point BeaconHD
|
||||
UDMPRO udm UDM-Pro Dream Machine Pro
|
||||
UDMPROSE udm UDM-SE Dream Machine Special Edition
|
||||
UDR udm UDR Dream Router
|
||||
UDW udm UDW Dream Wall
|
||||
UDWPRO udm UDWPRO Dream Wall Pro
|
||||
UFLHD uap UAP-FlexHD Access Point FlexHD
|
||||
UGW3 ugw USG-3P Security Gateway
|
||||
UGW4 ugw USG-Pro-4 Security Gateway Pro
|
||||
UGWHD4 ugw USG Security Gateway
|
||||
UGWXG ugw USG-XG-8 Security Gateway XG
|
||||
UHDIW uap UAP-IW-HD Access Point In-Wall HD
|
||||
ULTE uap U-LTE UniFi LTE
|
||||
ULTEPEU uap U-LTE-Pro UniFi LTE Pro
|
||||
ULTEPUS uap U-LTE-Pro UniFi LTE Pro
|
||||
UP1 uap USP-Plug SmartPower Plug
|
||||
UP4 uph UVP-X Phone
|
||||
UP5 uph UVP Phone
|
||||
UP5c uph UVP Phone
|
||||
UP5t uph UVP-Pro Phone Professional
|
||||
UP5tc uph UVP-Pro Phone Professional
|
||||
UP6 uap USP-Strip SmartPower Strip (6 ports)
|
||||
UP7 uph UVP-Executive Phone Executive
|
||||
UP7c uph UVP-Executive Phone Executive
|
||||
US16P150 usw US-16-150W Switch 16 PoE (150 W)
|
||||
US24 usw USW-24-G1 Switch 24
|
||||
US24P250 usw US-24-250W Switch 24 PoE (250 W)
|
||||
US24P500 usw US-24-500W Switch 24 PoE (500 W)
|
||||
US24PL2 usw US-L2-24-PoE Switch 24 PoE
|
||||
US24PRO usw USW-Pro-24-PoE Switch Pro 24 PoE
|
||||
US24PRO2 usw USW-Pro-24 Switch Pro 24
|
||||
US48 usw US-48-G1 Switch 48
|
||||
US48P500 usw US-48-500W Switch 48 PoE (500 W)
|
||||
US48P750 usw US-48-750W Switch 48 PoE (750 W)
|
||||
US48PL2 usw US-L2-48-PoE Switch 48 PoE
|
||||
US48PRO usw USW-Pro-48-PoE Switch Pro 48 PoE
|
||||
US48PRO2 usw USW-Pro-48 Switch Pro 48
|
||||
US624P usw USW-Enterprise-24-PoE Switch Enterprise 24 PoE
|
||||
US648P usw USW-Enterprise-48-PoE Switch Enterprise 48 PoE
|
||||
US68P usw USW-Enterprise-8-PoE Switch Enterprise 8 PoE
|
||||
US6XG150 usw US-XG-6PoE Switch 6 XG PoE
|
||||
US8 usw US-8 Switch 8
|
||||
US8P150 usw US-8-150W Switch 8 PoE (150 W)
|
||||
US8P60 usw US-8-60W Switch 8 (60 W)
|
||||
USAGGPRO usw USW-Pro-Aggregation Switch Aggregation Pro
|
||||
USC8 usw US-8 Switch 8
|
||||
USC8P150 usw US-8-150W Switch 8 PoE (150 W)
|
||||
USC8P450 usw USW-Industrial Switch Industrial
|
||||
USC8P60 usw US-8-60W Switch 8 (60 W)
|
||||
USF5P usw USW-Flex Switch Flex
|
||||
USFXG usw USW-Flex-XG Switch Flex XG
|
||||
USL16LP usw USW-Lite-16-PoE Switch Lite 16 PoE
|
||||
USL16P usw USW-16-PoE Switch 16 PoE
|
||||
USL24 usw USW-24-G2 Switch 24
|
||||
USL24P usw USW-24-PoE Switch 24 PoE
|
||||
USL48 usw USW-48-G2 Switch 48
|
||||
USL48P usw USW-48-PoE Switch 48 PoE
|
||||
USL8A usw USW-Aggregation Switch Aggregation
|
||||
USL8LP usw USW-Lite-8-PoE Switch Lite 8 PoE
|
||||
USL8MP usw USW-Mission-Critical Switch Mission Critical
|
||||
USMINI usw USW-Flex-Mini Switch Flex Mini
|
||||
USPPDUP usw USP-PDU-Pro SmartPower PDU Pro
|
||||
USPRPS usw USP-RPS SmartPower Redundant Power System
|
||||
USXG usw US-16-XG Switch XG 16
|
||||
USXG24 usw USW-EnterpriseXG-24 Switch Enterprise XG 24
|
||||
UXBSDM uap UWB-XG-BK WiFi BaseStation XG
|
||||
UXGPRO uxg UXG-Pro Next-Generation Gateway Pro
|
||||
UXSDM uap UWB-XG WiFi BaseStation XG
|
||||
p2N uap PICOM2HP PicoStation M2 HP
|
||||
DPI Category Code Name
|
||||
0 Instant messaging
|
||||
1 P2P
|
||||
3 File Transfer
|
||||
4 Streaming Media
|
||||
5 Mail and Collaboration
|
||||
6 Voice over IP
|
||||
7 Database
|
||||
8 Games
|
||||
9 Network Management
|
||||
10 Remote Access Terminals
|
||||
11 Bypass Proxies and Tunnels
|
||||
12 Stock Market
|
||||
13 Web
|
||||
14 Security Update
|
||||
15 Web IM
|
||||
17 Business
|
||||
18 Network Protocols
|
||||
19 Network Protocols
|
||||
20 Network Protocols
|
||||
23 Private Protocol
|
||||
24 Social Network
|
||||
255 Unknown
|
||||
There are 2,213 named applications in the javascript dynamic.dpi.js. See extracted cat_app.json to include for lookups and example usage.
|
||||
|
||||
The application id is a compound id using bitwise shift left on the category id + application id sent from the api using list_dpi_stats_filtered
|
||||
|
||||
function compoundId($cat, $app){
|
||||
return (intval($cat) << 16) + intval($app);
|
||||
}
|
||||
Uncategorized
|
||||
Dump of found endpoints waiting for documentation
|
||||
|
||||
# logged in user
|
||||
api/s/{site}/self
|
||||
|
||||
# Country codes
|
||||
api/s/{site}/stat/ccode
|
||||
# Availible WiFi channels
|
||||
api/s/{site}/stat/current-channel
|
||||
|
||||
# Dashboard health
|
||||
api/s/{site}/stat/health
|
||||
|
||||
# Active client devices
|
||||
api/s/{site}/stat/sta
|
||||
# Configured clients
|
||||
api/s/{site}/stat/user
|
||||
|
||||
# Devices
|
||||
api/s/{site}/stat/device-basic - mac, type
|
||||
api/s/{site}/stat/device - can be filtered with macs: [ ..., ... ]
|
||||
|
||||
# Detailed site settings
|
||||
api/s/{site}/stat/sysinfo
|
||||
|
||||
# /rest/ endpoints also have a /cnt/ which returns the count for the data portion
|
||||
# can be used for any but seems targeted towards alarms
|
||||
|
||||
# Site settings
|
||||
api/s/{site}/rest/setting - this is a big one with a weird mechanism for updating
|
||||
|
||||
# Firewall rules
|
||||
api/s/{site}/rest/firewallrule - only lists user-defined rules
|
||||
|
||||
# Firewall groups
|
||||
api/s/{site}/rest/firewallgroup
|
||||
|
||||
# routes
|
||||
api/s/{site}/rest/routing
|
||||
|
||||
# Alarms
|
||||
# List of alarms
|
||||
api/s/{site}/rest/alarm
|
||||
# list of unarchived alarms
|
||||
api/s/{site}/rest/alarm?archived=false
|
||||
|
||||
# User groups - bandwith settings
|
||||
api/s/{site}/rest/usergroup
|
||||
|
||||
# ?
|
||||
api/s/{site}/rest/wlangroup
|
||||
|
||||
# Wireless networks
|
||||
api/s/{site}/rest/wlanconf
|
||||
|
||||
# ?
|
||||
api/s/{site}/rest/tag
|
||||
|
||||
# Site networks
|
||||
api/s/{site}/rest/networkconf
|
||||
|
||||
# example backup path
|
||||
dl/autobackup/autobackup_5.7.23_20180513_0000_1526169600008.unf
|
||||
|
||||
# Insights - sessions
|
||||
api/s/{site}/stat/session?type=all&start=1526515200&end=1526688000
|
||||
|
||||
# Insights - EDU streams
|
||||
api/s/{site}/stat/stream
|
||||
|
||||
# Switch port conf?
|
||||
api/s/{site}/rest/portconf
|
||||
|
||||
# Configured port forwards and uPNP - transfer bytes is listed but doesn't appear populated
|
||||
api/s/{site}/stat/portforward
|
||||
|
||||
# Update User (User are the clients)
|
||||
api/s/{site}/upd/user/{UserId}
|
||||
you can get the users and the userid from "/api/s/{SiteId}/stat/alluser" (All Clients) or "/api/s/{SiteId}/stat/sta" (Active Clients) which contains the client id (_id).
|
||||
example: change name of user with clientid 5aca464bb79fc60200460394 to 'test-raw':
|
||||
${curl_cmd} --data "json={'name':'test-raw'}" $baseurl/api/s/$site/upd/user/5aca464bb79fc60200460394
|
||||
|
||||
# Get Hotspot Configuration
|
||||
guest/s/{site}/hotspotconfig
|
||||
You will get in "auth" the value "none" if it is not activated, if it is activated you will get for exemple "hotspot" and many other values on the design of the page.
|
||||
|
||||
# Get Hotspot Packages
|
||||
guest/s/{site}/hotspotpackages
|
||||
??
|
||||
|
||||
# Get trafficrules
|
||||
v2/api/site/{site}/trafficrules
|
||||
also possible to add new rule with a POST request.
|
||||
|
||||
# Edit trafficrules
|
||||
v2/api/site/{site}/trafficrules/{id}/
|
||||
PUT or DELETE request to update or delete traffic rule
|
||||
GET is not allowed on specific trafficrules.
|
||||
With PUT the result code is 201 and not 200 for successful change.
|
||||
|
||||
|
||||
# Possible list of all callable managers
|
||||
system
|
||||
devmgr
|
||||
stamgr
|
||||
evtmgr
|
||||
cfgmgr
|
||||
hotspot
|
||||
sitemgr
|
||||
streammgr
|
||||
backup
|
||||
throughput
|
||||
stat
|
||||
firmware
|
||||
firewall
|
||||
elite
|
||||
|
||||
|
||||
Update of Port Forward Rules
|
||||
This may apply to other configurations, but initial testing shows that port forward rules can be enabled/disabled using PUT against the endpoint /api/s/{site}/rest/portforward/{rule-id} with a body such as:
|
||||
|
||||
{
|
||||
"enabled": true
|
||||
}
|
||||
The rule ID can be retrieved using the above described port forwarding GET request and is found in the "_id" key.
|
||||
|
||||
New rules can be created using POST, but be aware that there seems to be very little validation (it's possible to create entries with no information other than the fact that they're enabled, for example).
|
||||
5
unifi_integration/examples/credential.txt
Normal file
5
unifi_integration/examples/credential.txt
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
UDMP_HOST=https://10.10.1.10
|
||||
UDMP_USERNAME=api
|
||||
UDMP_PASSWORD='o=7Jd&WDZ.T,u6t?'
|
||||
UDMP_PORT=443
|
||||
VERIFY_SSL=false
|
||||
164
unifi_integration/examples/models.md
Normal file
164
unifi_integration/examples/models.md
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
# Documentation des Modèles d'Intégration UniFi
|
||||
|
||||
Ce document décrit les modèles de données utilisés dans le module d'intégration UniFi pour Odoo 17.0.
|
||||
|
||||
## Modèles Principaux
|
||||
|
||||
### Site UniFi (`udm.site`)
|
||||
|
||||
Représente un site UniFi géré par un UDM/UDR ou un contrôleur logiciel.
|
||||
|
||||
**Champs:**
|
||||
- `name` (Char): Nom du site
|
||||
- `site_id` (Char): Identifiant du site dans UniFi (toujours 'default')
|
||||
- `description` (Text): Description du site
|
||||
- `address` (Text): Adresse physique
|
||||
- `active` (Boolean): Statut actif
|
||||
- `controller_type` (Selection): Type de contrôleur (UDM/UDR, Software)
|
||||
- `host` (Char): Adresse IP ou nom d'hôte du contrôleur
|
||||
- `port` (Integer): Numéro de port (par défaut: 443)
|
||||
- `username` (Char): Nom d'utilisateur pour l'authentification
|
||||
- `password` (Char): Mot de passe pour l'authentification
|
||||
- `mfa_token` (Char): Code d'authentification à deux facteurs
|
||||
- `timestamp` (Datetime): Horodatage de la dernière mise à jour
|
||||
- `raw_data` (Text): Données de configuration brutes au format JSON
|
||||
|
||||
**Relations:**
|
||||
- `system_info_id` (Many2one): Informations système
|
||||
- `network_ids` (One2many): Configurations réseau
|
||||
- `vlan_ids` (One2many): Configurations VLAN
|
||||
- `device_ids` (One2many): Appareils connectés
|
||||
- `user_ids` (One2many): Comptes utilisateur
|
||||
- `settings_id` (Many2one): Paramètres généraux
|
||||
- `firewall_rule_ids` (One2many): Règles de pare-feu
|
||||
- `port_forward_ids` (One2many): Règles de redirection de port
|
||||
- `dns_config_id` (Many2one): Configuration DNS
|
||||
- `routing_config_id` (Many2one): Configuration de routage
|
||||
- `dashboard_ids` (One2many): Métriques du tableau de bord
|
||||
- `statistic_ids` (One2many): Statistiques
|
||||
|
||||
## Modèles de Réseau
|
||||
|
||||
### Network (`udm.network`)
|
||||
|
||||
Représente une configuration réseau.
|
||||
|
||||
**Champs:**
|
||||
- `name` (Char): Nom du réseau
|
||||
- `purpose` (Selection): Objectif du réseau (entreprise, invité, etc.)
|
||||
- `subnet` (Char): Sous-réseau
|
||||
- `vlan_enabled` (Boolean): Statut VLAN
|
||||
- `vlan_id` (Integer): Identifiant VLAN
|
||||
- `dhcp_enabled` (Boolean): Statut DHCP
|
||||
- `dhcp_start` (Char): Adresse de début DHCP
|
||||
- `dhcp_stop` (Char): Adresse de fin DHCP
|
||||
- `domain_name` (Char): Nom de domaine du réseau
|
||||
- `raw_data` (Text): Données réseau brutes
|
||||
|
||||
### VLAN (`udm.vlan`)
|
||||
|
||||
Stocke la configuration VLAN.
|
||||
|
||||
**Champs:**
|
||||
- `vlan_id` (Integer): Identifiant VLAN
|
||||
- `name` (Char): Nom du VLAN
|
||||
- `raw_data` (Text): Données VLAN brutes
|
||||
|
||||
## Modèles de Sécurité
|
||||
|
||||
### Firewall Rule (`udm.firewall.rule`)
|
||||
|
||||
Définit les règles du pare-feu.
|
||||
|
||||
**Champs:**
|
||||
- `name` (Char): Nom de la règle
|
||||
- `description` (Text): Description de la règle
|
||||
- `action` (Selection): Action (accepter, rejeter, bloquer)
|
||||
- `protocol` (Char): Protocole réseau
|
||||
- `source` (Char): Adresse/réseau source
|
||||
- `destination` (Char): Adresse/réseau de destination
|
||||
- `enabled` (Boolean): Statut de la règle
|
||||
- `raw_data` (Text): Données de règle brutes
|
||||
|
||||
### Port Forward (`udm.port.forward`)
|
||||
|
||||
Définit les règles de redirection de port.
|
||||
|
||||
**Champs:**
|
||||
- `name` (Char): Nom de la règle
|
||||
- `enabled` (Boolean): Statut de la règle
|
||||
- `src_port` (Char): Port source
|
||||
- `dst_port` (Char): Port de destination
|
||||
- `protocol` (Selection): Protocole (TCP, UDP, les deux)
|
||||
- `dst_address` (Char): Adresse de destination
|
||||
- `raw_data` (Text): Données de règle brutes
|
||||
|
||||
## Modèles Système
|
||||
|
||||
### System Info (`udm.system.info`)
|
||||
|
||||
Stocke les informations système de l'UDM Pro.
|
||||
|
||||
**Champs:**
|
||||
- `hostname` (Char): Nom d'hôte du système
|
||||
- `version` (Char): Version du firmware
|
||||
- `model` (Char): Modèle de l'appareil
|
||||
- `uptime` (Integer): Temps de fonctionnement
|
||||
- `serial` (Char): Numéro de série
|
||||
- `mac_address` (Char): Adresse MAC
|
||||
- `raw_data` (Text): Données système brutes
|
||||
|
||||
### Settings (`udm.settings`)
|
||||
|
||||
Stocke les paramètres généraux de l'UDM Pro.
|
||||
|
||||
**Champs:**
|
||||
- `timezone` (Char): Fuseau horaire du système
|
||||
- `ntp_servers` (Char): Liste des serveurs NTP
|
||||
- `dns_servers` (Char): Liste des serveurs DNS
|
||||
- `raw_data` (Text): Données de paramètres brutes
|
||||
|
||||
### DNS Config (`udm.dns.config`)
|
||||
|
||||
Stocke la configuration DNS.
|
||||
|
||||
**Champs:**
|
||||
- `enabled` (Boolean): Statut du service DNS
|
||||
- `filters_enabled` (Boolean): Statut du filtrage de contenu
|
||||
- `custom_dns` (Char): Serveurs DNS personnalisés
|
||||
- `raw_data` (Text): Configuration DNS brute
|
||||
|
||||
### Routing Config (`udm.routing.config`)
|
||||
|
||||
Stocke la configuration de routage.
|
||||
|
||||
**Champs:**
|
||||
- `ospf_enabled` (Boolean): Statut OSPF
|
||||
- `static_routes` (Text): Liste des routes statiques
|
||||
- `raw_data` (Text): Données de routage brutes
|
||||
|
||||
## Modèles Utilisateur
|
||||
|
||||
### User (`udm.user`)
|
||||
|
||||
Représente un compte utilisateur UniFi.
|
||||
|
||||
**Champs:**
|
||||
- `name` (Char): Nom d'utilisateur
|
||||
- `email` (Char): Adresse email
|
||||
- `role` (Char): Rôle de l'utilisateur
|
||||
- `enabled` (Boolean): Statut du compte
|
||||
- `raw_data` (Text): Données utilisateur brutes
|
||||
|
||||
### Device (`udm.device`)
|
||||
|
||||
Représente un appareil réseau.
|
||||
|
||||
**Champs:**
|
||||
- `name` (Char): Nom de l'appareil
|
||||
- `mac` (Char): Adresse MAC
|
||||
- `ip` (Char): Adresse IP
|
||||
- `device_type` (Char): Type d'appareil
|
||||
- `model` (Char): Modèle de l'appareil
|
||||
- `last_seen` (Datetime): Dernier horodatage de connexion
|
||||
- `raw_data` (Text): Données de l'appareil brutes
|
||||
2
unifi_integration/examples/requirements.txt
Normal file
2
unifi_integration/examples/requirements.txt
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
requests>=2.31.0
|
||||
python-dotenv>=1.0.0
|
||||
98
unifi_integration/examples/show_clients.py
Normal file
98
unifi_integration/examples/show_clients.py
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
"""
|
||||
Script to display UniFi UDM Pro clients in a clear format
|
||||
"""
|
||||
from datetime import datetime
|
||||
from unifi_client import UnifiClient
|
||||
|
||||
def format_uptime(uptime_seconds):
|
||||
"""Format uptime in a readable format"""
|
||||
if not uptime_seconds:
|
||||
return "N/A"
|
||||
|
||||
days = uptime_seconds // (24 * 3600)
|
||||
hours = (uptime_seconds % (24 * 3600)) // 3600
|
||||
minutes = (uptime_seconds % 3600) // 60
|
||||
|
||||
if days > 0:
|
||||
return f"{days}j {hours}h {minutes}m"
|
||||
elif hours > 0:
|
||||
return f"{hours}h {minutes}m"
|
||||
else:
|
||||
return f"{minutes}m"
|
||||
|
||||
def format_bytes(bytes_value):
|
||||
"""Format bytes in human readable format"""
|
||||
if not bytes_value:
|
||||
return "0 B"
|
||||
|
||||
for unit in ['B', 'KB', 'MB', 'GB', 'TB']:
|
||||
if bytes_value < 1024:
|
||||
return f"{bytes_value:.1f} {unit}"
|
||||
bytes_value /= 1024
|
||||
return f"{bytes_value:.1f} PB"
|
||||
|
||||
def get_client_type(client):
|
||||
"""Determine client type based on various fields"""
|
||||
if client.get('is_wired', False):
|
||||
return 'Filaire'
|
||||
elif client.get('is_wireless', False):
|
||||
return 'WiFi'
|
||||
return 'Inconnu'
|
||||
|
||||
def main():
|
||||
"""Main function to display clients"""
|
||||
with UnifiClient() as client:
|
||||
print("Récupération des clients connectés...")
|
||||
|
||||
# Get clients
|
||||
clients = client.get_clients()
|
||||
if not clients or 'data' not in clients:
|
||||
print("Aucun client trouvé ou erreur de connexion")
|
||||
return
|
||||
|
||||
# Get networks for VLAN mapping
|
||||
networks = client.get_networks()
|
||||
vlan_names = {
|
||||
str(net.get('vlan', '')): net.get('name', 'N/A')
|
||||
for net in networks.get('data', [])
|
||||
if net.get('vlan') is not None
|
||||
}
|
||||
|
||||
# Group clients by connection type
|
||||
wired_clients = []
|
||||
wireless_clients = []
|
||||
|
||||
for c in clients['data']:
|
||||
if c.get('is_wired', False):
|
||||
wired_clients.append(c)
|
||||
elif c.get('is_wireless', False):
|
||||
wireless_clients.append(c)
|
||||
|
||||
# Display clients
|
||||
def print_client_list(client_list, title):
|
||||
if not client_list:
|
||||
return
|
||||
|
||||
print(f"\n=== Clients {title} ===")
|
||||
print(f"{'Nom':<25} {'IP':<15} {'MAC':<18} {'VLAN':<15} {'Uptime':<15} {'Trafic TX/RX'}")
|
||||
print("-" * 100)
|
||||
|
||||
for c in sorted(client_list, key=lambda x: x.get('name', '')):
|
||||
name = c.get('name', c.get('hostname', 'N/A'))
|
||||
ip = c.get('ip', 'N/A')
|
||||
mac = c.get('mac', 'N/A')
|
||||
vlan_id = str(c.get('vlan', ''))
|
||||
vlan = f"{vlan_id} ({vlan_names.get(vlan_id, 'N/A')})"
|
||||
uptime = format_uptime(c.get('uptime', 0))
|
||||
tx = format_bytes(c.get('tx_bytes', 0))
|
||||
rx = format_bytes(c.get('rx_bytes', 0))
|
||||
|
||||
print(f"{name[:25]:<25} {ip:<15} {mac:<18} {vlan[:15]:<15} {uptime:<15} {tx}/{rx}")
|
||||
|
||||
print_client_list(wired_clients, "Filaires")
|
||||
print_client_list(wireless_clients, "WiFi")
|
||||
|
||||
print(f"\nTotal: {len(wired_clients)} clients filaires, {len(wireless_clients)} clients WiFi")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
163
unifi_integration/examples/show_device_details.py
Normal file
163
unifi_integration/examples/show_device_details.py
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
"""
|
||||
Script to display detailed information about a UniFi device
|
||||
"""
|
||||
from datetime import datetime
|
||||
from unifi_client import UnifiClient
|
||||
|
||||
def format_bytes(bytes_value):
|
||||
"""Format bytes in human readable format"""
|
||||
if not bytes_value:
|
||||
return "0 B"
|
||||
|
||||
for unit in ['B', 'KB', 'MB', 'GB', 'TB']:
|
||||
if bytes_value < 1024:
|
||||
return f"{bytes_value:.1f} {unit}"
|
||||
bytes_value /= 1024
|
||||
return f"{bytes_value:.1f} PB"
|
||||
|
||||
def format_temperature(temp_celsius):
|
||||
"""Format temperature in Celsius"""
|
||||
if not temp_celsius:
|
||||
return "N/A"
|
||||
return f"{temp_celsius}°C"
|
||||
|
||||
def format_radio_stats(radio):
|
||||
"""Format radio statistics"""
|
||||
channel = radio.get('channel', 'N/A')
|
||||
width = radio.get('channel_width', 'N/A')
|
||||
tx_power = radio.get('tx_power', 'N/A')
|
||||
tx_power_str = f"{tx_power}dBm" if tx_power != 'N/A' else 'N/A'
|
||||
utilization = radio.get('utilization', 'N/A')
|
||||
util_str = f"{utilization}%" if utilization != 'N/A' else 'N/A'
|
||||
|
||||
return f"Canal {channel} ({width}MHz), Puissance: {tx_power_str}, Utilisation: {util_str}"
|
||||
|
||||
def format_client_info(client):
|
||||
"""Format client information"""
|
||||
name = client.get('name', client.get('hostname', 'Inconnu'))
|
||||
ip = client.get('ip', 'N/A')
|
||||
signal = client.get('signal', 0)
|
||||
uptime = client.get('uptime', 0)
|
||||
uptime_str = f"{uptime//3600}h {(uptime%3600)//60}m" if uptime else 'N/A'
|
||||
tx_rate = client.get('tx_rate', 0)
|
||||
rx_rate = client.get('rx_rate', 0)
|
||||
|
||||
return f"{name:<20} IP: {ip:<15} Signal: {signal}dBm, Up: {uptime_str}, Vitesse: ↓{rx_rate}Mbps ↑{tx_rate}Mbps"
|
||||
|
||||
def format_port_status(port):
|
||||
"""Format port status information"""
|
||||
if not port:
|
||||
return "N/A"
|
||||
|
||||
speed = port.get('speed', 0)
|
||||
if speed == 0:
|
||||
return "Déconnecté"
|
||||
return f"{speed}Mbps {'(PoE)' if port.get('poe_enable', False) else ''}"
|
||||
|
||||
def show_device_details(device):
|
||||
"""Display detailed information about a device"""
|
||||
print(f"\n=== Détails de l'appareil : {device.get('name', 'N/A')} ===")
|
||||
|
||||
# Informations de base
|
||||
print("\nInformations de base:")
|
||||
print(f" Modèle : {device.get('model', 'N/A')}")
|
||||
print(f" Version : {device.get('version', 'N/A')}")
|
||||
print(f" IP : {device.get('ip', 'N/A')}")
|
||||
print(f" MAC : {device.get('mac', 'N/A')}")
|
||||
print(f" État : {'En ligne' if device.get('state', 0) == 1 else 'Hors ligne'}")
|
||||
|
||||
# Statistiques système
|
||||
print("\nStatistiques système:")
|
||||
print(f" CPU : {device.get('system_stats', {}).get('cpu', 'N/A')}%")
|
||||
print(f" Mémoire : {device.get('system_stats', {}).get('mem', 'N/A')}%")
|
||||
print(f" Température : {format_temperature(device.get('general_temperature', 0))}")
|
||||
|
||||
# Statistiques réseau
|
||||
stats = device.get('stat', {})
|
||||
print("\nStatistiques réseau:")
|
||||
print(f" Bytes reçus : {format_bytes(stats.get('rx_bytes', 0))}")
|
||||
print(f" Bytes envoyés : {format_bytes(stats.get('tx_bytes', 0))}")
|
||||
print(f" Paquets reçus : {stats.get('rx_packets', 0)}")
|
||||
print(f" Paquets envoyés: {stats.get('tx_packets', 0)}")
|
||||
|
||||
# Information sur les ports (pour les switches)
|
||||
if 'port_table' in device:
|
||||
print("\nPorts:")
|
||||
for port in device['port_table']:
|
||||
port_name = port.get('name', f"Port {port.get('port_idx', 'N/A')}")
|
||||
status = format_port_status(port)
|
||||
print(f" {port_name:<12} : {status}")
|
||||
|
||||
# Information WiFi (pour les points d'accès)
|
||||
if device.get('radio_table'):
|
||||
print("\nRadios WiFi:")
|
||||
for radio in device['radio_table']:
|
||||
band = "5 GHz" if radio.get('radio', '') == 'na' else "2.4 GHz"
|
||||
channel = radio.get('channel', 'N/A')
|
||||
tx_power = radio.get('tx_power', 'N/A')
|
||||
print(f" {band:<12} : Canal {channel}, Puissance {tx_power}dBm")
|
||||
|
||||
def format_poe_status(port):
|
||||
"""Format PoE status information"""
|
||||
if not port or not port.get('poe_enable'):
|
||||
return ''
|
||||
|
||||
try:
|
||||
power = float(port.get('poe_power', 0))
|
||||
voltage = float(port.get('poe_voltage', 0))
|
||||
current = float(port.get('poe_current', 0))
|
||||
|
||||
if power > 0:
|
||||
return f"PoE: {power:.1f}W ({voltage:.1f}V, {current:.0f}mA)"
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
|
||||
return 'PoE activé'
|
||||
|
||||
def main():
|
||||
"""Main function to display device details"""
|
||||
with UnifiClient() as client:
|
||||
print("Récupération des appareils UniFi...")
|
||||
|
||||
# Get devices
|
||||
devices = client.get_devices()
|
||||
if not devices or 'data' not in devices:
|
||||
print("Aucun appareil trouvé ou erreur de connexion")
|
||||
return
|
||||
|
||||
# Find all APs
|
||||
aps = [device for device in devices['data'] if device.get('model') in ['U7PG2', 'U7LR', 'U7IW']]
|
||||
|
||||
if aps:
|
||||
for i, ap in enumerate(aps, 1):
|
||||
if i > 1:
|
||||
print("\n" + "=" * 80 + "\n")
|
||||
|
||||
# Show basic details
|
||||
show_device_details(ap)
|
||||
|
||||
# Show WiFi details
|
||||
if 'radio_table' in ap:
|
||||
print("\nRadios WiFi:")
|
||||
for radio in ap['radio_table']:
|
||||
band = "5 GHz" if radio.get('radio') == 'na' else "2.4 GHz"
|
||||
print(f" {band}: {format_radio_stats(radio)}")
|
||||
|
||||
# Show connected clients
|
||||
if 'vap_table' in ap:
|
||||
print("\nClients connectés:")
|
||||
for vap in ap['vap_table']:
|
||||
if vap.get('sta_count', 0) > 0:
|
||||
ssid = vap.get('essid', 'N/A')
|
||||
print(f"\nSSID: {ssid}")
|
||||
print(" Nom IP Signal Temps Vitesse")
|
||||
print(" " + "-" * 75)
|
||||
|
||||
for client in vap.get('sta_table', []):
|
||||
print(f" {format_client_info(client)}")
|
||||
|
||||
else:
|
||||
print("Aucun point d'accès trouvé")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
86
unifi_integration/examples/show_devices.py
Normal file
86
unifi_integration/examples/show_devices.py
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
"""
|
||||
Script to display UniFi UDM Pro devices in a clear format
|
||||
"""
|
||||
from datetime import datetime
|
||||
from unifi_client import UnifiClient
|
||||
|
||||
def format_uptime(uptime_seconds):
|
||||
"""Format uptime in a readable format"""
|
||||
if not uptime_seconds:
|
||||
return "N/A"
|
||||
|
||||
days = uptime_seconds // (24 * 3600)
|
||||
hours = (uptime_seconds % (24 * 3600)) // 3600
|
||||
minutes = (uptime_seconds % 3600) // 60
|
||||
|
||||
if days > 0:
|
||||
return f"{days}j {hours}h {minutes}m"
|
||||
elif hours > 0:
|
||||
return f"{hours}h {minutes}m"
|
||||
else:
|
||||
return f"{minutes}m"
|
||||
|
||||
def get_device_type(device):
|
||||
"""Get device type in French"""
|
||||
model = device.get('model', '').lower()
|
||||
if 'udm' in model:
|
||||
return 'Routeur'
|
||||
elif 'usw' in model:
|
||||
return 'Switch'
|
||||
elif 'uap' in model:
|
||||
return 'Point d\'accès'
|
||||
return 'Autre'
|
||||
|
||||
def get_device_status(device):
|
||||
"""Get device status in French"""
|
||||
if device.get('state', 0) == 1:
|
||||
return '✓ En ligne'
|
||||
return '✗ Hors ligne'
|
||||
|
||||
def format_device(device):
|
||||
"""Format device information"""
|
||||
return {
|
||||
'name': device.get('name', 'N/A'),
|
||||
'model': device.get('model', 'N/A'),
|
||||
'type': get_device_type(device),
|
||||
'ip': device.get('ip', 'N/A'),
|
||||
'mac': device.get('mac', 'N/A'),
|
||||
'version': device.get('version', 'N/A'),
|
||||
'status': get_device_status(device),
|
||||
'uptime': format_uptime(device.get('uptime', 0))
|
||||
}
|
||||
|
||||
def main():
|
||||
"""Main function to display devices"""
|
||||
with UnifiClient() as client:
|
||||
print("Récupération des appareils UniFi...")
|
||||
|
||||
# Get devices
|
||||
devices = client.get_devices()
|
||||
if not devices or 'data' not in devices:
|
||||
print("Aucun appareil trouvé ou erreur de connexion")
|
||||
return
|
||||
|
||||
# Group devices by type
|
||||
devices_by_type = {}
|
||||
for device in devices['data']:
|
||||
device_type = get_device_type(device)
|
||||
if device_type not in devices_by_type:
|
||||
devices_by_type[device_type] = []
|
||||
devices_by_type[device_type].append(device)
|
||||
|
||||
# Display devices by type
|
||||
for device_type, device_list in devices_by_type.items():
|
||||
print(f"\n=== {device_type}s ===")
|
||||
print(f"{'Nom':<20} {'Modèle':<15} {'IP':<15} {'Version':<10} {'État':<15} {'Uptime'}")
|
||||
print("-" * 90)
|
||||
|
||||
for device in sorted(device_list, key=lambda x: x.get('name', '')):
|
||||
d = format_device(device)
|
||||
print(f"{d['name'][:20]:<20} {d['model'][:15]:<15} {d['ip']:<15} "
|
||||
f"{d['version'][:10]:<10} {d['status']:<15} {d['uptime']}")
|
||||
|
||||
print(f"\nTotal: {len(devices['data'])} appareils UniFi")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
68
unifi_integration/examples/show_firewall.py
Normal file
68
unifi_integration/examples/show_firewall.py
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
"""
|
||||
Script to display UniFi UDM Pro firewall rules in a clear format
|
||||
"""
|
||||
import json
|
||||
from unifi_client import UnifiClient
|
||||
|
||||
def format_rule(rule):
|
||||
"""Format a firewall rule for better readability"""
|
||||
direction = "IN" if rule.get('ruleset', '').endswith('_IN') else "OUT"
|
||||
src = rule.get('src_address', 'any')
|
||||
dst = rule.get('dst_address', 'any')
|
||||
|
||||
if direction == "IN":
|
||||
from_to = f"FROM {src} TO {dst if dst != '' else 'any'}"
|
||||
else:
|
||||
from_to = f"FROM {src if src != '' else 'any'} TO {dst}"
|
||||
|
||||
return {
|
||||
'name': rule.get('name', 'N/A'),
|
||||
'action': rule.get('action', 'N/A').upper(),
|
||||
'direction': direction,
|
||||
'protocol': rule.get('protocol', 'all').upper(),
|
||||
'ports': f"{rule.get('src_port', 'any')} -> {rule.get('dst_port', 'any')}",
|
||||
'addresses': from_to,
|
||||
'enabled': '✓' if rule.get('enabled', True) else '✗'
|
||||
}
|
||||
|
||||
def print_rules_by_type(rules, ruleset_type):
|
||||
"""Print rules filtered by ruleset type"""
|
||||
filtered_rules = [r for r in rules if ruleset_type in r.get('ruleset', '')]
|
||||
if filtered_rules:
|
||||
print(f"\n=== Règles {ruleset_type} ===")
|
||||
for rule in filtered_rules:
|
||||
r = format_rule(rule)
|
||||
print(f"\n{r['name']}")
|
||||
print(f" État : {r['enabled']}")
|
||||
print(f" Action : {r['action']}")
|
||||
print(f" Protocol : {r['protocol']}")
|
||||
print(f" Ports : {r['ports']}")
|
||||
print(f" Adresses : {r['addresses']}")
|
||||
|
||||
def main():
|
||||
"""Main function to display firewall rules"""
|
||||
with UnifiClient() as client:
|
||||
print("Récupération des règles de pare-feu...")
|
||||
|
||||
# Get firewall rules
|
||||
rules = client.get_firewall_rules()
|
||||
if not rules or 'data' not in rules:
|
||||
print("Aucune règle trouvée ou erreur de connexion")
|
||||
return
|
||||
|
||||
# Display rules by type
|
||||
print_rules_by_type(rules['data'], 'WAN_IN')
|
||||
print_rules_by_type(rules['data'], 'WAN_OUT')
|
||||
print_rules_by_type(rules['data'], 'LAN_IN')
|
||||
print_rules_by_type(rules['data'], 'LAN_OUT')
|
||||
|
||||
# Get and display firewall groups
|
||||
groups = client.get_firewall_groups()
|
||||
if groups and 'data' in groups:
|
||||
print("\n=== Groupes de Pare-feu ===")
|
||||
for group in groups['data']:
|
||||
print(f"\n{group.get('name', 'N/A')} ({group.get('group_type', 'N/A')})")
|
||||
print(f" Membres : {', '.join(group.get('group_members', []))}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
58
unifi_integration/examples/show_networks.py
Normal file
58
unifi_integration/examples/show_networks.py
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
"""
|
||||
Script to display UniFi UDM Pro networks in a clear format
|
||||
"""
|
||||
import json
|
||||
from unifi_client import UnifiClient
|
||||
|
||||
def format_network(network):
|
||||
"""Format network information for better readability"""
|
||||
purpose = network.get('purpose', 'corporate')
|
||||
if purpose == 'corporate':
|
||||
purpose = 'Entreprise'
|
||||
elif purpose == 'guest':
|
||||
purpose = 'Invité'
|
||||
elif purpose == 'vlan-only':
|
||||
purpose = 'VLAN uniquement'
|
||||
|
||||
dhcp_enabled = network.get('dhcpd_enabled', False)
|
||||
dhcp_info = ""
|
||||
if dhcp_enabled:
|
||||
start = network.get('dhcpd_start', 'N/A')
|
||||
stop = network.get('dhcpd_stop', 'N/A')
|
||||
dhcp_info = f"({start} - {stop})"
|
||||
|
||||
return {
|
||||
'nom': network.get('name', 'N/A'),
|
||||
'vlan': network.get('vlan', 'Non'),
|
||||
'purpose': purpose,
|
||||
'subnet': network.get('subnet', 'N/A'),
|
||||
'dhcp': 'Activé ' + dhcp_info if dhcp_enabled else 'Désactivé',
|
||||
'dns': network.get('dns_nameservers', ['N/A']),
|
||||
'enabled': network.get('enabled', True)
|
||||
}
|
||||
|
||||
def main():
|
||||
"""Main function to display networks"""
|
||||
with UnifiClient() as client:
|
||||
print("Récupération des réseaux configurés...")
|
||||
|
||||
# Get networks
|
||||
networks = client.get_networks()
|
||||
if not networks or 'data' not in networks:
|
||||
print("Aucun réseau trouvé ou erreur de connexion")
|
||||
return
|
||||
|
||||
# Display networks
|
||||
print("\n=== Réseaux Configurés ===")
|
||||
for network in networks['data']:
|
||||
net = format_network(network)
|
||||
print(f"\n{net['nom']}")
|
||||
print(f" État : {'Actif' if net['enabled'] else 'Inactif'}")
|
||||
print(f" Type : {net['purpose']}")
|
||||
print(f" VLAN : {net['vlan']}")
|
||||
print(f" Subnet : {net['subnet']}")
|
||||
print(f" DHCP : {net['dhcp']}")
|
||||
print(f" DNS : {', '.join(net['dns'])}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
54
unifi_integration/examples/show_vlans.py
Normal file
54
unifi_integration/examples/show_vlans.py
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
"""
|
||||
Script to display UniFi UDM Pro VLANs in a clear format
|
||||
"""
|
||||
from unifi_client import UnifiClient
|
||||
|
||||
def get_vlan_purpose(network):
|
||||
"""Get the purpose/usage of a VLAN"""
|
||||
purpose = network.get('purpose', 'corporate')
|
||||
if purpose == 'corporate':
|
||||
return 'Entreprise'
|
||||
elif purpose == 'guest':
|
||||
return 'Invité'
|
||||
elif purpose == 'vlan-only':
|
||||
return 'VLAN uniquement'
|
||||
return purpose
|
||||
|
||||
def main():
|
||||
"""Main function to display VLANs"""
|
||||
with UnifiClient() as client:
|
||||
print("Récupération des VLANs configurés...")
|
||||
|
||||
# Get networks
|
||||
networks = client.get_networks()
|
||||
if not networks or 'data' not in networks:
|
||||
print("Aucun VLAN trouvé ou erreur de connexion")
|
||||
return
|
||||
|
||||
# Filter and sort networks with VLANs
|
||||
vlan_networks = [n for n in networks['data'] if n.get('vlan', None) is not None]
|
||||
vlan_networks.sort(key=lambda x: x.get('vlan', 0))
|
||||
|
||||
# Display VLANs
|
||||
print("\n=== VLANs Configurés ===")
|
||||
print(f"{'VLAN ID':<8} {'Nom':<20} {'Type':<15} {'Plage DHCP':<35} {'État'}")
|
||||
print("-" * 85)
|
||||
|
||||
for network in vlan_networks:
|
||||
vlan_id = network.get('vlan', 'N/A')
|
||||
name = network.get('name', 'N/A')
|
||||
purpose = get_vlan_purpose(network)
|
||||
|
||||
# Format DHCP range
|
||||
dhcp_range = 'DHCP désactivé'
|
||||
if network.get('dhcpd_enabled', False):
|
||||
start = network.get('dhcpd_start', '')
|
||||
stop = network.get('dhcpd_stop', '')
|
||||
dhcp_range = f"{start} - {stop}"
|
||||
|
||||
state = 'Actif' if network.get('enabled', True) else 'Inactif'
|
||||
|
||||
print(f"{vlan_id:<8} {name[:20]:<20} {purpose[:15]:<15} {dhcp_range[:35]:<35} {state}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
74
unifi_integration/examples/test_firewall.py
Normal file
74
unifi_integration/examples/test_firewall.py
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
"""
|
||||
Test script for UniFi UDM Pro Firewall Management
|
||||
This script demonstrates the firewall management capabilities of the UnifiClient class.
|
||||
"""
|
||||
import json
|
||||
from unifi_client import UnifiClient
|
||||
|
||||
def format_firewall_rule(rule):
|
||||
"""Format a firewall rule for display
|
||||
|
||||
Args:
|
||||
rule (dict): Firewall rule data
|
||||
|
||||
Returns:
|
||||
dict: Formatted rule data
|
||||
"""
|
||||
return {
|
||||
'name': rule.get('name', 'N/A'),
|
||||
'ruleset': rule.get('ruleset', 'N/A'),
|
||||
'action': rule.get('action', 'N/A'),
|
||||
'protocol': rule.get('protocol', 'all'),
|
||||
'src_address': rule.get('src_address', 'any'),
|
||||
'dst_address': rule.get('dst_address', 'any'),
|
||||
'enabled': rule.get('enabled', True)
|
||||
}
|
||||
|
||||
def main():
|
||||
"""
|
||||
Main test function to demonstrate firewall management
|
||||
"""
|
||||
with UnifiClient() as client:
|
||||
print("Connexion à l'UDM Pro...")
|
||||
|
||||
# Get existing firewall rules
|
||||
print("\nRègles de pare-feu existantes:")
|
||||
rules = client.get_firewall_rules()
|
||||
if rules and rules.get('data'):
|
||||
for rule in rules['data']:
|
||||
formatted_rule = format_firewall_rule(rule)
|
||||
print(json.dumps(formatted_rule, indent=2))
|
||||
|
||||
# Get firewall groups
|
||||
print("\nGroupes de pare-feu:")
|
||||
groups = client.get_firewall_groups()
|
||||
if groups and groups.get('data'):
|
||||
for group in groups['data']:
|
||||
print(json.dumps({
|
||||
'name': group.get('name', 'N/A'),
|
||||
'type': group.get('group_type', 'N/A'),
|
||||
'members': group.get('group_members', [])
|
||||
}, indent=2))
|
||||
|
||||
# Example: Create a new firewall rule
|
||||
# Note: Commented out to prevent accidental rule creation
|
||||
"""
|
||||
new_rule = {
|
||||
'name': 'Test SSH Access',
|
||||
'enabled': True,
|
||||
'action': 'accept',
|
||||
'ruleset': 'WAN_IN',
|
||||
'protocol': 'tcp',
|
||||
'dst_port': '22',
|
||||
'description': 'Allow SSH access from WAN'
|
||||
}
|
||||
|
||||
print("\nCréation d'une nouvelle règle:")
|
||||
result = client.create_firewall_rule(new_rule)
|
||||
if result:
|
||||
print("Règle créée avec succès:")
|
||||
print(json.dumps(format_firewall_rule(result['data'][0]), indent=2))
|
||||
"""
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
91
unifi_integration/examples/test_unifi.py
Normal file
91
unifi_integration/examples/test_unifi.py
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
"""
|
||||
Test script for UniFi UDM Pro API Client
|
||||
This script demonstrates the basic usage of the UnifiClient class.
|
||||
"""
|
||||
import json
|
||||
from unifi_client import UnifiClient
|
||||
|
||||
def format_response(data, max_items=5):
|
||||
"""Format API response data to show only essential information
|
||||
|
||||
Args:
|
||||
data (dict): API response data
|
||||
max_items (int): Maximum number of items to show
|
||||
|
||||
Returns:
|
||||
dict: Formatted data
|
||||
"""
|
||||
if not data or 'data' not in data:
|
||||
return data
|
||||
|
||||
result = {'meta': data.get('meta', {}), 'data': []}
|
||||
|
||||
# Take only the first max_items
|
||||
items = data['data'][:max_items]
|
||||
|
||||
for item in items:
|
||||
# For devices, show only essential info
|
||||
if 'model' in item:
|
||||
result['data'].append({
|
||||
'name': item.get('name', 'N/A'),
|
||||
'model': item.get('model', 'N/A'),
|
||||
'ip': item.get('ip', 'N/A'),
|
||||
'status': item.get('state', 'N/A')
|
||||
})
|
||||
# For clients, show only essential info
|
||||
elif 'hostname' in item:
|
||||
result['data'].append({
|
||||
'name': item.get('hostname', 'N/A'),
|
||||
'ip': item.get('ip', 'N/A'),
|
||||
'mac': item.get('mac', 'N/A'),
|
||||
'network': item.get('network', 'N/A')
|
||||
})
|
||||
# For other types, keep all fields
|
||||
else:
|
||||
result['data'].append(item)
|
||||
|
||||
if len(data['data']) > max_items:
|
||||
print(f"\nNote: Showing {max_items} of {len(data['data'])} items")
|
||||
|
||||
return result
|
||||
|
||||
def main():
|
||||
"""
|
||||
Main test function to demonstrate UnifiClient usage
|
||||
"""
|
||||
# Using context manager for automatic login/logout
|
||||
with UnifiClient() as client:
|
||||
# Get system information
|
||||
system_info = client.get_system_info()
|
||||
|
||||
if system_info:
|
||||
print("Successfully connected to UDM Pro!")
|
||||
print("\nSystem Information:")
|
||||
system_info = format_response(system_info)
|
||||
print(json.dumps(system_info, indent=2))
|
||||
|
||||
# Get network health status
|
||||
health = client.get_health()
|
||||
if health:
|
||||
print("\nNetwork Health:")
|
||||
health = format_response(health)
|
||||
print(json.dumps(health, indent=2))
|
||||
|
||||
# Get connected devices
|
||||
devices = client.get_devices()
|
||||
if devices:
|
||||
print("\nConnected Devices:")
|
||||
devices = format_response(devices)
|
||||
print(json.dumps(devices, indent=2))
|
||||
|
||||
# Get active clients
|
||||
clients = client.get_clients()
|
||||
if clients:
|
||||
print("\nActive Clients:")
|
||||
clients = format_response(clients)
|
||||
print(json.dumps(clients, indent=2))
|
||||
else:
|
||||
print("Failed to retrieve system information")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
306
unifi_integration/examples/unifi_client.py
Normal file
306
unifi_integration/examples/unifi_client.py
Normal file
|
|
@ -0,0 +1,306 @@
|
|||
"""
|
||||
UniFi UDM Pro API Client
|
||||
This module provides a client to interact with the UniFi Dream Machine Pro API.
|
||||
"""
|
||||
import os
|
||||
import json
|
||||
import requests
|
||||
from urllib3.exceptions import InsecureRequestWarning
|
||||
import dotenv
|
||||
|
||||
# Suppress only the single warning from urllib3 needed.
|
||||
requests.packages.urllib3.disable_warnings(category=InsecureRequestWarning)
|
||||
|
||||
class UnifiClient:
|
||||
"""
|
||||
A client for interacting with the UniFi Dream Machine Pro API.
|
||||
Handles authentication and basic API operations.
|
||||
"""
|
||||
def __init__(self, config_file=None):
|
||||
"""
|
||||
Initialize the UniFi client with configuration from environment file.
|
||||
|
||||
Args:
|
||||
config_file (str): Path to the configuration file (default: Doc/credential.txt)
|
||||
"""
|
||||
if config_file is None:
|
||||
config_file = os.path.join(os.path.dirname(__file__), 'Doc/credential.txt')
|
||||
|
||||
# Load configuration from file
|
||||
dotenv.load_dotenv(config_file)
|
||||
|
||||
self.host = os.getenv('UDMP_HOST').rstrip('/')
|
||||
self.username = os.getenv('UDMP_USERNAME')
|
||||
self.password = os.getenv('UDMP_PASSWORD')
|
||||
self.verify_ssl = os.getenv('VERIFY_SSL', 'false').lower() == 'true'
|
||||
|
||||
self.session = requests.Session()
|
||||
self.session.verify = self.verify_ssl
|
||||
self.csrf_token = None
|
||||
|
||||
def login(self):
|
||||
"""
|
||||
Authenticate with the UDM Pro.
|
||||
|
||||
Returns:
|
||||
bool: True if login successful, False otherwise
|
||||
"""
|
||||
login_url = f"{self.host}/api/auth/login"
|
||||
headers = {'Content-Type': 'application/json'}
|
||||
data = {
|
||||
'username': self.username,
|
||||
'password': self.password
|
||||
}
|
||||
|
||||
try:
|
||||
response = self.session.post(
|
||||
login_url,
|
||||
headers=headers,
|
||||
json=data,
|
||||
timeout=10 # 10 seconds timeout
|
||||
)
|
||||
response.raise_for_status()
|
||||
|
||||
# Store CSRF token if present
|
||||
if 'X-CSRF-Token' in response.headers:
|
||||
self.csrf_token = response.headers['X-CSRF-Token']
|
||||
|
||||
return True
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f"Login failed: {str(e)}")
|
||||
return False
|
||||
|
||||
def get_system_info(self):
|
||||
"""
|
||||
Get basic system information.
|
||||
|
||||
Returns:
|
||||
dict: System information or None if request fails
|
||||
"""
|
||||
url = f"{self.host}/proxy/network/api/s/default/self"
|
||||
|
||||
try:
|
||||
response = self.session.get(url, timeout=10)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f"Failed to get system info: {str(e)}")
|
||||
return None
|
||||
|
||||
def logout(self):
|
||||
"""
|
||||
Logout from the UDM Pro.
|
||||
|
||||
Returns:
|
||||
bool: True if logout successful, False otherwise
|
||||
"""
|
||||
logout_url = f"{self.host}/api/auth/logout"
|
||||
|
||||
# Add required headers for logout
|
||||
headers = {
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
if self.csrf_token:
|
||||
headers['X-CSRF-Token'] = self.csrf_token
|
||||
|
||||
try:
|
||||
# Send empty JSON body as required by the API
|
||||
response = self.session.post(logout_url, headers=headers, json={}, timeout=10)
|
||||
|
||||
# Note: UDM Pro returns 200 even if session is already expired
|
||||
if response.status_code == 200:
|
||||
return True
|
||||
|
||||
response.raise_for_status()
|
||||
return True
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
# If we get a 403, it might mean we're already logged out
|
||||
if isinstance(e, requests.exceptions.HTTPError) and e.response.status_code == 403:
|
||||
return True
|
||||
|
||||
print(f"Logout failed: {str(e)}")
|
||||
return False
|
||||
|
||||
def __enter__(self):
|
||||
"""Context manager entry point"""
|
||||
self.login()
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
"""Context manager exit point"""
|
||||
self.logout()
|
||||
|
||||
def get_devices(self):
|
||||
"""Get all devices connected to the UDM Pro.
|
||||
|
||||
Returns:
|
||||
dict: List of devices or None if request fails
|
||||
"""
|
||||
url = f"{self.host}/proxy/network/api/s/default/stat/device"
|
||||
|
||||
try:
|
||||
response = self.session.get(url, timeout=10)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f"Failed to get devices: {str(e)}")
|
||||
return None
|
||||
|
||||
def get_clients(self):
|
||||
"""Get all clients (users) connected to the network.
|
||||
|
||||
Returns:
|
||||
dict: List of clients or None if request fails
|
||||
"""
|
||||
url = f"{self.host}/proxy/network/api/s/default/stat/sta"
|
||||
|
||||
try:
|
||||
response = self.session.get(url, timeout=10)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f"Failed to get clients: {str(e)}")
|
||||
return None
|
||||
|
||||
def get_health(self):
|
||||
"""Get the health status of the UDM Pro and network.
|
||||
|
||||
Returns:
|
||||
dict: Health information or None if request fails
|
||||
"""
|
||||
url = f"{self.host}/proxy/network/api/s/default/stat/health"
|
||||
|
||||
try:
|
||||
response = self.session.get(url, timeout=10)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f"Failed to get health status: {str(e)}")
|
||||
return None
|
||||
|
||||
def get_firewall_rules(self):
|
||||
"""Get all firewall rules.
|
||||
|
||||
Returns:
|
||||
dict: List of firewall rules or None if request fails
|
||||
"""
|
||||
url = f"{self.host}/proxy/network/api/s/default/rest/firewallrule"
|
||||
|
||||
try:
|
||||
response = self.session.get(url, timeout=10)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f"Failed to get firewall rules: {str(e)}")
|
||||
return None
|
||||
|
||||
def get_firewall_groups(self):
|
||||
"""Get all firewall groups (address groups, port groups).
|
||||
|
||||
Returns:
|
||||
dict: List of firewall groups or None if request fails
|
||||
"""
|
||||
url = f"{self.host}/proxy/network/api/s/default/rest/firewallgroup"
|
||||
|
||||
try:
|
||||
response = self.session.get(url, timeout=10)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f"Failed to get firewall groups: {str(e)}")
|
||||
return None
|
||||
|
||||
def create_firewall_rule(self, rule_data):
|
||||
"""Create a new firewall rule.
|
||||
|
||||
Args:
|
||||
rule_data (dict): Rule configuration with the following fields:
|
||||
- name: Rule name
|
||||
- action: 'accept' or 'drop' or 'reject'
|
||||
- ruleset: 'WAN_IN', 'WAN_OUT', 'WAN_LOCAL', 'LAN_IN', etc.
|
||||
- protocol: 'all', 'tcp', 'udp', etc.
|
||||
- src_address: Source address/network (optional)
|
||||
- dst_address: Destination address/network (optional)
|
||||
- src_port: Source port (optional)
|
||||
- dst_port: Destination port (optional)
|
||||
- enabled: True/False
|
||||
|
||||
Returns:
|
||||
dict: Created rule data or None if request fails
|
||||
"""
|
||||
url = f"{self.host}/proxy/network/api/s/default/rest/firewallrule"
|
||||
|
||||
try:
|
||||
response = self.session.post(url, json=rule_data, timeout=10)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f"Failed to create firewall rule: {str(e)}")
|
||||
return None
|
||||
|
||||
def update_firewall_rule(self, rule_id, rule_data):
|
||||
"""Update an existing firewall rule.
|
||||
|
||||
Args:
|
||||
rule_id (str): ID of the rule to update
|
||||
rule_data (dict): Updated rule configuration
|
||||
|
||||
Returns:
|
||||
dict: Updated rule data or None if request fails
|
||||
"""
|
||||
url = f"{self.host}/proxy/network/api/s/default/rest/firewallrule/{rule_id}"
|
||||
|
||||
try:
|
||||
response = self.session.put(url, json=rule_data, timeout=10)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f"Failed to update firewall rule: {str(e)}")
|
||||
return None
|
||||
|
||||
def delete_firewall_rule(self, rule_id):
|
||||
"""Delete a firewall rule.
|
||||
|
||||
Args:
|
||||
rule_id (str): ID of the rule to delete
|
||||
|
||||
Returns:
|
||||
bool: True if successful, False otherwise
|
||||
"""
|
||||
url = f"{self.host}/proxy/network/api/s/default/rest/firewallrule/{rule_id}"
|
||||
|
||||
try:
|
||||
response = self.session.delete(url, timeout=10)
|
||||
response.raise_for_status()
|
||||
return True
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f"Failed to delete firewall rule: {str(e)}")
|
||||
return False
|
||||
|
||||
def get_networks(self):
|
||||
"""Get all networks configuration.
|
||||
|
||||
Returns:
|
||||
dict: List of networks or None if request fails
|
||||
"""
|
||||
url = f"{self.host}/proxy/network/api/s/default/rest/networkconf"
|
||||
|
||||
try:
|
||||
response = self.session.get(url, timeout=10)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f"Failed to get networks: {str(e)}")
|
||||
return None
|
||||
157
unifi_integration/i18n/fr_CA.po
Normal file
157
unifi_integration/i18n/fr_CA.po
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * unifi_integration
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server 17.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-03-11 12:34+0000\n"
|
||||
"PO-Revision-Date: 2025-03-11 12:34+0000\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Plural-Forms: \n"
|
||||
|
||||
#. module: unifi_integration
|
||||
#: model:ir.model.fields,field_description:unifi_integration.field_udm_port_forward__name
|
||||
msgid "Name"
|
||||
msgstr "Nom"
|
||||
|
||||
#. module: unifi_integration
|
||||
#: model:ir.model.fields,field_description:unifi_integration.field_udm_port_forward__enabled
|
||||
msgid "Enabled"
|
||||
msgstr "Activé"
|
||||
|
||||
#. module: unifi_integration
|
||||
#: model:ir.model.fields,field_description:unifi_integration.field_udm_port_forward__src_port
|
||||
msgid "Source Port"
|
||||
msgstr "Port source"
|
||||
|
||||
#. module: unifi_integration
|
||||
#: model:ir.model.fields,field_description:unifi_integration.field_udm_port_forward__dst_port
|
||||
msgid "Destination Port"
|
||||
msgstr "Port de destination"
|
||||
|
||||
#. module: unifi_integration
|
||||
#: model:ir.model.fields,field_description:unifi_integration.field_udm_port_forward__dst_address
|
||||
msgid "Destination Address"
|
||||
msgstr "Adresse de destination"
|
||||
|
||||
#. module: unifi_integration
|
||||
#: model:ir.model.fields,field_description:unifi_integration.field_udm_dns_config__enabled
|
||||
msgid "Enabled"
|
||||
msgstr "Activé"
|
||||
|
||||
#. module: unifi_integration
|
||||
#: model:ir.model.fields,field_description:unifi_integration.field_udm_dns_config__filters_enabled
|
||||
msgid "Content Filtering Enabled"
|
||||
msgstr "Filtrage de contenu activé"
|
||||
|
||||
#. module: unifi_integration
|
||||
#: model:ir.model.fields,field_description:unifi_integration.field_udm_dns_config__custom_dns
|
||||
msgid "Custom DNS Servers"
|
||||
msgstr "Serveurs DNS personnalisés"
|
||||
|
||||
#. module: unifi_integration
|
||||
#: model:ir.model.fields,field_description:unifi_integration.field_udm_routing_config__ospf_enabled
|
||||
msgid "OSPF Enabled"
|
||||
msgstr "OSPF activé"
|
||||
|
||||
#. module: unifi_integration
|
||||
#: model:ir.model.fields,field_description:unifi_integration.field_udm_routing_config__static_routes
|
||||
msgid "Static Routes"
|
||||
msgstr "Routes statiques"
|
||||
|
||||
#. module: unifi_integration
|
||||
#: model:ir.model.fields,field_description:unifi_integration.field_udm_port_forward__protocol
|
||||
msgid "Protocol"
|
||||
msgstr "Protocole"
|
||||
|
||||
#. module: unifi_integration
|
||||
#: model:ir.model.fields.selection,name:unifi_integration.selection__udm_port_forward__protocol__tcp
|
||||
msgid "TCP"
|
||||
msgstr "TCP"
|
||||
|
||||
#. module: unifi_integration
|
||||
#: model:ir.model.fields.selection,name:unifi_integration.selection__udm_port_forward__protocol__udp
|
||||
msgid "UDP"
|
||||
msgstr "UDP"
|
||||
|
||||
#. module: unifi_integration
|
||||
#: model:ir.model.fields.selection,name:unifi_integration.selection__udm_port_forward__protocol__both
|
||||
msgid "TCP & UDP"
|
||||
msgstr "TCP et UDP"
|
||||
|
||||
#. module: unifi_integration
|
||||
#: model:ir.actions.act_window,name:unifi_integration.action_udm_port_forward
|
||||
#: model:ir.ui.menu,name:unifi_integration.menu_udm_port_forward
|
||||
msgid "Port Forwards"
|
||||
msgstr "Redirections de ports"
|
||||
|
||||
#. module: unifi_integration
|
||||
#: model:ir.actions.act_window,name:unifi_integration.action_udm_dns_config
|
||||
#: model:ir.ui.menu,name:unifi_integration.menu_udm_dns_config
|
||||
msgid "DNS Configuration"
|
||||
msgstr "Configuration DNS"
|
||||
|
||||
#. module: unifi_integration
|
||||
#: model:ir.actions.act_window,name:unifi_integration.action_udm_routing_config
|
||||
#: model:ir.ui.menu,name:unifi_integration.menu_udm_routing_config
|
||||
msgid "Routing Configuration"
|
||||
msgstr "Configuration du routage"
|
||||
|
||||
#. module: unifi_integration
|
||||
#: model:ir.model,name:unifi_integration.model_udm_port_forward
|
||||
msgid "UDM Pro Port Forward Rule"
|
||||
msgstr "Règle de redirection de port UDM Pro"
|
||||
|
||||
#. module: unifi_integration
|
||||
#: model:ir.model,name:unifi_integration.model_udm_dns_config
|
||||
msgid "UDM Pro DNS Configuration"
|
||||
msgstr "Configuration DNS UDM Pro"
|
||||
|
||||
#. module: unifi_integration
|
||||
#: model:ir.model,name:unifi_integration.model_udm_routing_config
|
||||
msgid "UDM Pro Routing Configuration"
|
||||
msgstr "Configuration du routage UDM Pro"
|
||||
|
||||
#. module: unifi_integration
|
||||
#: model_terms:ir.ui.view,arch_db:unifi_integration.view_udm_port_forward_form
|
||||
msgid "Identification"
|
||||
msgstr "Identification"
|
||||
|
||||
#. module: unifi_integration
|
||||
#: model_terms:ir.ui.view,arch_db:unifi_integration.view_udm_port_forward_form
|
||||
msgid "Redirection"
|
||||
msgstr "Redirection"
|
||||
|
||||
#. module: unifi_integration
|
||||
#: model_terms:ir.ui.view,arch_db:unifi_integration.view_udm_dns_config_form
|
||||
msgid "État"
|
||||
msgstr "État"
|
||||
|
||||
#. module: unifi_integration
|
||||
#: model_terms:ir.ui.view,arch_db:unifi_integration.view_udm_dns_config_form
|
||||
#: model_terms:ir.ui.view,arch_db:unifi_integration.view_udm_routing_config_form
|
||||
msgid "Configuration"
|
||||
msgstr "Configuration"
|
||||
|
||||
#. module: unifi_integration
|
||||
#: model_terms:ir.ui.view,arch_db:unifi_integration.view_udm_routing_config_form
|
||||
msgid "Protocoles de routage"
|
||||
msgstr "Protocoles de routage"
|
||||
|
||||
#. module: unifi_integration
|
||||
#: model_terms:ir.ui.view,arch_db:unifi_integration.view_udm_routing_config_form
|
||||
msgid "Routes"
|
||||
msgstr "Routes"
|
||||
|
||||
#. module: unifi_integration
|
||||
#: model_terms:ir.ui.view,arch_db:unifi_integration.view_udm_port_forward_form
|
||||
#: model_terms:ir.ui.view,arch_db:unifi_integration.view_udm_dns_config_form
|
||||
#: model_terms:ir.ui.view,arch_db:unifi_integration.view_udm_routing_config_form
|
||||
msgid "Technical"
|
||||
msgstr "Technique"
|
||||
|
|
@ -1,9 +1,25 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Import module files
|
||||
from . import udm_site
|
||||
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
|
||||
from . import udm_port_forward
|
||||
from . import udm_dns
|
||||
from . import udm_routing
|
||||
|
||||
# Import all models to ensure they are registered with Odoo
|
||||
from .udm_site import UdmSite
|
||||
from .udm_config import UdmConfiguration
|
||||
from .udm_network import UdmNetwork, UdmVlan
|
||||
from .udm_device import UdmDevice
|
||||
from .udm_user import UdmUser
|
||||
from .udm_settings import UdmSettings
|
||||
from .udm_firewall import UdmFirewallRule
|
||||
from .udm_port_forward import UdmPortForward
|
||||
from .udm_dns import UdmDnsConfig
|
||||
from .udm_routing import UdmRoutingConfig
|
||||
|
|
|
|||
316
unifi_integration/models/dashboard.py
Normal file
316
unifi_integration/models/dashboard.py
Normal file
|
|
@ -0,0 +1,316 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from odoo import models, fields, api, _
|
||||
from odoo.exceptions import UserError
|
||||
import json
|
||||
from datetime import datetime, timedelta
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
class UdmDashboardMetric(models.Model):
|
||||
"""Model for storing and managing UDM Pro dashboard metrics.
|
||||
|
||||
This model stores real-time and near-real-time metrics from UDM Pro devices,
|
||||
such as bandwidth usage, CPU/memory utilization, connected clients count,
|
||||
and security threat counts. Each metric is associated with a specific site
|
||||
and includes both raw and formatted values for display.
|
||||
|
||||
The model supports:
|
||||
- Multiple metric types with appropriate formatting
|
||||
- Status computation based on thresholds
|
||||
- Historical data storage for trending
|
||||
- Automatic value formatting based on metric type
|
||||
|
||||
Metrics are ordered by last update time to show most recent data first.
|
||||
"""
|
||||
_name = 'udm.dashboard.metric'
|
||||
_description = 'UDM Dashboard Metric'
|
||||
_order = 'last_update desc' # Most recent metrics first
|
||||
|
||||
# Site this metric belongs to, cascade deletion if site is deleted
|
||||
site_id = fields.Many2one('udm.site', string='Site', required=True, ondelete='cascade')
|
||||
|
||||
# Type of metric being tracked
|
||||
metric_type = fields.Selection([
|
||||
('bandwidth_usage', 'Bandwidth Usage'), # Network bandwidth utilization
|
||||
('cpu_usage', 'CPU Usage'), # CPU utilization percentage
|
||||
('memory_usage', 'Memory Usage'), # Memory utilization percentage
|
||||
('clients_count', 'Connected Clients'), # Number of connected network clients
|
||||
('wan_status', 'WAN Status'), # WAN connection status (up/down)
|
||||
('threat_count', 'Security Threats'), # Number of security threats detected
|
||||
('device_status', 'Device Status'), # Status of network devices (online/offline)
|
||||
], string='Metric Type', required=True)
|
||||
|
||||
# Raw metric values
|
||||
current_value = fields.Char(string='Current Value', required=True,
|
||||
help="Current raw value of the metric")
|
||||
max_value = fields.Char(string='Maximum Value',
|
||||
help="Maximum allowed value for this metric, used for threshold calculations")
|
||||
history_data = fields.Text(string='Historical Data',
|
||||
help="JSON data containing historical values for graphing")
|
||||
last_update = fields.Datetime(string='Last Update', default=fields.Datetime.now,
|
||||
help="Timestamp of the last metric update")
|
||||
|
||||
# Computed fields for display
|
||||
formatted_value = fields.Char(compute='_compute_formatted_value', string='Formatted Value',
|
||||
help="Human-readable formatted value with appropriate units")
|
||||
status = fields.Selection([
|
||||
('normal', 'Normal'), # Operating within normal parameters
|
||||
('warning', 'Warning'), # Approaching critical thresholds
|
||||
('critical', 'Critical'), # Exceeded critical thresholds
|
||||
], compute='_compute_status', string='Status',
|
||||
help="Status indicator based on metric thresholds")
|
||||
|
||||
@api.depends('current_value', 'metric_type')
|
||||
def _compute_formatted_value(self):
|
||||
"""Compute a human-readable formatted value based on the metric type.
|
||||
|
||||
This method formats the raw value into a user-friendly string with appropriate units:
|
||||
- For bandwidth: Converts to Mbps or Gbps with 1 decimal place
|
||||
- For CPU/Memory: Adds percentage sign with 1 decimal place
|
||||
- For other metrics: Uses the raw value as is
|
||||
|
||||
The formatted value is used in the UI to display metrics in a consistent
|
||||
and readable format.
|
||||
"""
|
||||
for record in self:
|
||||
if not record.current_value:
|
||||
record.formatted_value = ''
|
||||
continue
|
||||
|
||||
if record.metric_type == 'bandwidth_usage':
|
||||
try:
|
||||
value = float(record.current_value)
|
||||
if value >= 1000:
|
||||
record.formatted_value = f"{value/1000:.1f} Gbps"
|
||||
else:
|
||||
record.formatted_value = f"{value:.1f} 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
|
||||
else:
|
||||
record.formatted_value = record.current_value
|
||||
|
||||
@api.depends('current_value', 'metric_type', 'max_value')
|
||||
def _compute_status(self):
|
||||
"""Compute the status of the metric based on predefined thresholds.
|
||||
|
||||
This method evaluates the current value against thresholds to determine
|
||||
if the metric is in a normal, warning, or critical state. The thresholds
|
||||
vary by metric type:
|
||||
|
||||
CPU/Memory Usage:
|
||||
- Critical: >= 90%
|
||||
- Warning: >= 75%
|
||||
|
||||
Bandwidth Usage:
|
||||
- Critical: >= 90% of max
|
||||
- Warning: >= 75% of max
|
||||
|
||||
Security Threats:
|
||||
- Critical: >= 10 threats
|
||||
- Warning: >= 5 threats
|
||||
|
||||
WAN Status:
|
||||
- Critical: not 'up'
|
||||
|
||||
Device Status (format: 'online/total'):
|
||||
- Critical: > 2 devices offline
|
||||
- Warning: > 0 devices offline
|
||||
|
||||
The status is used in the UI to highlight metrics that need attention,
|
||||
using color-coded badges (green/yellow/red).
|
||||
"""
|
||||
for record in self:
|
||||
status = 'normal'
|
||||
|
||||
try:
|
||||
if record.metric_type in ['cpu_usage', 'memory_usage']:
|
||||
value = float(record.current_value)
|
||||
if value >= 90:
|
||||
status = 'critical'
|
||||
elif value >= 75:
|
||||
status = 'warning'
|
||||
elif record.metric_type == 'bandwidth_usage':
|
||||
if record.max_value:
|
||||
value = float(record.current_value)
|
||||
max_val = float(record.max_value)
|
||||
usage_pct = (value / max_val) * 100
|
||||
if usage_pct >= 90:
|
||||
status = 'critical'
|
||||
elif usage_pct >= 75:
|
||||
status = 'warning'
|
||||
elif record.metric_type == 'threat_count':
|
||||
value = int(record.current_value)
|
||||
if value >= 10:
|
||||
status = 'critical'
|
||||
elif value >= 5:
|
||||
status = 'warning'
|
||||
elif record.metric_type == 'wan_status':
|
||||
if record.current_value != 'up':
|
||||
status = 'critical'
|
||||
elif record.metric_type == 'device_status':
|
||||
if '/' in record.current_value:
|
||||
online, total = map(int, record.current_value.split('/'))
|
||||
offline = total - online
|
||||
if offline > 2:
|
||||
status = 'critical'
|
||||
elif offline > 0:
|
||||
status = 'warning'
|
||||
except (ValueError, TypeError):
|
||||
# If we can't parse the value, assume normal status
|
||||
status = 'normal'
|
||||
|
||||
record.status = status
|
||||
|
||||
class UdmDashboardStat(models.Model):
|
||||
"""Model for storing and analyzing historical UDM Pro statistics.
|
||||
|
||||
This model stores historical statistical data from UDM Pro devices for
|
||||
long-term trend analysis and reporting. It supports both raw data points
|
||||
and aggregated statistics (e.g., daily averages, totals).
|
||||
|
||||
Key features:
|
||||
- Multiple statistic types (bandwidth, clients, threats, uptime)
|
||||
- Support for different units of measurement
|
||||
- Time-based statistics with start/end times
|
||||
- Aggregation capabilities (sum, average, min, max)
|
||||
- Built-in graphing support
|
||||
|
||||
Statistics are ordered by date (descending) to show most recent data first.
|
||||
Raw and aggregated statistics are stored separately to maintain data
|
||||
integrity while allowing flexible reporting.
|
||||
"""
|
||||
_name = 'udm.dashboard.stat'
|
||||
_description = 'UDM Dashboard Statistic'
|
||||
_order = 'date desc' # Most recent statistics first
|
||||
|
||||
# Site this statistic belongs to, cascade deletion if site is deleted
|
||||
site_id = fields.Many2one('udm.site', string='Site', required=True, ondelete='cascade')
|
||||
|
||||
# Date of the statistic record
|
||||
date = fields.Date(string='Date', required=True, default=fields.Date.today,
|
||||
help="Date this statistic was recorded")
|
||||
|
||||
# Type of statistic being tracked
|
||||
stat_type = fields.Selection([
|
||||
('bandwidth_usage', 'Bandwidth Usage'), # Total bandwidth used
|
||||
('client_count', 'Client Count'), # Number of clients over time
|
||||
('threat_blocked', 'Threats Blocked'), # Number of security threats blocked
|
||||
('device_uptime', 'Device Uptime'), # Device uptime duration
|
||||
], string='Statistic Type', required=True,
|
||||
help="Type of statistical data being recorded")
|
||||
|
||||
# Numerical value and its unit
|
||||
value = fields.Float(string='Value', required=True,
|
||||
help="Numerical value of the statistic")
|
||||
unit = fields.Selection([
|
||||
('bytes', 'Bytes'), # For bandwidth measurements
|
||||
('count', 'Count'), # For counting items (clients, threats)
|
||||
('percentage', 'Percentage'), # For utilization metrics
|
||||
('hours', 'Hours'), # For time-based metrics
|
||||
], string='Unit', required=True,
|
||||
help="Unit of measurement for the value")
|
||||
|
||||
# Time range for detailed statistics
|
||||
time_start = fields.Datetime(string='Start Time',
|
||||
help="Start time for time-based statistics (e.g. hourly bandwidth usage)")
|
||||
time_end = fields.Datetime(string='End Time',
|
||||
help="End time for time-based statistics")
|
||||
|
||||
# Aggregation fields
|
||||
is_aggregate = fields.Boolean(string='Is Aggregate', default=False,
|
||||
help="Indicates if this record represents aggregated data")
|
||||
aggregate_type = fields.Selection([
|
||||
('sum', 'Sum'), # Total over the period
|
||||
('avg', 'Average'), # Average over the period
|
||||
('min', 'Minimum'), # Minimum value in the period
|
||||
('max', 'Maximum'), # Maximum value in the period
|
||||
], string='Aggregate Type',
|
||||
help="Type of aggregation used for this record")
|
||||
|
||||
@api.model
|
||||
def aggregate_stats(self, site_id, stat_type, start_date, end_date, aggregate_type='avg'):
|
||||
"""Aggregate statistics for a specific site and type over a date range.
|
||||
|
||||
This method calculates aggregate values (sum, average, min, max) for
|
||||
non-aggregated statistics within the specified date range.
|
||||
|
||||
Args:
|
||||
site_id (int): ID of the site to aggregate stats for
|
||||
stat_type (str): Type of statistic to aggregate
|
||||
start_date (date): Start date of the range (inclusive)
|
||||
end_date (date): End date of the range (inclusive)
|
||||
aggregate_type (str): Type of aggregation to perform
|
||||
'sum': Total of all values
|
||||
'avg': Average of all values
|
||||
'min': Minimum value
|
||||
'max': Maximum value
|
||||
|
||||
Returns:
|
||||
float: The aggregated value, or 0.0 if no stats found
|
||||
"""
|
||||
domain = [
|
||||
('site_id', '=', site_id),
|
||||
('stat_type', '=', stat_type),
|
||||
('date', '>=', start_date),
|
||||
('date', '<=', end_date),
|
||||
('is_aggregate', '=', False) # Only aggregate raw statistics
|
||||
]
|
||||
|
||||
stats = self.search(domain)
|
||||
if not stats:
|
||||
return 0.0
|
||||
|
||||
values = stats.mapped('value')
|
||||
|
||||
if aggregate_type == 'sum':
|
||||
return sum(values)
|
||||
elif aggregate_type == 'avg':
|
||||
return sum(values) / len(values)
|
||||
elif aggregate_type == 'min':
|
||||
return min(values)
|
||||
elif aggregate_type == 'max':
|
||||
return max(values)
|
||||
else:
|
||||
return 0.0
|
||||
|
||||
def action_view_graph(self):
|
||||
"""Open a graph view showing statistics over time.
|
||||
|
||||
This method creates an action to display a line graph of the statistic
|
||||
values over time. The graph shows raw (non-aggregated) values grouped
|
||||
by day.
|
||||
|
||||
The graph view is configured to:
|
||||
- Show values on the y-axis
|
||||
- Use a line graph for trend visualization
|
||||
- Group data points by day on the x-axis
|
||||
- Filter for the same site and statistic type
|
||||
- Exclude aggregated records
|
||||
|
||||
Returns:
|
||||
dict: An action dictionary that Odoo uses to open the graph view
|
||||
"""
|
||||
self.ensure_one()
|
||||
action = {
|
||||
'name': _('Statistics Graph'),
|
||||
'view_mode': 'graph',
|
||||
'res_model': 'udm.dashboard.stat',
|
||||
'type': 'ir.actions.act_window',
|
||||
'domain': [
|
||||
('site_id', '=', self.site_id.id),
|
||||
('stat_type', '=', self.stat_type),
|
||||
('is_aggregate', '=', False) # Show only raw values
|
||||
],
|
||||
'context': {
|
||||
'graph_measure': 'value', # Y-axis measurement
|
||||
'graph_mode': 'line', # Line graph for trends
|
||||
'graph_groupbys': ['date:day'] # Group by day on X-axis
|
||||
}
|
||||
}
|
||||
return action
|
||||
File diff suppressed because it is too large
Load diff
21
unifi_integration/models/udm_dashboard_data_point.py
Normal file
21
unifi_integration/models/udm_dashboard_data_point.py
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from odoo import models, fields, api, _
|
||||
|
||||
class UdmDashboardDataPoint(models.Model):
|
||||
"""Data point for dashboard statistics"""
|
||||
_name = 'udm.dashboard.data.point'
|
||||
_description = 'Dashboard Data Point'
|
||||
_order = 'sequence'
|
||||
|
||||
# Reference to the statistic this data point belongs to
|
||||
stat_id = fields.Many2one('udm.dashboard.stat', string='Statistic',
|
||||
required=True, ondelete='cascade')
|
||||
|
||||
# Data point attributes
|
||||
sequence = fields.Integer(string='Sequence', default=10,
|
||||
help='Order of this data point in the series')
|
||||
value = fields.Float(string='Value',
|
||||
help='Numeric value of this data point')
|
||||
timestamp = fields.Datetime(string='Timestamp',
|
||||
help='When this data point was recorded')
|
||||
|
|
@ -3,20 +3,44 @@
|
|||
from odoo import models, fields, api, _
|
||||
|
||||
class UdmDevice(models.Model):
|
||||
"""Représentation d'un périphérique dans l'UDM Pro"""
|
||||
"""Represents a network device in the UniFi system
|
||||
|
||||
This model stores information about devices connected to the network,
|
||||
including both UniFi devices (APs, switches) and client devices.
|
||||
|
||||
Devices are linked to a specific site and are automatically deleted when
|
||||
the site is deleted (cascade).
|
||||
"""
|
||||
_name = 'udm.device'
|
||||
_description = 'UDM Pro Device'
|
||||
_description = 'UniFi Device'
|
||||
_order = 'last_seen desc' # Most recently seen devices first
|
||||
|
||||
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')
|
||||
site_id = fields.Many2one('udm.site', string='Site', required=True,
|
||||
ondelete='cascade',
|
||||
help='Site this device belongs to')
|
||||
name = fields.Char(string='Name',
|
||||
help='Device name or hostname')
|
||||
mac_address = fields.Char(string='MAC Address', required=True,
|
||||
help='Device MAC address')
|
||||
ip_address = fields.Char(string='IP Address',
|
||||
help='Current IP address')
|
||||
device_type = fields.Selection([
|
||||
('uap', 'Access Point'),
|
||||
('usw', 'Switch'),
|
||||
('ugw', 'Gateway'),
|
||||
('udm', 'Dream Machine'),
|
||||
('client', 'Client Device'),
|
||||
('other', 'Other')
|
||||
], string='Device Type', required=True, default='client',
|
||||
help='Type of device')
|
||||
model = fields.Char(string='Model',
|
||||
help='Device model name')
|
||||
last_seen = fields.Datetime(string='Last Seen',
|
||||
help='Last time the device was seen online')
|
||||
raw_data = fields.Text(string='Raw Data',
|
||||
help='Raw device data in JSON format')
|
||||
|
||||
# Champs calculés
|
||||
# Computed fields
|
||||
status = fields.Selection([
|
||||
('online', 'Online'),
|
||||
('offline', 'Offline'),
|
||||
|
|
@ -25,8 +49,13 @@ class UdmDevice(models.Model):
|
|||
|
||||
@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é
|
||||
"""Compute device online status based on last seen timestamp
|
||||
|
||||
Status is determined by comparing the last_seen timestamp with current time:
|
||||
- Online: seen within last 10 minutes
|
||||
- Offline: not seen for more than 10 minutes
|
||||
- Unknown: never seen (no last_seen timestamp)
|
||||
"""
|
||||
for record in self:
|
||||
if not record.last_seen:
|
||||
record.status = 'unknown'
|
||||
|
|
|
|||
27
unifi_integration/models/udm_dns.py
Normal file
27
unifi_integration/models/udm_dns.py
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from odoo import models, fields, api, _
|
||||
|
||||
class UdmDnsConfig(models.Model):
|
||||
"""Configuration DNS pour le système UniFi
|
||||
|
||||
Ce modèle stocke la configuration DNS pour les contrôleurs UDM/UDR.
|
||||
Il gère les paramètres DNS comme les serveurs personnalisés et le filtrage de contenu.
|
||||
|
||||
La configuration DNS est liée à un site spécifique et est automatiquement supprimée
|
||||
lorsque le site est supprimé (cascade).
|
||||
"""
|
||||
_name = 'udm.dns.config'
|
||||
_description = 'UniFi DNS Configuration'
|
||||
|
||||
site_id = fields.Many2one('udm.site', string='Site', required=True,
|
||||
ondelete='cascade',
|
||||
help='Site this DNS configuration belongs to')
|
||||
enabled = fields.Boolean(string='Enabled', default=True,
|
||||
help='Enable DNS server')
|
||||
filters_enabled = fields.Boolean(string='Content Filtering Enabled', default=False,
|
||||
help='Enable DNS content filtering')
|
||||
custom_dns = fields.Char(string='Custom DNS Servers',
|
||||
help='Comma-separated list of custom DNS servers')
|
||||
raw_data = fields.Text(string='Raw Data',
|
||||
help='Raw DNS configuration data in JSON format')
|
||||
21
unifi_integration/models/udm_dns_config.py
Normal file
21
unifi_integration/models/udm_dns_config.py
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from odoo import models, fields, api, _
|
||||
|
||||
class UdmDnsConfig(models.Model):
|
||||
"""DNS configuration for the UDM Pro"""
|
||||
_name = 'udm.dns.config'
|
||||
_description = 'UDM Pro DNS Configuration'
|
||||
|
||||
# Site this DNS configuration belongs to
|
||||
site_id = fields.Many2one('udm.site', string='Site', required=True, ondelete='cascade')
|
||||
|
||||
# DNS settings
|
||||
enabled = fields.Boolean(string='Enabled', default=True,
|
||||
help='Whether DNS service is enabled')
|
||||
filters_enabled = fields.Boolean(string='Content Filtering Enabled', default=False,
|
||||
help='Enable content filtering on DNS queries')
|
||||
custom_dns = fields.Char(string='Custom DNS Servers',
|
||||
help='Comma-separated list of custom DNS servers')
|
||||
raw_data = fields.Text(string='Raw Data',
|
||||
help='Raw configuration data in JSON format')
|
||||
|
|
@ -3,35 +3,75 @@
|
|||
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'
|
||||
"""Represents a firewall rule in the UniFi system
|
||||
|
||||
config_id = fields.Many2one('udm.configuration', string='Configuration', ondelete='cascade')
|
||||
name = fields.Char(string='Name', required=True)
|
||||
enabled = fields.Boolean(string='Enabled', default=True)
|
||||
This model stores firewall rules that control network traffic. Each rule
|
||||
specifies what traffic is allowed or blocked based on various criteria such
|
||||
as source/destination addresses, ports, and protocols.
|
||||
|
||||
Rules are linked to a specific site and are automatically deleted when
|
||||
the site is deleted (cascade).
|
||||
"""
|
||||
_name = 'udm.firewall.rule'
|
||||
_description = 'UniFi Firewall Rule'
|
||||
_order = 'sequence, id'
|
||||
|
||||
site_id = fields.Many2one('udm.site', string='Site', required=True,
|
||||
ondelete='cascade',
|
||||
help='Site this firewall rule belongs to')
|
||||
name = fields.Char(string='Name', required=True,
|
||||
help='Rule name')
|
||||
description = fields.Text(string='Description',
|
||||
help='Detailed description of the rule purpose')
|
||||
enabled = fields.Boolean(string='Enabled', default=True,
|
||||
help='Whether this rule is active')
|
||||
sequence = fields.Integer(string='Sequence', default=10,
|
||||
help='Order in which rules are evaluated')
|
||||
action = fields.Selection([
|
||||
('accept', 'Accept'),
|
||||
('drop', 'Drop'),
|
||||
('reject', 'Reject')
|
||||
], string='Action')
|
||||
], string='Action', required=True, default='drop',
|
||||
help='Action to take when rule matches')
|
||||
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')
|
||||
], string='Protocol', required=True, default='all',
|
||||
help='Network protocol this rule applies to')
|
||||
source = fields.Char(string='Source',
|
||||
help='Source network or IP address in CIDR format')
|
||||
destination = fields.Char(string='Destination',
|
||||
help='Destination network or IP address in CIDR format')
|
||||
src_address = fields.Char(string='Source Address',
|
||||
help='Legacy field, use source instead')
|
||||
dst_address = fields.Char(string='Destination Address',
|
||||
help='Legacy field, use destination instead')
|
||||
src_port = fields.Char(string='Source Port',
|
||||
help='Source port number or range (e.g. 80 or 1024-2048)')
|
||||
dst_port = fields.Char(string='Destination Port',
|
||||
help='Destination port number or range (e.g. 80 or 1024-2048)')
|
||||
raw_data = fields.Text(string='Raw Data',
|
||||
help='Raw firewall rule data in JSON format')
|
||||
|
||||
# Champs calculés
|
||||
# Computed fields
|
||||
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):
|
||||
"""Compute a human-readable summary of the firewall rule
|
||||
|
||||
This method generates a concise description of the rule's action,
|
||||
protocol, and source/destination addresses and ports. The summary
|
||||
is used in list views and reports to quickly understand what the
|
||||
rule does without viewing all details.
|
||||
|
||||
Example summaries:
|
||||
- ACCEPT TCP from 192.168.1.0/24:80 to 10.0.0.0/8:443
|
||||
- DROP ICMP from 172.16.0.0/16 to any
|
||||
- REJECT UDP from any to 192.168.2.10:53
|
||||
"""
|
||||
for record in self:
|
||||
parts = []
|
||||
|
||||
|
|
@ -41,16 +81,18 @@ class UdmFirewallRule(models.Model):
|
|||
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)
|
||||
# Use new source/destination fields if available, fall back to legacy fields
|
||||
src_addr = record.source or record.src_address or 'any'
|
||||
dst_addr = record.destination or record.dst_address or 'any'
|
||||
|
||||
if record.dst_address:
|
||||
dst = f"to {record.dst_address}"
|
||||
if record.dst_port:
|
||||
dst += f":{record.dst_port}"
|
||||
parts.append(dst)
|
||||
src = f"from {src_addr}"
|
||||
if record.src_port:
|
||||
src += f":{record.src_port}"
|
||||
parts.append(src)
|
||||
|
||||
record.rule_summary = ' '.join(parts) if parts else 'No details'
|
||||
dst = f"to {dst_addr}"
|
||||
if record.dst_port:
|
||||
dst += f":{record.dst_port}"
|
||||
parts.append(dst)
|
||||
|
||||
record.rule_summary = ' '.join(parts)
|
||||
|
|
|
|||
|
|
@ -3,34 +3,64 @@
|
|||
from odoo import models, fields, api, _
|
||||
|
||||
class UdmNetwork(models.Model):
|
||||
"""Représentation d'un réseau dans l'UDM Pro"""
|
||||
"""Represents a network in the UniFi system
|
||||
|
||||
This model stores network configuration for both UDM/UDR and software controllers.
|
||||
Each network can be associated with a VLAN and contains DHCP configuration.
|
||||
|
||||
Networks are linked to a specific site and are automatically deleted when
|
||||
the site is deleted (cascade).
|
||||
"""
|
||||
_name = 'udm.network'
|
||||
_description = 'UDM Pro Network'
|
||||
_description = 'UniFi 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')
|
||||
site_id = fields.Many2one('udm.site', string='Site', required=True, ondelete='cascade',
|
||||
help='Site this network belongs to')
|
||||
name = fields.Char(string='Name', required=True,
|
||||
help='Network name')
|
||||
purpose = fields.Selection([
|
||||
('corporate', 'Corporate'),
|
||||
('guest', 'Guest'),
|
||||
('iot', 'IoT'),
|
||||
('other', 'Other')
|
||||
], string='Purpose', required=True, default='corporate',
|
||||
help='Network purpose/type')
|
||||
subnet = fields.Char(string='Subnet', required=True,
|
||||
help='Network subnet in CIDR format (e.g. 192.168.1.0/24)')
|
||||
vlan_id_number = fields.Integer(string='VLAN ID',
|
||||
help='VLAN ID number (1-4094)')
|
||||
vlan_id = fields.Many2one('udm.vlan', string='VLAN',
|
||||
compute='_compute_vlan_id', store=True,
|
||||
help='Associated VLAN configuration')
|
||||
dhcp_enabled = fields.Boolean(string='DHCP Enabled', default=True,
|
||||
help='Enable DHCP server for this network')
|
||||
dhcp_start = fields.Char(string='DHCP Start',
|
||||
help='Start of DHCP range')
|
||||
dhcp_stop = fields.Char(string='DHCP Stop',
|
||||
help='End of DHCP range')
|
||||
domain_name = fields.Char(string='Domain Name',
|
||||
help='Network domain name')
|
||||
raw_data = fields.Text(string='Raw Data',
|
||||
help='Raw network configuration data in JSON format')
|
||||
|
||||
# Statistiques
|
||||
# Statistics
|
||||
device_count = fields.Integer(string='Device Count', compute='_compute_device_count')
|
||||
|
||||
@api.depends('vlan_id_number', 'config_id')
|
||||
@api.depends('vlan_id_number', 'site_id')
|
||||
def _compute_vlan_id(self):
|
||||
"""Compute the associated VLAN based on the VLAN ID number
|
||||
|
||||
This method searches for a VLAN with matching ID in the same site.
|
||||
The VLAN ID is stored and updated automatically when either the
|
||||
VLAN ID number or site changes.
|
||||
"""
|
||||
for record in self:
|
||||
if not record.vlan_id_number or not record.config_id:
|
||||
if not record.vlan_id_number or not record.site_id:
|
||||
record.vlan_id = False
|
||||
continue
|
||||
|
||||
vlan = self.env['udm.vlan'].search([
|
||||
('config_id', '=', record.config_id.id),
|
||||
('site_id', '=', record.site_id.id),
|
||||
('vlan_id', '=', record.vlan_id_number)
|
||||
], limit=1)
|
||||
|
||||
|
|
@ -38,23 +68,36 @@ class UdmNetwork(models.Model):
|
|||
|
||||
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
|
||||
# This function would be more accurate if devices were linked to networks
|
||||
# For now, this is just an example
|
||||
record.device_count = 0
|
||||
|
||||
|
||||
class UdmVLAN(models.Model):
|
||||
"""Représentation d'un VLAN dans l'UDM Pro"""
|
||||
class UdmVlan(models.Model):
|
||||
"""Represents a VLAN in the UniFi system
|
||||
|
||||
This model stores VLAN configurations for both UDM/UDR and software controllers.
|
||||
VLANs are used to segment network traffic and can be associated with multiple
|
||||
networks.
|
||||
|
||||
VLANs are linked to a specific site and are automatically deleted when
|
||||
the site is deleted (cascade).
|
||||
"""
|
||||
_name = 'udm.vlan'
|
||||
_description = 'UDM Pro VLAN'
|
||||
_description = 'UniFi 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')
|
||||
site_id = fields.Many2one('udm.site', string='Site', required=True, ondelete='cascade',
|
||||
help='Site this VLAN belongs to')
|
||||
vlan_id = fields.Integer(string='VLAN ID', required=True,
|
||||
help='VLAN ID number (1-4094)')
|
||||
name = fields.Char(string='Name', required=True,
|
||||
help='VLAN name')
|
||||
enabled = fields.Boolean(string='Enabled', default=True,
|
||||
help='VLAN status')
|
||||
raw_data = fields.Text(string='Raw Data',
|
||||
help='Raw VLAN configuration data in JSON format')
|
||||
|
||||
# Relations inverses
|
||||
# Reverse relations
|
||||
network_ids = fields.One2many('udm.network', 'vlan_id', string='Networks')
|
||||
network_count = fields.Integer(compute='_compute_network_count')
|
||||
|
||||
|
|
|
|||
80
unifi_integration/models/udm_port_forward.py
Normal file
80
unifi_integration/models/udm_port_forward.py
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from odoo import models, fields, api, _
|
||||
|
||||
class UdmPortForward(models.Model):
|
||||
"""Represents a port forwarding rule in the UniFi system
|
||||
|
||||
This model stores port forwarding rules that allow external access to
|
||||
internal services. Each rule maps an external port to an internal
|
||||
IP address and port.
|
||||
|
||||
Port forwards are linked to a specific site and are automatically deleted
|
||||
when the site is deleted (cascade).
|
||||
"""
|
||||
_name = 'udm.port.forward'
|
||||
_description = 'UniFi Port Forward'
|
||||
_order = 'sequence, id'
|
||||
|
||||
site_id = fields.Many2one('udm.site', string='Site', required=True,
|
||||
ondelete='cascade',
|
||||
help='Site this port forward belongs to')
|
||||
name = fields.Char(string='Name', required=True,
|
||||
help='Rule name')
|
||||
description = fields.Text(string='Description',
|
||||
help='Detailed description of the port forward purpose')
|
||||
enabled = fields.Boolean(string='Enabled', default=True,
|
||||
help='Whether this rule is active')
|
||||
sequence = fields.Integer(string='Sequence', default=10,
|
||||
help='Order in which rules are evaluated')
|
||||
protocol = fields.Selection([
|
||||
('tcp', 'TCP'),
|
||||
('udp', 'UDP'),
|
||||
('tcp_udp', 'TCP & UDP')
|
||||
], string='Protocol', required=True, default='tcp',
|
||||
help='Network protocol to forward')
|
||||
source = fields.Char(string='Source',
|
||||
help='Source network or IP address in CIDR format')
|
||||
dst_port = fields.Char(string='Destination Port', required=True,
|
||||
help='External port number or range (e.g. 80 or 1024-2048)')
|
||||
fwd_ip = fields.Char(string='Forward IP', required=True,
|
||||
help='Internal IP address to forward to')
|
||||
fwd_port = fields.Char(string='Forward Port', required=True,
|
||||
help='Internal port number or range')
|
||||
raw_data = fields.Text(string='Raw Data',
|
||||
help='Raw port forward data in JSON format')
|
||||
|
||||
# Computed fields
|
||||
rule_summary = fields.Char(string='Rule Summary', compute='_compute_rule_summary')
|
||||
|
||||
@api.depends('protocol', 'source', 'dst_port', 'fwd_ip', 'fwd_port')
|
||||
def _compute_rule_summary(self):
|
||||
"""Compute a human-readable summary of the port forward rule
|
||||
|
||||
This method generates a concise description of the rule's protocol,
|
||||
source, and port mappings. The summary is used in list views and
|
||||
reports to quickly understand what the rule does without viewing
|
||||
all details.
|
||||
|
||||
Example summaries:
|
||||
- TCP from any:80 to 192.168.1.100:8080
|
||||
- UDP from 203.0.113.0/24:53 to 192.168.2.10:53
|
||||
- TCP & UDP from any:25565 to 192.168.3.50:25565
|
||||
"""
|
||||
for record in self:
|
||||
parts = []
|
||||
|
||||
if record.protocol:
|
||||
parts.append(record.protocol.upper())
|
||||
|
||||
src = f"from {record.source or 'any'}"
|
||||
if record.dst_port:
|
||||
src += f":{record.dst_port}"
|
||||
parts.append(src)
|
||||
|
||||
dst = f"to {record.fwd_ip}"
|
||||
if record.fwd_port:
|
||||
dst += f":{record.fwd_port}"
|
||||
parts.append(dst)
|
||||
|
||||
record.rule_summary = ' '.join(parts)
|
||||
25
unifi_integration/models/udm_routing.py
Normal file
25
unifi_integration/models/udm_routing.py
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from odoo import models, fields, api, _
|
||||
|
||||
class UdmRoutingConfig(models.Model):
|
||||
"""Routing configuration for the UniFi system
|
||||
|
||||
This model stores routing configuration for both UDM/UDR controllers.
|
||||
It manages routing settings such as OSPF and static routes.
|
||||
|
||||
Routing configuration is linked to a specific site and is automatically deleted
|
||||
when the site is deleted (cascade).
|
||||
"""
|
||||
_name = 'udm.routing.config'
|
||||
_description = 'UniFi Routing Configuration'
|
||||
|
||||
site_id = fields.Many2one('udm.site', string='Site', required=True,
|
||||
ondelete='cascade',
|
||||
help='Site this routing configuration belongs to')
|
||||
ospf_enabled = fields.Boolean(string='OSPF Enabled', default=False,
|
||||
help='Enable OSPF routing')
|
||||
static_routes = fields.Text(string='Static Routes',
|
||||
help='Comma-separated list of static routes in format: network/prefix via nexthop')
|
||||
raw_data = fields.Text(string='Raw Data',
|
||||
help='Raw routing configuration data in JSON format')
|
||||
19
unifi_integration/models/udm_routing_config.py
Normal file
19
unifi_integration/models/udm_routing_config.py
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from odoo import models, fields, api, _
|
||||
|
||||
class UdmRoutingConfig(models.Model):
|
||||
"""Routing configuration for the UDM Pro"""
|
||||
_name = 'udm.routing.config'
|
||||
_description = 'UDM Pro Routing Configuration'
|
||||
|
||||
# Site this routing configuration belongs to
|
||||
site_id = fields.Many2one('udm.site', string='Site', required=True, ondelete='cascade')
|
||||
|
||||
# Routing settings
|
||||
ospf_enabled = fields.Boolean(string='OSPF Enabled', default=False,
|
||||
help='Enable OSPF routing protocol')
|
||||
static_routes = fields.Text(string='Static Routes',
|
||||
help='List of static routes in format: network/prefix via nexthop')
|
||||
raw_data = fields.Text(string='Raw Data',
|
||||
help='Raw configuration data in JSON format')
|
||||
|
|
@ -3,25 +3,90 @@
|
|||
from odoo import models, fields, api, _
|
||||
|
||||
class UdmSettings(models.Model):
|
||||
"""Configuration générale de l'UDM Pro"""
|
||||
"""General configuration of the 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')
|
||||
# Basic fields
|
||||
site_id = fields.Many2one('udm.site', string='Site', required=True, ondelete='cascade',
|
||||
help='Site these settings belong to')
|
||||
name = fields.Char(string='Name', compute='_compute_name', store=True,
|
||||
help='Settings name for display')
|
||||
|
||||
# Champs calculés
|
||||
ntp_server_list = fields.Many2many('ir.model.data', string='NTP Server List',
|
||||
compute='_compute_server_lists')
|
||||
# Time settings
|
||||
timezone = fields.Selection([
|
||||
('UTC', 'UTC'),
|
||||
('America/Montreal', 'America/Montreal'),
|
||||
('America/New_York', 'America/New_York'),
|
||||
('America/Toronto', 'America/Toronto'),
|
||||
('Europe/Paris', 'Europe/Paris')
|
||||
], string='Timezone', default='America/Montreal',
|
||||
help='Timezone for this site')
|
||||
ntp_enabled = fields.Boolean(string='NTP Enabled', default=True,
|
||||
help='Enable NTP synchronization')
|
||||
ntp_servers = fields.Char(string='NTP Servers',
|
||||
help='Comma-separated list of NTP servers')
|
||||
|
||||
# DNS settings
|
||||
dns_enabled = fields.Boolean(string='DNS Enabled', default=True,
|
||||
help='Enable DNS service')
|
||||
dns_servers = fields.Char(string='DNS Servers',
|
||||
help='Comma-separated list of DNS servers')
|
||||
dns_forwarding = fields.Boolean(string='DNS Forwarding', default=True,
|
||||
help='Enable DNS forwarding')
|
||||
|
||||
# Advanced settings
|
||||
upnp_enabled = fields.Boolean(string='UPnP Enabled', default=False,
|
||||
help='Enable Universal Plug and Play')
|
||||
mdns_enabled = fields.Boolean(string='mDNS Enabled', default=True,
|
||||
help='Enable multicast DNS')
|
||||
igmp_proxy = fields.Boolean(string='IGMP Proxy', default=False,
|
||||
help='Enable IGMP proxy')
|
||||
|
||||
# Raw data
|
||||
raw_data = fields.Text(string='Raw Data',
|
||||
help='Raw settings data in JSON format')
|
||||
|
||||
# Computed fields
|
||||
ntp_server_list = fields.Many2many('ir.model.data', string='NTP Server List',
|
||||
compute='_compute_server_lists',
|
||||
help='List of NTP servers for display')
|
||||
dns_server_list = fields.Many2many('ir.model.data', string='DNS Server List',
|
||||
compute='_compute_server_lists')
|
||||
compute='_compute_server_lists',
|
||||
help='List of DNS servers for display')
|
||||
|
||||
@api.depends('site_id')
|
||||
def _compute_name(self):
|
||||
"""Compute a display name for the settings
|
||||
|
||||
The name is based on the site name and includes a timestamp to
|
||||
differentiate between multiple settings records for the same site.
|
||||
"""
|
||||
for record in self:
|
||||
if record.site_id:
|
||||
record.name = f'{record.site_id.name} Settings'
|
||||
else:
|
||||
record.name = 'New Settings'
|
||||
|
||||
@api.depends('ntp_servers', 'dns_servers')
|
||||
def _compute_server_lists(self):
|
||||
"""Convert comma-separated server lists to Many2many fields
|
||||
|
||||
This method splits the NTP and DNS server strings into lists
|
||||
for display in the user interface. The lists are stored in
|
||||
technical fields that are not persisted to the database.
|
||||
"""
|
||||
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
|
||||
# Convert NTP servers string to list
|
||||
if record.ntp_servers:
|
||||
ntp_servers = [s.strip() for s in record.ntp_servers.split(',')]
|
||||
record.ntp_server_list = [(6, 0, ntp_servers)]
|
||||
else:
|
||||
record.ntp_server_list = False
|
||||
|
||||
# Convert DNS servers string to list
|
||||
if record.dns_servers:
|
||||
dns_servers = [s.strip() for s in record.dns_servers.split(',')]
|
||||
record.dns_server_list = [(6, 0, dns_servers)]
|
||||
else:
|
||||
record.dns_server_list = False
|
||||
|
|
|
|||
231
unifi_integration/models/udm_site.py
Normal file
231
unifi_integration/models/udm_site.py
Normal file
|
|
@ -0,0 +1,231 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from odoo import models, fields, api, _
|
||||
from odoo.exceptions import UserError
|
||||
import requests
|
||||
import json
|
||||
import logging
|
||||
import random
|
||||
from datetime import timedelta
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
class UdmSite(models.Model):
|
||||
"""Represents a UniFi site managed by one or more UDM Pro
|
||||
|
||||
This model is the central entity that groups all UniFi configurations and devices.
|
||||
Each site can have multiple configurations, devices, networks, and users.
|
||||
"""
|
||||
_name = 'udm.site'
|
||||
_description = 'UniFi Site'
|
||||
_order = 'name'
|
||||
_inherit = ['mail.thread']
|
||||
|
||||
# Basic site information
|
||||
name = fields.Char(string='Name', required=True, tracking=True,
|
||||
help='Site name')
|
||||
site_id = fields.Char(string='Site ID',
|
||||
help="Site identifier in UniFi (usually 'default')",
|
||||
default='default',
|
||||
readonly=True,
|
||||
required=True)
|
||||
description = fields.Text(string='Description', tracking=True,
|
||||
help='Site description')
|
||||
address = fields.Text(string='Physical Address', tracking=True,
|
||||
help='Physical location of this site')
|
||||
active = fields.Boolean(string='Active', default=True, tracking=True,
|
||||
help='Indicates if this site is currently active')
|
||||
|
||||
# Controller configuration
|
||||
controller_type = fields.Selection([
|
||||
('udm', 'UDM/UDR'),
|
||||
('software', 'Software Controller')
|
||||
], string='Controller Type', required=True, default='udm', tracking=True,
|
||||
help='Type of UniFi controller managing this site')
|
||||
|
||||
host = fields.Char(string='Host', required=True, tracking=True,
|
||||
help='IP address or hostname of the controller')
|
||||
port = fields.Integer(string='Port', default=443, required=True,
|
||||
help='Port number (default: 443)')
|
||||
username = fields.Char(string='Username', required=True, tracking=True)
|
||||
password = fields.Char(string='Password', required=True)
|
||||
mfa_token = fields.Char(string='MFA Token',
|
||||
help='Two-factor authentication token if enabled')
|
||||
|
||||
# Configuration data
|
||||
timestamp = fields.Datetime(string='Last Update', tracking=True)
|
||||
raw_data = fields.Text(string='Raw Data',
|
||||
help='Raw configuration data in JSON format')
|
||||
|
||||
# Related Records
|
||||
configuration_ids = fields.One2many('udm.configuration', 'site_id',
|
||||
string='Configurations',
|
||||
help='Configurations for this site')
|
||||
network_ids = fields.One2many('udm.network', 'site_id',
|
||||
string='Networks',
|
||||
help='Networks in this site')
|
||||
vlan_ids = fields.One2many('udm.vlan', 'site_id',
|
||||
string='VLANs',
|
||||
help='VLANs in this site')
|
||||
device_ids = fields.One2many('udm.device', 'site_id',
|
||||
string='Devices',
|
||||
help='Devices in this site')
|
||||
user_ids = fields.One2many('udm.user', 'site_id',
|
||||
string='Users',
|
||||
help='Users in this site')
|
||||
firewall_rule_ids = fields.One2many('udm.firewall.rule', 'site_id',
|
||||
string='Firewall Rules',
|
||||
help='Firewall rules for this site')
|
||||
port_forward_ids = fields.One2many('udm.port.forward', 'site_id',
|
||||
string='Port Forwards',
|
||||
help='Port forwarding rules for this site')
|
||||
dns_config_ids = fields.One2many('udm.dns.config', 'site_id',
|
||||
string='DNS Configurations',
|
||||
help='DNS configurations for this site')
|
||||
routing_config_ids = fields.One2many('udm.routing.config', 'site_id',
|
||||
string='Routing Configurations',
|
||||
help='Routing configurations for this site')
|
||||
|
||||
# Dashboard Metrics
|
||||
dashboard_metric_ids = fields.One2many('udm.dashboard.metric', 'site_id',
|
||||
string='Dashboard Metrics',
|
||||
help='Real-time metrics for this site')
|
||||
dashboard_stat_ids = fields.One2many('udm.dashboard.stat', 'site_id',
|
||||
string='Statistics',
|
||||
help='Historical statistics for this site')
|
||||
|
||||
# Computed Fields
|
||||
config_count = fields.Integer(compute='_compute_counts',
|
||||
string='Configuration Count',
|
||||
help='Number of configurations in this site')
|
||||
device_count = fields.Integer(compute='_compute_device_count',
|
||||
string='Total Devices',
|
||||
help='Total number of devices in this site')
|
||||
client_count = fields.Integer(compute='_compute_client_count',
|
||||
string='Connected Clients',
|
||||
help='Number of currently connected clients')
|
||||
|
||||
@api.depends('configuration_ids')
|
||||
def _compute_counts(self):
|
||||
"""Compute the number of configurations in this site"""
|
||||
for site in self:
|
||||
site.config_count = len(site.configuration_ids)
|
||||
|
||||
@api.depends('device_ids')
|
||||
def _compute_device_count(self):
|
||||
"""Compute the total number of devices in this site"""
|
||||
for site in self:
|
||||
site.device_count = len(site.device_ids)
|
||||
|
||||
@api.depends('user_ids')
|
||||
def _compute_client_count(self):
|
||||
"""Compute the number of connected clients"""
|
||||
for site in self:
|
||||
site.client_count = len(site.user_ids.filtered(lambda u: u.is_connected))
|
||||
|
||||
def action_view_configurations(self):
|
||||
"""Open the configurations view filtered for this site"""
|
||||
self.ensure_one()
|
||||
return {
|
||||
'name': _('Configurations'),
|
||||
'type': 'ir.actions.act_window',
|
||||
'res_model': 'udm.configuration',
|
||||
'view_mode': 'tree,form',
|
||||
'domain': [('site_id', '=', self.id)],
|
||||
'context': {'default_site_id': self.id},
|
||||
}
|
||||
|
||||
def action_view_dashboard(self):
|
||||
"""Display the dashboard for this site"""
|
||||
self.ensure_one()
|
||||
return {
|
||||
'name': _('Site Dashboard'),
|
||||
'type': 'ir.actions.act_window',
|
||||
'res_model': 'udm.dashboard',
|
||||
'view_mode': 'kanban,form',
|
||||
'domain': [('site_id', '=', self.id)],
|
||||
'context': {'default_site_id': self.id},
|
||||
}
|
||||
|
||||
def action_refresh_metrics(self):
|
||||
"""Refresh the dashboard metrics and statistics for this site"""
|
||||
self.ensure_one()
|
||||
for config in self.configuration_ids:
|
||||
config.action_update_dashboard_metrics()
|
||||
|
||||
_sql_constraints = [
|
||||
('name_uniq', 'unique(name)',
|
||||
'Site name must be unique!'),
|
||||
]
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
"""Override create to verify connection before saving"""
|
||||
for vals in vals_list:
|
||||
if not self._verify_connection(vals):
|
||||
raise UserError(_('Could not connect to the UniFi controller. '
|
||||
'Please verify your credentials and connection details.'))
|
||||
return super(UdmSite, self).create(vals_list)
|
||||
|
||||
def write(self, vals):
|
||||
"""Override write to verify connection if connection details change"""
|
||||
if any(field in vals for field in ['host', 'port', 'username',
|
||||
'password', 'mfa_token']):
|
||||
for record in self:
|
||||
test_vals = {**record.copy_data()[0], **vals}
|
||||
if not self._verify_connection(test_vals):
|
||||
raise UserError(_('Could not connect to the UniFi controller. '
|
||||
'Please verify your credentials and connection details.'))
|
||||
return super(UdmSite, self).write(vals)
|
||||
|
||||
def _verify_connection(self, vals):
|
||||
"""Verify connection to the UniFi controller
|
||||
|
||||
Args:
|
||||
vals (dict): Values to use for connection test
|
||||
|
||||
Returns:
|
||||
bool: True if connection successful, False otherwise
|
||||
"""
|
||||
try:
|
||||
# Build base URL based on controller type
|
||||
base_url = f"https://{vals['host']}:{vals.get('port', 443)}"
|
||||
if vals.get('controller_type') == 'software':
|
||||
base_url += '/api'
|
||||
else:
|
||||
base_url += '/proxy/network'
|
||||
|
||||
# Disable SSL verification warning
|
||||
import urllib3
|
||||
urllib3.disable_warnings()
|
||||
|
||||
# Try to authenticate
|
||||
session = requests.Session()
|
||||
response = session.post(
|
||||
f"{base_url}/api/auth/login",
|
||||
json={
|
||||
'username': vals['username'],
|
||||
'password': vals['password'],
|
||||
'remember': True,
|
||||
'token': vals.get('mfa_token', '')
|
||||
},
|
||||
verify=False
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
return True
|
||||
|
||||
_logger.error(
|
||||
"Authentication failed for UniFi controller at %s: %s",
|
||||
vals['host'],
|
||||
response.text
|
||||
)
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
_logger.error(
|
||||
"Error connecting to UniFi controller at %s: %s",
|
||||
vals['host'],
|
||||
str(e)
|
||||
)
|
||||
return False
|
||||
|
|
@ -3,11 +3,12 @@
|
|||
from odoo import models, fields, api, _
|
||||
|
||||
class UdmSystemInfo(models.Model):
|
||||
"""Informations système de l'UDM Pro"""
|
||||
"""System information of the UDM Pro"""
|
||||
_name = 'udm.system.info'
|
||||
_description = 'UDM Pro System Information'
|
||||
|
||||
config_id = fields.Many2one('udm.configuration', string='Configuration', ondelete='cascade')
|
||||
# Site this system information belongs to
|
||||
site_id = fields.Many2one('udm.site', string='Site', required=True, ondelete='cascade')
|
||||
hostname = fields.Char(string='Hostname')
|
||||
version = fields.Char(string='Firmware Version')
|
||||
model = fields.Char(string='Model')
|
||||
|
|
|
|||
|
|
@ -1,23 +1,105 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from odoo import models, fields, api, _
|
||||
from datetime import timedelta
|
||||
|
||||
class UdmUser(models.Model):
|
||||
"""Représentation d'un utilisateur dans l'UDM Pro"""
|
||||
"""Represents a user in the UniFi system
|
||||
|
||||
This model stores information about users who have access to the UniFi
|
||||
network, including their roles and permissions. Users can be administrators,
|
||||
operators, or read-only viewers.
|
||||
|
||||
Users are linked to a specific site and are automatically deleted when
|
||||
the site is deleted (cascade).
|
||||
"""
|
||||
_name = 'udm.user'
|
||||
_description = 'UDM Pro User'
|
||||
_description = 'UniFi 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')
|
||||
# Basic fields
|
||||
site_id = fields.Many2one('udm.site', string='Site', required=True,
|
||||
ondelete='cascade',
|
||||
help='Site this user belongs to')
|
||||
name = fields.Char(string='Name', required=True,
|
||||
help='User full name')
|
||||
email = fields.Char(string='Email',
|
||||
help='User email address')
|
||||
mac_address = fields.Char(string='MAC Address',
|
||||
help='Device MAC address')
|
||||
ip_address = fields.Char(string='IP Address',
|
||||
help='Current IP address')
|
||||
network_id = fields.Many2one('udm.network', string='Network',
|
||||
ondelete='set null',
|
||||
help='Network this user is connected to')
|
||||
|
||||
# Champs calculés pour des statistiques ou filtrage
|
||||
is_admin = fields.Boolean(string='Is Admin', compute='_compute_is_admin', store=True)
|
||||
# Role and permissions
|
||||
role = fields.Selection([
|
||||
('admin', 'Administrator'),
|
||||
('operator', 'Operator'),
|
||||
('viewer', 'Viewer')
|
||||
], string='Role', required=True, default='viewer',
|
||||
help='User role and permissions level')
|
||||
enabled = fields.Boolean(string='Enabled', default=True,
|
||||
help='Whether the user account is active')
|
||||
|
||||
# Connection status
|
||||
status = fields.Selection([
|
||||
('connected', 'Connected'),
|
||||
('disconnected', 'Disconnected'),
|
||||
('idle', 'Idle')
|
||||
], string='Status', default='disconnected',
|
||||
help='Current connection status of the user')
|
||||
last_seen = fields.Datetime(string='Last Seen',
|
||||
help='Last time the user was seen online')
|
||||
|
||||
# Technical fields
|
||||
raw_data = fields.Text(string='Raw Data',
|
||||
help='Raw user data in JSON format')
|
||||
|
||||
# Computed fields
|
||||
is_admin = fields.Boolean(string='Is Admin', compute='_compute_is_admin', store=True,
|
||||
help='Whether this user has administrator privileges')
|
||||
is_connected = fields.Boolean(string='Is Connected', compute='_compute_is_connected',
|
||||
store=True, help='Whether this user is currently connected')
|
||||
|
||||
@api.depends('role')
|
||||
def _compute_is_admin(self):
|
||||
"""Compute whether the user has administrator privileges
|
||||
|
||||
This is a convenience field that makes it easy to filter for or check
|
||||
if a user has admin rights without parsing the role field.
|
||||
"""
|
||||
for record in self:
|
||||
record.is_admin = record.role and 'admin' in record.role.lower() or False
|
||||
record.is_admin = record.role == 'admin'
|
||||
|
||||
@api.depends('status')
|
||||
def _compute_is_connected(self):
|
||||
"""Compute whether the user is currently connected
|
||||
|
||||
This is a convenience field that makes it easy to filter for or check
|
||||
if a user is currently connected without parsing the status field.
|
||||
"""
|
||||
for record in self:
|
||||
record.is_connected = record.status == 'connected'
|
||||
|
||||
def _update_status(self):
|
||||
"""Update the user's connection status based on last seen time
|
||||
|
||||
This method is called periodically to update the user's status:
|
||||
- If last seen within 5 minutes: Connected
|
||||
- If last seen within 30 minutes: Idle
|
||||
- Otherwise: Disconnected
|
||||
"""
|
||||
now = fields.Datetime.now()
|
||||
for record in self:
|
||||
if not record.last_seen:
|
||||
record.status = 'disconnected'
|
||||
continue
|
||||
|
||||
delta = now - record.last_seen
|
||||
if delta <= timedelta(minutes=5):
|
||||
record.status = 'connected'
|
||||
elif delta <= timedelta(minutes=30):
|
||||
record.status = 'idle'
|
||||
else:
|
||||
record.status = 'disconnected'
|
||||
|
|
|
|||
|
|
@ -1,22 +1,33 @@
|
|||
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
|
||||
access_udm_site_user,udm.site.user,unifi_integration.model_udm_site,unifi_integration.group_udm_pro_user,1,0,0,0
|
||||
access_udm_site_manager,udm.site.manager,unifi_integration.model_udm_site,unifi_integration.group_udm_pro_manager,1,1,1,1
|
||||
access_udm_configuration_user,udm.configuration.user,unifi_integration.model_udm_configuration,unifi_integration.group_udm_pro_user,1,0,0,0
|
||||
access_udm_configuration_manager,udm.configuration.manager,unifi_integration.model_udm_configuration,unifi_integration.group_udm_pro_manager,1,1,1,1
|
||||
access_udm_settings_user,udm.settings.user,unifi_integration.model_udm_settings,unifi_integration.group_udm_pro_user,1,0,0,0
|
||||
access_udm_settings_manager,udm.settings.manager,unifi_integration.model_udm_settings,unifi_integration.group_udm_pro_manager,1,1,1,1
|
||||
access_udm_system_info_user,udm.system.info.user,unifi_integration.model_udm_system_info,unifi_integration.group_udm_pro_user,1,0,0,0
|
||||
access_udm_system_info_manager,udm.system.info.manager,unifi_integration.model_udm_system_info,unifi_integration.group_udm_pro_manager,1,1,1,1
|
||||
access_udm_network_user,udm.network.user,unifi_integration.model_udm_network,unifi_integration.group_udm_pro_user,1,0,0,0
|
||||
access_udm_network_manager,udm.network.manager,unifi_integration.model_udm_network,unifi_integration.group_udm_pro_manager,1,1,1,1
|
||||
access_udm_vlan_user,udm.vlan.user,unifi_integration.model_udm_vlan,unifi_integration.group_udm_pro_user,1,0,0,0
|
||||
access_udm_vlan_manager,udm.vlan.manager,unifi_integration.model_udm_vlan,unifi_integration.group_udm_pro_manager,1,1,1,1
|
||||
access_udm_device_user,udm.device.user,unifi_integration.model_udm_device,unifi_integration.group_udm_pro_user,1,0,0,0
|
||||
access_udm_device_manager,udm.device.manager,unifi_integration.model_udm_device,unifi_integration.group_udm_pro_manager,1,1,1,1
|
||||
access_udm_user_user,udm.user.user,unifi_integration.model_udm_user,unifi_integration.group_udm_pro_user,1,0,0,0
|
||||
access_udm_user_manager,udm.user.manager,unifi_integration.model_udm_user,unifi_integration.group_udm_pro_manager,1,1,1,1
|
||||
access_udm_firewall_rule_user,udm.firewall.rule.user,unifi_integration.model_udm_firewall_rule,unifi_integration.group_udm_pro_user,1,0,0,0
|
||||
access_udm_firewall_rule_manager,udm.firewall.rule.manager,unifi_integration.model_udm_firewall_rule,unifi_integration.group_udm_pro_manager,1,1,1,1
|
||||
access_udm_dashboard_metric_user,udm.dashboard.metric.user,unifi_integration.model_udm_dashboard_metric,unifi_integration.group_udm_pro_user,1,0,0,0
|
||||
access_udm_dashboard_metric_manager,udm.dashboard.metric.manager,unifi_integration.model_udm_dashboard_metric,unifi_integration.group_udm_pro_manager,1,1,1,1
|
||||
access_udm_dashboard_stat_user,udm.dashboard.stat.user,unifi_integration.model_udm_dashboard_stat,unifi_integration.group_udm_pro_user,1,0,0,0
|
||||
access_udm_dashboard_stat_manager,udm.dashboard.stat.manager,unifi_integration.model_udm_dashboard_stat,unifi_integration.group_udm_pro_manager,1,1,1,1
|
||||
access_udm_site_import_wizard_user,udm.site.import.wizard.user,unifi_integration.model_udm_site_import_wizard,unifi_integration.group_udm_pro_user,1,1,1,0
|
||||
access_udm_site_import_wizard_manager,udm.site.import.wizard.manager,unifi_integration.model_udm_site_import_wizard,unifi_integration.group_udm_pro_manager,1,1,1,1
|
||||
access_udm_mfa_wizard_user,udm.mfa.wizard.user,unifi_integration.model_udm_mfa_wizard,unifi_integration.group_udm_pro_user,1,1,1,0
|
||||
access_udm_mfa_wizard_manager,udm.mfa.wizard.manager,unifi_integration.model_udm_mfa_wizard,unifi_integration.group_udm_pro_manager,1,1,1,1
|
||||
access_udm_port_forward_user,udm.port.forward.user,unifi_integration.model_udm_port_forward,unifi_integration.group_udm_pro_user,1,0,0,0
|
||||
access_udm_port_forward_manager,udm.port.forward.manager,unifi_integration.model_udm_port_forward,unifi_integration.group_udm_pro_manager,1,1,1,1
|
||||
access_udm_dns_config_user,udm.dns.config.user,unifi_integration.model_udm_dns_config,unifi_integration.group_udm_pro_user,1,0,0,0
|
||||
access_udm_dns_config_manager,udm.dns.config.manager,unifi_integration.model_udm_dns_config,unifi_integration.group_udm_pro_manager,1,1,1,1
|
||||
access_udm_routing_config_user,udm.routing.config.user,unifi_integration.model_udm_routing_config,unifi_integration.group_udm_pro_user,1,0,0,0
|
||||
access_udm_routing_config_manager,udm.routing.config.manager,unifi_integration.model_udm_routing_config,unifi_integration.group_udm_pro_manager,1,1,1,1
|
||||
|
|
|
|||
|
Can't render this file because it has a wrong number of fields in line 18.
|
|
|
@ -1,50 +1,56 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data noupdate="0">
|
||||
<!-- UDM Pro Documentation Security Groups -->
|
||||
<!-- Security Groups for UDM Pro Integration -->
|
||||
<!-- Defines two groups: Users (read-only) and Managers (full access) -->
|
||||
<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="name">UDM Pro Integration</field>
|
||||
<field name="description">Manage UDM Pro configurations and network settings</field>
|
||||
<field name="sequence">50</field>
|
||||
</record>
|
||||
|
||||
<!-- User Group: can only view configurations -->
|
||||
<!-- User Group: Read-only access to configurations and network data -->
|
||||
<!-- Members can view settings but cannot modify them -->
|
||||
<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="category_id" ref="unifi_integration.module_category_udm_pro"/>
|
||||
<field name="implied_ids" eval="[(4, ref('base.group_user'))]"/>
|
||||
</record>
|
||||
|
||||
<!-- Manager Group: can create, edit, and import configurations -->
|
||||
<!-- Manager Group: Full access to all UDM Pro features -->
|
||||
<!-- Members can create, edit, delete configurations and perform network operations -->
|
||||
<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="category_id" ref="unifi_integration.module_category_udm_pro"/>
|
||||
<field name="implied_ids" eval="[(4, ref('unifi_integration.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 -->
|
||||
<!-- Record-Level Access Rules -->
|
||||
<!-- Define permissions for different user groups -->
|
||||
|
||||
<!-- Users can only view records -->
|
||||
<!-- User Group Rule: Read-only access -->
|
||||
<!-- Allows viewing but prevents any modifications -->
|
||||
<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="model_id" ref="unifi_integration.model_udm_configuration"/>
|
||||
<field name="domain_force">[(1, '=', 1)]</field>
|
||||
<field name="groups" eval="[(4, ref('group_udm_pro_user'))]"/>
|
||||
<field name="groups" eval="[(4, ref('unifi_integration.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 -->
|
||||
<!-- Manager Group Rule: Full access -->
|
||||
<!-- Allows all operations including create, read, update, and delete -->
|
||||
<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="model_id" ref="unifi_integration.model_udm_configuration"/>
|
||||
<field name="domain_force">[(1, '=', 1)]</field>
|
||||
<field name="groups" eval="[(4, ref('group_udm_pro_manager'))]"/>
|
||||
<field name="groups" eval="[(4, ref('unifi_integration.group_udm_pro_manager'))]"/>
|
||||
<field name="perm_read" eval="True"/>
|
||||
<field name="perm_write" eval="True"/>
|
||||
<field name="perm_create" eval="True"/>
|
||||
|
|
|
|||
Binary file not shown.
|
Before Width: | Height: | Size: 1 B After Width: | Height: | Size: 16 KiB |
8
unifi_integration/static/description/icon.svg
Normal file
8
unifi_integration/static/description/icon.svg
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="512" height="512">
|
||||
<path d="M0 0 C1.27091568 -0.00226556 2.54183137 -0.00453111 3.85125965 -0.00686532 C7.34476346 -0.01292804 10.83824314 -0.01285632 14.33175099 -0.01167095 C18.1131286 -0.01146552 21.89449874 -0.01699916 25.6758728 -0.02172852 C33.07210029 -0.03000555 40.46831981 -0.03278203 47.86455155 -0.03334089 C53.88288677 -0.03381752 59.90121937 -0.03587956 65.91955376 -0.03904152 C83.01771857 -0.04784057 100.11587713 -0.05246215 117.21404422 -0.05171105 C118.59429327 -0.05165111 118.59429327 -0.05165111 120.00242615 -0.05158997 C121.38437899 -0.05152864 121.38437899 -0.05152864 122.79425006 -0.05146609 C137.71187188 -0.05105998 152.6294684 -0.0606226 167.54708291 -0.07472833 C182.89877481 -0.08912886 198.25045442 -0.09599648 213.60215324 -0.09513456 C222.20706529 -0.09480382 230.81195084 -0.09749634 239.41685677 -0.10831261 C246.74547615 -0.11746621 254.07405703 -0.11955335 261.40267969 -0.11280318 C265.13365979 -0.10956212 268.86456623 -0.10939261 272.59553909 -0.11815643 C309.54080544 -0.19929666 343.40948187 2.22050447 371.12913513 29.29386902 C393.77009446 53.01141575 399.01606619 81.45718414 398.9737854 113.29364014 C398.97718374 115.20001366 398.97718374 115.20001366 398.98065072 117.14489979 C398.98671344 120.63840359 398.98664172 124.13188327 398.98545635 127.62539113 C398.98525092 131.40676873 398.99078456 135.18813888 398.99551392 138.96951294 C399.00379095 146.36574043 399.00656743 153.76195995 399.00712629 161.15819169 C399.00760292 167.1765269 399.00966496 173.19485951 399.01282692 179.21319389 C399.02162597 196.31135871 399.02624755 213.40951726 399.02549645 230.50768436 C399.02543651 231.88793341 399.02543651 231.88793341 399.02537537 233.29606628 C399.02531405 234.67801912 399.02531405 234.67801912 399.02525149 236.0878902 C399.02484538 251.00551201 399.034408 265.92310854 399.04851373 280.84072305 C399.06291426 296.19241495 399.06978188 311.54409455 399.06891996 326.89579338 C399.06858922 335.50070543 399.07128174 344.10559098 399.08209801 352.7104969 C399.09125161 360.03911629 399.09333875 367.36769717 399.08658858 374.69631983 C399.08334752 378.42729993 399.08317801 382.15820636 399.09194183 385.88917923 C399.17308206 422.83444557 396.75328093 456.70312201 369.67991638 484.42277527 C345.96236965 507.0637346 317.51660126 512.30970633 285.68014526 512.26742554 C283.77377174 512.27082387 283.77377174 512.27082387 281.82888561 512.27429086 C278.33538181 512.28035357 274.84190213 512.28028186 271.34839427 512.27909648 C267.56701667 512.27889106 263.78564652 512.2844247 260.00427246 512.28915405 C252.60804497 512.29743109 245.21182545 512.30020757 237.81559371 512.30076643 C231.7972585 512.30124306 225.77892589 512.3033051 219.76059151 512.30646706 C202.66242669 512.31526611 185.56426814 512.31988768 168.46610104 512.31913658 C167.54593501 512.31909663 166.62576897 512.31905667 165.67771912 512.3190155 C164.75641722 512.31897462 163.83511533 512.31893374 162.8858952 512.31889162 C147.96827339 512.31848551 133.05067686 512.32804814 118.13306236 512.34215387 C102.78137045 512.3565544 87.42969085 512.36342201 72.07799202 512.36256009 C63.47307997 512.36222936 54.86819442 512.36492188 46.2632885 512.37573814 C38.93466911 512.38489174 31.60608823 512.38697888 24.27746557 512.38022872 C20.54648547 512.37698766 16.81557904 512.37681815 13.08460617 512.38558197 C-23.86066017 512.46672219 -57.72933661 510.04692106 -85.44898987 482.97355652 C-108.0899492 459.25600979 -113.33592092 430.81024139 -113.29364014 398.9737854 C-113.29590569 397.70286972 -113.29817125 396.43195403 -113.30050546 395.12252575 C-113.30656817 391.62902195 -113.30649646 388.13554226 -113.30531108 384.64203441 C-113.30510566 380.8606568 -113.3106393 377.07928666 -113.31536865 373.2979126 C-113.32364569 365.90168511 -113.32642217 358.50546559 -113.32698103 351.10923385 C-113.32745766 345.09089863 -113.3295197 339.07256603 -113.33268166 333.05423164 C-113.34148071 315.95606683 -113.34610228 298.85790827 -113.34535118 281.75974118 C-113.34531123 280.83957514 -113.34527127 279.91940911 -113.3452301 278.97135925 C-113.34518922 278.05005736 -113.34514834 277.12875547 -113.34510622 276.17953534 C-113.34470011 261.26191353 -113.35426274 246.344317 -113.36836847 231.42670249 C-113.38276899 216.07501059 -113.38963661 200.72333098 -113.38877469 185.37163216 C-113.38844396 176.76672011 -113.39113648 168.16183456 -113.40195274 159.55692863 C-113.41110634 152.22830925 -113.41319348 144.89972837 -113.40644332 137.57110571 C-113.40320226 133.84012561 -113.40303275 130.10921917 -113.41179657 126.37824631 C-113.49293679 89.43297996 -111.07313566 55.56430353 -83.99977112 27.84465027 C-60.28222439 5.20369094 -31.83645599 -0.04228079 0 0 Z " fill="#0050C5" transform="translate(113.15992736816406,-0.1337127685546875)"/>
|
||||
<path d="M0 0 C21.12 0 42.24 0 64 0 C64.04898438 9.41273438 64.09796875 18.82546875 64.1484375 28.5234375 C64.18383101 34.49349867 64.21971551 40.46355023 64.2578125 46.43359375 C64.31816453 55.90541375 64.37653601 65.37721574 64.421875 74.84912109 C64.45490884 81.74839107 64.49480221 88.64758222 64.54279006 95.54676497 C64.56794231 99.1967854 64.58975625 102.8467543 64.60334396 106.49683762 C64.61867474 110.58052567 64.6493258 114.66396575 64.68115234 118.74755859 C64.68331253 119.94590332 64.68547272 121.14424805 64.68769836 122.37890625 C64.87873988 141.62110372 70.38428347 160.32199607 84.04296875 174.234375 C96.41235283 186.18617581 113.13309488 191.46547739 129.99365234 191.50634766 C146.46008424 191.13108713 161.62981619 183.838499 173.54296875 172.67578125 C187.69937896 157.25902292 191.19801217 138.56515288 191.4140625 118.13671875 C191.43342865 116.97872269 191.4527948 115.82072662 191.4727478 114.62763977 C191.53184279 110.98099806 191.57864514 107.33432357 191.625 103.6875 C191.66324444 101.1933467 191.70229977 98.69920569 191.7421875 96.20507812 C191.83898974 90.13678797 191.91911364 84.06860592 192 78 C202.56 78 213.12 78 224 78 C224 88.56 224 99.12 224 110 C234.56 110 245.12 110 256 110 C256.46628602 150.33374099 249.99851134 185.94314883 221 216 C219.34123834 217.67453436 217.67438855 219.34109116 216 221 C215.23042969 221.76699219 214.46085938 222.53398437 213.66796875 223.32421875 C199.12575944 237.17939455 180.48003919 247.16114165 161 252 C160.27135742 252.18191895 159.54271484 252.36383789 158.79199219 252.55126953 C125.5327954 260.5777138 91.54317854 255.67203142 62.08642578 238.2890625 C34.81062868 221.43717756 11.50670415 193.69497306 4 162 C0.88536857 146.99604654 -0.1605994 131.89477306 -0.11352539 116.59057617 C-0.11367142 115.32071365 -0.11381744 114.05085114 -0.1139679 112.74250793 C-0.11425142 109.30535619 -0.10842757 105.8682403 -0.10139394 102.43109679 C-0.09508542 98.82233732 -0.09455073 95.21357676 -0.09336853 91.60481262 C-0.09027711 84.79120564 -0.08209362 77.97761496 -0.07201904 71.16401511 C-0.05868414 61.94259134 -0.05334029 52.72116302 -0.04751682 43.49973202 C-0.03815194 28.99980917 -0.01821852 14.49992069 0 0 Z " fill="#EBECF1" transform="translate(128,136)"/>
|
||||
<path d="M0 0 C10.56 0 21.12 0 32 0 C32 10.56 32 21.12 32 32 C21.44 32 10.88 32 0 32 C0 21.44 0 10.88 0 0 Z " fill="#FEFEFF" transform="translate(352,182)"/>
|
||||
<path d="M0 0 C7.92 0 15.84 0 24 0 C24 7.92 24 15.84 24 24 C16.08 24 8.16 24 0 24 C0 16.08 0 8.16 0 0 Z " fill="#FEFEFF" transform="translate(328,158)"/>
|
||||
<path d="M0 0 C5.28 0 10.56 0 16 0 C16 5.28 16 10.56 16 16 C10.72 16 5.44 16 0 16 C0 10.72 0 5.44 0 0 Z " fill="#FEFEFF" transform="translate(360,134)"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 7.4 KiB |
|
|
@ -122,7 +122,7 @@
|
|||
</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"/>
|
||||
<field name="raw_data" widget="ace" options="{'mode': 'json'}" help="Raw JSON data of the configuration"/>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
|
|
@ -172,7 +172,7 @@
|
|||
</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"/>
|
||||
<field name="raw_data" widget="ace" options="{'mode': 'json'}" help="Raw JSON data of the configuration"/>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
|
|
@ -208,7 +208,7 @@
|
|||
</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"/>
|
||||
<field name="raw_data" widget="ace" options="{'mode': 'json'}" help="Raw JSON data of the configuration"/>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
|
|
@ -245,7 +245,7 @@
|
|||
</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"/>
|
||||
<field name="raw_data" widget="ace" options="{'mode': 'json'}" help="Raw JSON data of the configuration"/>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
|
|
@ -280,7 +280,7 @@
|
|||
</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"/>
|
||||
<field name="raw_data" widget="ace" options="{'mode': 'json'}" help="Raw JSON data of the configuration"/>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
|
|
@ -313,7 +313,7 @@
|
|||
</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"/>
|
||||
<field name="raw_data" widget="ace" options="{'mode': 'json'}" help="Raw JSON data of the configuration"/>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
|
|
@ -340,7 +340,7 @@
|
|||
</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"/>
|
||||
<field name="raw_data" widget="ace" options="{'mode': 'json'}" help="Raw JSON data of the configuration"/>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
|
|
@ -379,7 +379,7 @@
|
|||
</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"/>
|
||||
<field name="raw_data" widget="ace" options="{'mode': 'json'}" help="Raw JSON data of the configuration"/>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
<?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>
|
||||
<!-- Configuration List View -->
|
||||
<record id="view_udm_configuration_list" model="ir.ui.view">
|
||||
<field name="name">udm.configuration.list</field>
|
||||
<field name="model">udm.configuration</field>
|
||||
<field name="type">list</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="UDM Pro Configurations">
|
||||
<list string="UDM Pro Configurations">
|
||||
<field name="name"/>
|
||||
<field name="timestamp"/>
|
||||
<field name="system_info_id" optional="show"/>
|
||||
|
|
@ -13,7 +14,7 @@
|
|||
<field name="device_count" optional="show"/>
|
||||
<field name="user_count" optional="show"/>
|
||||
<field name="firewall_rule_count" optional="show"/>
|
||||
</tree>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
|
@ -21,6 +22,7 @@
|
|||
<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="type">form</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="UDM Pro Configuration">
|
||||
<sheet>
|
||||
|
|
@ -38,13 +40,24 @@
|
|||
<field name="firewall_rule_count" widget="statinfo" string="Firewall Rules"/>
|
||||
</button>
|
||||
</div>
|
||||
<header>
|
||||
<button name="action_import_configuration" string="Import Configuration" type="object" class="oe_highlight" invisible="not host"/>
|
||||
</header>
|
||||
<div class="oe_title">
|
||||
<h1>
|
||||
<field name="name"/>
|
||||
</h1>
|
||||
</div>
|
||||
<group>
|
||||
<group>
|
||||
<group string="Connection Settings">
|
||||
<field name="host" required="1"/>
|
||||
<field name="port" required="1"/>
|
||||
<field name="username" required="1"/>
|
||||
<field name="password" password="True" required="1"/>
|
||||
<field name="mfa_token" placeholder="Code reçu par email"/>
|
||||
<field name="active"/>
|
||||
</group>
|
||||
<group string="Configuration Info">
|
||||
<field name="timestamp"/>
|
||||
<field name="system_info_id"/>
|
||||
<field name="settings_id"/>
|
||||
|
|
@ -53,27 +66,27 @@
|
|||
<notebook>
|
||||
<page string="Networks" name="networks">
|
||||
<field name="network_ids" nolabel="1">
|
||||
<tree>
|
||||
<list>
|
||||
<field name="name"/>
|
||||
<field name="purpose"/>
|
||||
<field name="subnet"/>
|
||||
<field name="vlan_id"/>
|
||||
<field name="dhcp_enabled"/>
|
||||
</tree>
|
||||
</list>
|
||||
</field>
|
||||
</page>
|
||||
<page string="VLANs" name="vlans">
|
||||
<field name="vlan_ids" nolabel="1">
|
||||
<tree>
|
||||
<list>
|
||||
<field name="vlan_id"/>
|
||||
<field name="name"/>
|
||||
<field name="enabled"/>
|
||||
</tree>
|
||||
</list>
|
||||
</field>
|
||||
</page>
|
||||
<page string="Devices" name="devices">
|
||||
<field name="device_ids" nolabel="1">
|
||||
<tree>
|
||||
<list>
|
||||
<field name="name"/>
|
||||
<field name="mac"/>
|
||||
<field name="ip"/>
|
||||
|
|
@ -81,23 +94,23 @@
|
|||
<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>
|
||||
</list>
|
||||
</field>
|
||||
</page>
|
||||
<page string="Users" name="users">
|
||||
<field name="user_ids" nolabel="1">
|
||||
<tree>
|
||||
<list>
|
||||
<field name="name"/>
|
||||
<field name="email"/>
|
||||
<field name="role"/>
|
||||
<field name="enabled"/>
|
||||
<field name="is_admin"/>
|
||||
</tree>
|
||||
</list>
|
||||
</field>
|
||||
</page>
|
||||
<page string="Firewall Rules" name="firewall_rules">
|
||||
<field name="firewall_rule_ids" nolabel="1">
|
||||
<tree>
|
||||
<list>
|
||||
<field name="name"/>
|
||||
<field name="enabled"/>
|
||||
<field name="action"/>
|
||||
|
|
@ -105,7 +118,7 @@
|
|||
<field name="src_address"/>
|
||||
<field name="dst_address"/>
|
||||
<field name="rule_summary" optional="show"/>
|
||||
</tree>
|
||||
</list>
|
||||
</field>
|
||||
</page>
|
||||
<page string="Raw Data" name="raw_data">
|
||||
|
|
@ -121,6 +134,7 @@
|
|||
<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="type">search</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Search UDM Pro Configurations">
|
||||
<field name="name"/>
|
||||
|
|
@ -135,4 +149,19 @@
|
|||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Configuration Action Window -->
|
||||
<record id="action_udm_configuration" model="ir.actions.act_window">
|
||||
<field name="name">UDM Pro Configurations</field>
|
||||
<field name="res_model">udm.configuration</field>
|
||||
<field name="view_mode">list,form</field>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
No configurations found
|
||||
</p>
|
||||
<p>
|
||||
Configurations are automatically created when you refresh your UDM Pro settings.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
|
|
|
|||
|
|
@ -1,18 +1,18 @@
|
|||
<?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>
|
||||
<!-- Dashboard Metric List View -->
|
||||
<record id="view_udm_dashboard_metric_list" model="ir.ui.view">
|
||||
<field name="name">udm.dashboard.metric.list</field>
|
||||
<field name="model">udm.dashboard.metric</field>
|
||||
<field name="type">list</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"/>
|
||||
<list string="UDM Pro Dashboard Metrics">
|
||||
<field name="site_id"/>
|
||||
</tree>
|
||||
<field name="metric_type"/>
|
||||
<field name="formatted_value"/>
|
||||
<field name="status" widget="badge" decoration-success="status == 'normal'" decoration-warning="status == 'warning'" decoration-danger="status == 'critical'"/>
|
||||
<field name="last_update"/>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
|
@ -20,21 +20,22 @@
|
|||
<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="type">form</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"/>
|
||||
<field name="site_id"/>
|
||||
<field name="metric_type"/>
|
||||
<field name="current_value"/>
|
||||
<field name="max_value"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="status" widget="badge" decoration-success="status == 'normal'" decoration-warning="status == 'warning'" decoration-danger="status == 'danger'"/>
|
||||
<field name="description"/>
|
||||
<field name="formatted_value"/>
|
||||
<field name="status" widget="badge" decoration-success="status == 'normal'" decoration-warning="status == 'warning'" decoration-danger="status == 'critical'"/>
|
||||
<field name="last_update"/>
|
||||
<field name="site_id"/>
|
||||
<field name="history_data" widget="ace" options="{'mode': 'json'}"/>
|
||||
</group>
|
||||
</group>
|
||||
</sheet>
|
||||
|
|
@ -46,16 +47,18 @@
|
|||
<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="type">search</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Search Dashboard Metrics">
|
||||
<field name="name"/>
|
||||
<field name="value"/>
|
||||
<field name="site_id"/>
|
||||
<field name="metric_type"/>
|
||||
<field name="current_value"/>
|
||||
<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')]"/>
|
||||
<filter string="Critical Status" name="status_critical" domain="[('status', '=', 'critical')]"/>
|
||||
<group expand="0" string="Group By">
|
||||
<filter string="Site" name="group_by_site" domain="[]" context="{'group_by': 'site_id'}"/>
|
||||
<filter string="Metric Type" name="group_by_type" domain="[]" context="{'group_by': 'metric_type'}"/>
|
||||
<filter string="Status" name="group_by_status" domain="[]" context="{'group_by': 'status'}"/>
|
||||
</group>
|
||||
</search>
|
||||
|
|
@ -66,7 +69,7 @@
|
|||
<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="view_mode">list,form</field>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
No dashboard metrics found
|
||||
|
|
@ -76,4 +79,88 @@
|
|||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Dashboard Stat List View -->
|
||||
<record id="view_udm_dashboard_stat_list" model="ir.ui.view">
|
||||
<field name="name">udm.dashboard.stat.list</field>
|
||||
<field name="model">udm.dashboard.stat</field>
|
||||
<field name="type">list</field>
|
||||
<field name="arch" type="xml">
|
||||
<list string="UDM Pro Dashboard Statistics">
|
||||
<field name="site_id"/>
|
||||
<field name="date"/>
|
||||
<field name="stat_type"/>
|
||||
<field name="value"/>
|
||||
<field name="unit"/>
|
||||
<field name="is_aggregate"/>
|
||||
<field name="aggregate_type" attrs="{'invisible': [('is_aggregate', '=', False)]}"/>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Dashboard Stat Form View -->
|
||||
<record id="view_udm_dashboard_stat_form" model="ir.ui.view">
|
||||
<field name="name">udm.dashboard.stat.form</field>
|
||||
<field name="model">udm.dashboard.stat</field>
|
||||
<field name="type">form</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="UDM Pro Dashboard Statistic">
|
||||
<sheet>
|
||||
<group>
|
||||
<group>
|
||||
<field name="site_id"/>
|
||||
<field name="date"/>
|
||||
<field name="stat_type"/>
|
||||
<field name="value"/>
|
||||
<field name="unit"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="time_start"/>
|
||||
<field name="time_end"/>
|
||||
<field name="is_aggregate"/>
|
||||
<field name="aggregate_type" attrs="{'invisible': [('is_aggregate', '=', False)]}"/>
|
||||
</group>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Dashboard Stat Search View -->
|
||||
<record id="view_udm_dashboard_stat_search" model="ir.ui.view">
|
||||
<field name="name">udm.dashboard.stat.search</field>
|
||||
<field name="model">udm.dashboard.stat</field>
|
||||
<field name="type">search</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Search Dashboard Statistics">
|
||||
<field name="site_id"/>
|
||||
<field name="date"/>
|
||||
<field name="stat_type"/>
|
||||
<field name="value"/>
|
||||
<filter string="Aggregated" name="is_aggregate" domain="[('is_aggregate', '=', True)]"/>
|
||||
<filter string="Last 7 Days" name="last_7_days" domain="[('date','>=', (context_today() - relativedelta(days=7)))]" help="Show statistics from the last 7 days"/>
|
||||
<filter string="Last 30 Days" name="last_30_days" domain="[('date','>=', (context_today() - relativedelta(days=30)))]" help="Show statistics from the last 30 days"/>
|
||||
<group expand="0" string="Group By">
|
||||
<filter string="Site" name="group_by_site" domain="[]" context="{'group_by': 'site_id'}"/>
|
||||
<filter string="Statistic Type" name="group_by_type" domain="[]" context="{'group_by': 'stat_type'}"/>
|
||||
<filter string="Date" name="group_by_date" domain="[]" context="{'group_by': 'date:day'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Dashboard Stat Action -->
|
||||
<record id="action_udm_dashboard_stat" model="ir.actions.act_window">
|
||||
<field name="name">Dashboard Statistics</field>
|
||||
<field name="res_model">udm.dashboard.stat</field>
|
||||
<field name="view_mode">list,form</field>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
No dashboard statistics found
|
||||
</p>
|
||||
<p>
|
||||
Dashboard statistics are automatically created and updated when you refresh site metrics.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,28 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<!-- Device List View -->
|
||||
<record id="view_udm_device_list" model="ir.ui.view">
|
||||
<field name="name">udm.device.list</field>
|
||||
<field name="model">udm.device</field>
|
||||
<field name="type">list</field>
|
||||
<field name="arch" type="xml">
|
||||
<list string="UDM Pro Devices">
|
||||
<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'"/>
|
||||
</list>
|
||||
</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="type">form</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="UDM Pro Device">
|
||||
<sheet>
|
||||
|
|
@ -34,4 +53,26 @@
|
|||
</form>
|
||||
</field>
|
||||
</record>
|
||||
<!-- Device Search View -->
|
||||
<record id="view_udm_device_search" model="ir.ui.view">
|
||||
<field name="name">udm.device.search</field>
|
||||
<field name="model">udm.device</field>
|
||||
<field name="type">search</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Search Devices">
|
||||
<field name="name"/>
|
||||
<field name="mac"/>
|
||||
<field name="ip"/>
|
||||
<field name="device_type"/>
|
||||
<field name="model"/>
|
||||
<filter string="Online" name="online" domain="[('status', '=', 'online')]"/>
|
||||
<filter string="Offline" name="offline" domain="[('status', '=', 'offline')]"/>
|
||||
<group expand="0" string="Group By">
|
||||
<filter string="Device Type" name="group_by_type" domain="[]" context="{'group_by': 'device_type'}"/>
|
||||
<filter string="Status" name="group_by_status" domain="[]" context="{'group_by': 'status'}"/>
|
||||
<filter string="Configuration" name="group_by_config" domain="[]" context="{'group_by': 'config_id'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,28 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<!-- Firewall Rule List View -->
|
||||
<record id="view_udm_firewall_rule_list" model="ir.ui.view">
|
||||
<field name="name">udm.firewall.rule.list</field>
|
||||
<field name="model">udm.firewall.rule</field>
|
||||
<field name="type">list</field>
|
||||
<field name="arch" type="xml">
|
||||
<list string="UDM Pro Firewall Rules">
|
||||
<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"/>
|
||||
</list>
|
||||
</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="type">form</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="UDM Pro Firewall Rule">
|
||||
<sheet>
|
||||
|
|
@ -26,16 +45,39 @@
|
|||
</group>
|
||||
</group>
|
||||
<group>
|
||||
<field name="rule_summary" readonly="1" force_save="1" help="Résumé de la règle en langage naturel"/>
|
||||
<field name="rule_summary" readonly="1" force_save="1" help="Natural language summary of the rule"/>
|
||||
<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"/>
|
||||
<field name="raw_data" widget="ace" options="{'mode': 'json'}" help="Raw JSON data of the configuration"/>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
<!-- Firewall Rule Search View -->
|
||||
<record id="view_udm_firewall_rule_search" model="ir.ui.view">
|
||||
<field name="name">udm.firewall.rule.search</field>
|
||||
<field name="model">udm.firewall.rule</field>
|
||||
<field name="type">search</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Search Firewall Rules">
|
||||
<field name="name"/>
|
||||
<field name="action"/>
|
||||
<field name="protocol"/>
|
||||
<field name="src_address"/>
|
||||
<field name="dst_address"/>
|
||||
<filter string="Enabled" name="enabled" domain="[('enabled', '=', True)]"/>
|
||||
<filter string="Allow" name="allow" domain="[('action', '=', 'accept')]"/>
|
||||
<filter string="Drop" name="drop" domain="[('action', '=', 'drop')]"/>
|
||||
<group expand="0" string="Group By">
|
||||
<filter string="Action" name="group_by_action" domain="[]" context="{'group_by': 'action'}"/>
|
||||
<filter string="Protocol" name="group_by_protocol" domain="[]" context="{'group_by': 'protocol'}"/>
|
||||
<filter string="Configuration" name="group_by_config" domain="[]" context="{'group_by': 'config_id'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
|
|
|
|||
|
|
@ -1,170 +1,256 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<!-- UDM Pro Documentation Main Menu -->
|
||||
<!-- UniFi Dream Machine Pro Integration Main Menu -->
|
||||
<menuitem id="menu_udm_pro_root"
|
||||
name="UDM Pro"
|
||||
web_icon="udm_pro_docs,static/description/icon.png"
|
||||
sequence="90"/>
|
||||
name="UniFi Integration"
|
||||
web_icon="unifi_integration,static/description/icon.png"
|
||||
sequence="90"
|
||||
groups="unifi_integration.group_udm_pro_user,unifi_integration.group_udm_pro_manager"/>
|
||||
|
||||
<!-- UDM Pro Main Submenu: Configurations -->
|
||||
<menuitem id="menu_udm_pro_main"
|
||||
name="Configurations"
|
||||
parent="menu_udm_pro_root"
|
||||
sequence="10"/>
|
||||
sequence="10"
|
||||
groups="unifi_integration.group_udm_pro_user,unifi_integration.group_udm_pro_manager"/>
|
||||
|
||||
<!-- Import Configuration Form View -->
|
||||
<record id="view_udm_pro_import_form" model="ir.ui.view">
|
||||
<field name="name">udm.configuration.import.form</field>
|
||||
<field name="model">udm.configuration</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Import UDM Pro Configuration">
|
||||
<group>
|
||||
<group string="Connection Information">
|
||||
<field name="site_id" required="1"/>
|
||||
<field name="host" required="1" placeholder="192.168.1.1 or udm.example.com"/>
|
||||
<field name="port" required="1"/>
|
||||
<field name="username" required="1"/>
|
||||
<field name="password" required="1" password="1"/>
|
||||
</group>
|
||||
<group string="Import Options">
|
||||
<field name="active"/>
|
||||
</group>
|
||||
</group>
|
||||
<footer>
|
||||
<button string="Import" name="action_import_configuration" type="object" class="btn-primary"/>
|
||||
<button string="Cancel" class="btn-secondary" special="cancel"/>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Import Configuration Action -->
|
||||
<record id="action_udm_configuration_import" model="ir.actions.act_window">
|
||||
<field name="name">Import Configuration</field>
|
||||
<field name="res_model">udm.configuration</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="view_id" ref="view_udm_pro_import_form"/>
|
||||
<field name="target">new</field>
|
||||
<field name="context">{'default_active': True}</field>
|
||||
</record>
|
||||
|
||||
<!-- 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"
|
||||
action="action_udm_configuration_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>
|
||||
groups="unifi_integration.group_udm_pro_manager"/>
|
||||
|
||||
<menuitem id="menu_udm_configuration"
|
||||
name="Configurations"
|
||||
parent="menu_udm_pro_main"
|
||||
action="action_udm_configuration"
|
||||
sequence="10"/>
|
||||
action="unifi_integration.action_udm_configuration"
|
||||
sequence="10"
|
||||
groups="unifi_integration.group_udm_pro_user,unifi_integration.group_udm_pro_manager"/>
|
||||
|
||||
<!-- Network Components Menu -->
|
||||
<menuitem id="menu_udm_pro_network"
|
||||
name="Network"
|
||||
parent="menu_udm_pro_root"
|
||||
sequence="20"/>
|
||||
sequence="20"
|
||||
groups="unifi_integration.group_udm_pro_user,unifi_integration.group_udm_pro_manager"/>
|
||||
|
||||
<!-- 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>
|
||||
<field name="view_mode">list,form</field>
|
||||
<field name="context">{'search_default_group_by_config_id': 1, 'search_default_active': 1}</field>
|
||||
</record>
|
||||
|
||||
<menuitem id="menu_udm_network"
|
||||
name="Networks"
|
||||
parent="menu_udm_pro_network"
|
||||
action="action_udm_network"
|
||||
sequence="10"/>
|
||||
sequence="10"
|
||||
groups="unifi_integration.group_udm_pro_user,unifi_integration.group_udm_pro_manager"/>
|
||||
|
||||
<!-- 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>
|
||||
<field name="view_mode">list,form</field>
|
||||
<field name="context">{'search_default_group_by_config_id': 1, 'search_default_active': 1}</field>
|
||||
</record>
|
||||
|
||||
<menuitem id="menu_udm_vlan"
|
||||
name="VLANs"
|
||||
parent="menu_udm_pro_network"
|
||||
action="action_udm_vlan"
|
||||
sequence="20"/>
|
||||
sequence="20"
|
||||
groups="unifi_integration.group_udm_pro_user,unifi_integration.group_udm_pro_manager"/>
|
||||
|
||||
<!-- Devices Menu -->
|
||||
<menuitem id="menu_udm_pro_devices"
|
||||
name="Devices"
|
||||
parent="menu_udm_pro_root"
|
||||
sequence="30"/>
|
||||
sequence="30"
|
||||
groups="unifi_integration.group_udm_pro_user,unifi_integration.group_udm_pro_manager"/>
|
||||
|
||||
<!-- 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>
|
||||
<field name="view_mode">list,form</field>
|
||||
<field name="context">{'search_default_group_by_config_id': 1, 'search_default_active': 1}</field>
|
||||
</record>
|
||||
|
||||
<menuitem id="menu_udm_device"
|
||||
name="Devices"
|
||||
parent="menu_udm_pro_devices"
|
||||
action="action_udm_device"
|
||||
sequence="10"/>
|
||||
sequence="10"
|
||||
groups="unifi_integration.group_udm_pro_user,unifi_integration.group_udm_pro_manager"/>
|
||||
|
||||
<!-- Security Menu -->
|
||||
<menuitem id="menu_udm_pro_security"
|
||||
name="Security"
|
||||
parent="menu_udm_pro_root"
|
||||
sequence="40"/>
|
||||
sequence="40"
|
||||
groups="unifi_integration.group_udm_pro_user,unifi_integration.group_udm_pro_manager"/>
|
||||
|
||||
<!-- 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>
|
||||
<field name="view_mode">list,form</field>
|
||||
<field name="context">{'search_default_group_by_config_id': 1, 'search_default_active': 1}</field>
|
||||
</record>
|
||||
|
||||
<menuitem id="menu_udm_user"
|
||||
name="Users"
|
||||
parent="menu_udm_pro_security"
|
||||
action="action_udm_user"
|
||||
sequence="10"/>
|
||||
sequence="10"
|
||||
groups="unifi_integration.group_udm_pro_user,unifi_integration.group_udm_pro_manager"/>
|
||||
|
||||
<!-- 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>
|
||||
<field name="view_mode">list,form</field>
|
||||
<field name="context">{'search_default_group_by_config_id': 1, 'search_default_active': 1}</field>
|
||||
</record>
|
||||
|
||||
<menuitem id="menu_udm_firewall_rule"
|
||||
name="Firewall Rules"
|
||||
parent="menu_udm_pro_security"
|
||||
action="action_udm_firewall_rule"
|
||||
sequence="20"/>
|
||||
sequence="20"
|
||||
groups="unifi_integration.group_udm_pro_user,unifi_integration.group_udm_pro_manager"/>
|
||||
|
||||
<!-- Dashboard Menu -->
|
||||
<menuitem id="menu_udm_pro_dashboard"
|
||||
name="Dashboard"
|
||||
parent="menu_udm_pro_root"
|
||||
sequence="5"
|
||||
groups="unifi_integration.group_udm_pro_user,unifi_integration.group_udm_pro_manager"/>
|
||||
|
||||
<!-- Dashboard Metrics Menu 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">list,form</field>
|
||||
<field name="context">{}</field>
|
||||
</record>
|
||||
|
||||
<menuitem id="menu_udm_dashboard_metric"
|
||||
name="Metrics"
|
||||
parent="menu_udm_pro_dashboard"
|
||||
action="action_udm_dashboard_metric"
|
||||
sequence="10"
|
||||
groups="unifi_integration.group_udm_pro_user,unifi_integration.group_udm_pro_manager"/>
|
||||
|
||||
<!-- Dashboard Stats Menu Action -->
|
||||
<record id="action_udm_dashboard_stat" model="ir.actions.act_window">
|
||||
<field name="name">Dashboard Statistics</field>
|
||||
<field name="res_model">udm.dashboard.stat</field>
|
||||
<field name="view_mode">list,form</field>
|
||||
<field name="context">{}</field>
|
||||
</record>
|
||||
|
||||
<menuitem id="menu_udm_dashboard_stat"
|
||||
name="Statistics"
|
||||
parent="menu_udm_pro_dashboard"
|
||||
action="action_udm_dashboard_stat"
|
||||
sequence="20"
|
||||
groups="unifi_integration.group_udm_pro_user,unifi_integration.group_udm_pro_manager"/>
|
||||
|
||||
<!-- Sites Menu Action -->
|
||||
<record id="action_udm_site" model="ir.actions.act_window">
|
||||
<field name="name">Sites</field>
|
||||
<field name="res_model">udm.site</field>
|
||||
<field name="view_mode">list,form</field>
|
||||
<field name="context">{}</field>
|
||||
</record>
|
||||
|
||||
<menuitem id="menu_udm_site"
|
||||
name="Sites"
|
||||
parent="menu_udm_pro_network"
|
||||
action="action_udm_site"
|
||||
sequence="5"
|
||||
groups="unifi_integration.group_udm_pro_user,unifi_integration.group_udm_pro_manager"/>
|
||||
|
||||
<!-- Settings Menu -->
|
||||
<menuitem id="menu_udm_pro_settings"
|
||||
name="Settings"
|
||||
parent="menu_udm_pro_root"
|
||||
sequence="50"/>
|
||||
sequence="50"
|
||||
groups="unifi_integration.group_udm_pro_manager"/>
|
||||
|
||||
<!-- 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>
|
||||
<field name="view_mode">list,form</field>
|
||||
</record>
|
||||
|
||||
<menuitem id="menu_udm_system_info"
|
||||
name="System Info"
|
||||
parent="menu_udm_pro_settings"
|
||||
action="action_udm_system_info"
|
||||
sequence="10"/>
|
||||
sequence="10"
|
||||
groups="unifi_integration.group_udm_pro_user,unifi_integration.group_udm_pro_manager"/>
|
||||
|
||||
<!-- UDM Settings Menu Action -->
|
||||
<record id="action_udm_settings" model="ir.actions.act_window">
|
||||
<field name="name">UDM Settings</field>
|
||||
<field name="name">UniFi Settings</field>
|
||||
<field name="res_model">udm.settings</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="view_mode">list,form</field>
|
||||
</record>
|
||||
|
||||
<menuitem id="menu_udm_settings"
|
||||
name="UDM Settings"
|
||||
name="UniFi Settings"
|
||||
parent="menu_udm_pro_settings"
|
||||
action="action_udm_settings"
|
||||
sequence="20"/>
|
||||
sequence="20"
|
||||
groups="unifi_integration.group_udm_pro_user,unifi_integration.group_udm_pro_manager"/>
|
||||
|
||||
<!-- Import URL Action -->
|
||||
<record id="action_udm_pro_import" model="ir.actions.act_url">
|
||||
<field name="name">Import UDM Pro Configuration</field>
|
||||
<field name="name">Import UniFi Configuration</field>
|
||||
<field name="url">/udm_pro/import_config</field>
|
||||
<field name="target">self</field>
|
||||
</record>
|
||||
|
|
|
|||
158
unifi_integration/views/udm_network_config_views.xml
Normal file
158
unifi_integration/views/udm_network_config_views.xml
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<!-- Port Forward Views -->
|
||||
<record id="view_udm_port_forward_list" model="ir.ui.view">
|
||||
<field name="name">udm.port.forward.list</field>
|
||||
<field name="model">udm.port.forward</field>
|
||||
<field name="arch" type="xml">
|
||||
<list>
|
||||
<field name="name"/>
|
||||
<field name="enabled"/>
|
||||
<field name="src_port"/>
|
||||
<field name="dst_port"/>
|
||||
<field name="protocol"/>
|
||||
<field name="dst_address"/>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_udm_port_forward_form" model="ir.ui.view">
|
||||
<field name="name">udm.port.forward.form</field>
|
||||
<field name="model">udm.port.forward</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Port Forward">
|
||||
<sheet>
|
||||
<group>
|
||||
<group string="Identification">
|
||||
<field name="name"/>
|
||||
<field name="enabled"/>
|
||||
<field name="protocol"/>
|
||||
</group>
|
||||
<group string="Redirection">
|
||||
<field name="src_port" string="Port source"/>
|
||||
<field name="dst_port" string="Port de destination"/>
|
||||
<field name="dst_address" string="Adresse de destination"/>
|
||||
</group>
|
||||
</group>
|
||||
<group string="Technical">
|
||||
<field name="raw_data" readonly="1"/>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- DNS Configuration Views -->
|
||||
<record id="view_udm_dns_config_list" model="ir.ui.view">
|
||||
<field name="name">udm.dns.config.list</field>
|
||||
<field name="model">udm.dns.config</field>
|
||||
<field name="arch" type="xml">
|
||||
<list>
|
||||
<field name="enabled"/>
|
||||
<field name="filters_enabled"/>
|
||||
<field name="custom_dns"/>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_udm_dns_config_form" model="ir.ui.view">
|
||||
<field name="name">udm.dns.config.form</field>
|
||||
<field name="model">udm.dns.config</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="DNS Configuration">
|
||||
<sheet>
|
||||
<group>
|
||||
<group string="État">
|
||||
<field name="enabled" string="DNS activé"/>
|
||||
<field name="filters_enabled" string="Filtrage activé"/>
|
||||
</group>
|
||||
<group string="Configuration">
|
||||
<field name="custom_dns" string="Serveurs DNS personnalisés"/>
|
||||
</group>
|
||||
</group>
|
||||
<group string="Technical">
|
||||
<field name="raw_data" readonly="1"/>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Routing Configuration Views -->
|
||||
<record id="view_udm_routing_config_list" model="ir.ui.view">
|
||||
<field name="name">udm.routing.config.list</field>
|
||||
<field name="model">udm.routing.config</field>
|
||||
<field name="arch" type="xml">
|
||||
<list>
|
||||
<field name="ospf_enabled"/>
|
||||
<field name="static_routes"/>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_udm_routing_config_form" model="ir.ui.view">
|
||||
<field name="name">udm.routing.config.form</field>
|
||||
<field name="model">udm.routing.config</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Routing Configuration">
|
||||
<sheet>
|
||||
<group>
|
||||
<group string="Protocoles de routage">
|
||||
<field name="ospf_enabled" string="OSPF activé"/>
|
||||
</group>
|
||||
<group string="Routes">
|
||||
<field name="static_routes" string="Routes statiques"/>
|
||||
</group>
|
||||
</group>
|
||||
<group string="Technical">
|
||||
<field name="raw_data" readonly="1"/>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Actions -->
|
||||
<record id="action_udm_port_forward" model="ir.actions.act_window">
|
||||
<field name="name">Port Forwards</field>
|
||||
<field name="res_model">udm.port.forward</field>
|
||||
<field name="view_mode">list,form</field>
|
||||
<field name="view_id" ref="view_udm_port_forward_list"/>
|
||||
</record>
|
||||
|
||||
<record id="action_udm_dns_config" model="ir.actions.act_window">
|
||||
<field name="name">DNS Configuration</field>
|
||||
<field name="res_model">udm.dns.config</field>
|
||||
<field name="view_mode">list,form</field>
|
||||
<field name="view_id" ref="view_udm_dns_config_list"/>
|
||||
</record>
|
||||
|
||||
<record id="action_udm_routing_config" model="ir.actions.act_window">
|
||||
<field name="name">Routing Configuration</field>
|
||||
<field name="res_model">udm.routing.config</field>
|
||||
<field name="view_mode">list,form</field>
|
||||
<field name="view_id" ref="view_udm_routing_config_list"/>
|
||||
</record>
|
||||
|
||||
<!-- Menu Items -->
|
||||
<menuitem id="menu_udm_port_forward"
|
||||
name="Port Forwards"
|
||||
parent="menu_udm_pro_security"
|
||||
action="action_udm_port_forward"
|
||||
sequence="30"
|
||||
groups="unifi_integration.group_udm_pro_user,unifi_integration.group_udm_pro_manager"/>
|
||||
|
||||
<menuitem id="menu_udm_dns_config"
|
||||
name="DNS Configuration"
|
||||
parent="menu_udm_pro_security"
|
||||
action="action_udm_dns_config"
|
||||
sequence="40"
|
||||
groups="unifi_integration.group_udm_pro_user,unifi_integration.group_udm_pro_manager"/>
|
||||
|
||||
<menuitem id="menu_udm_routing_config"
|
||||
name="Routing Configuration"
|
||||
parent="menu_udm_pro_security"
|
||||
action="action_udm_routing_config"
|
||||
sequence="50"
|
||||
groups="unifi_integration.group_udm_pro_user,unifi_integration.group_udm_pro_manager"/>
|
||||
</odoo>
|
||||
|
|
@ -1,9 +1,28 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<!-- Network List View -->
|
||||
<record id="view_udm_network_list" model="ir.ui.view">
|
||||
<field name="name">udm.network.list</field>
|
||||
<field name="model">udm.network</field>
|
||||
<field name="type">list</field>
|
||||
<field name="arch" type="xml">
|
||||
<list string="UDM Pro Networks">
|
||||
<field name="name"/>
|
||||
<field name="purpose"/>
|
||||
<field name="subnet"/>
|
||||
<field name="vlan_id_number" optional="show"/>
|
||||
<field name="vlan_id" optional="show"/>
|
||||
<field name="dhcp_enabled"/>
|
||||
<field name="domain_name" optional="show"/>
|
||||
</list>
|
||||
</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="type">form</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="UDM Pro Network">
|
||||
<sheet>
|
||||
|
|
@ -28,11 +47,30 @@
|
|||
</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"/>
|
||||
<field name="raw_data" widget="ace" options="{'mode': 'json'}" help="Raw JSON data of the configuration"/>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
<!-- Network Search View -->
|
||||
<record id="view_udm_network_search" model="ir.ui.view">
|
||||
<field name="name">udm.network.search</field>
|
||||
<field name="model">udm.network</field>
|
||||
<field name="type">search</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Search Networks">
|
||||
<field name="name"/>
|
||||
<field name="purpose"/>
|
||||
<field name="subnet"/>
|
||||
<field name="vlan_id"/>
|
||||
<filter string="DHCP Enabled" name="dhcp_enabled" domain="[('dhcp_enabled', '=', True)]"/>
|
||||
<group expand="0" string="Group By">
|
||||
<filter string="Purpose" name="group_by_purpose" domain="[]" context="{'group_by': 'purpose'}"/>
|
||||
<filter string="VLAN" name="group_by_vlan" domain="[]" context="{'group_by': 'vlan_id'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
|
|
|
|||
222
unifi_integration/views/udm_pro_docs_templates.xml
Normal file
222
unifi_integration/views/udm_pro_docs_templates.xml
Normal file
|
|
@ -0,0 +1,222 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<!-- Advanced Options Form Template -->
|
||||
<template id="advanced_options_form">
|
||||
<t t-call="web.layout">
|
||||
<t t-set="title">Advanced Options</t>
|
||||
<div class="container">
|
||||
<h1>Advanced UDM Pro Options</h1>
|
||||
<form method="post" t-att-action="'/udm_pro/advanced_options'">
|
||||
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
|
||||
<div class="form-group">
|
||||
<label for="site">Site</label>
|
||||
<input type="text" class="form-control" name="site" t-att-value="default_site"/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="custom-control custom-checkbox">
|
||||
<input type="checkbox" class="custom-control-input" id="fixed_only" name="fixed_only" t-att-checked="fixed_only"/>
|
||||
<label class="custom-control-label" for="fixed_only">Fixed IP Only</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="custom-control custom-checkbox">
|
||||
<input type="checkbox" class="custom-control-input" id="lowercase_hostnames" name="lowercase_hostnames" t-att-checked="lowercase_hostnames"/>
|
||||
<label class="custom-control-label" for="lowercase_hostnames">Lowercase Hostnames</label>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Apply</button>
|
||||
</form>
|
||||
</div>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
<!-- Restart Device Form Template -->
|
||||
<template id="restart_device_form">
|
||||
<t t-call="web.layout">
|
||||
<t t-set="title">Restart Device</t>
|
||||
<div class="container">
|
||||
<h1>Restart UDM Pro Device</h1>
|
||||
<form method="post" t-att-action="'/udm_pro/restart_device'">
|
||||
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
|
||||
<div class="form-group">
|
||||
<label for="config_id">Configuration</label>
|
||||
<select class="form-control" name="config_id" required="required">
|
||||
<option value="">Select a Configuration</option>
|
||||
<t t-foreach="configs" t-as="config">
|
||||
<option t-att-value="config.id">
|
||||
<t t-esc="config.name"/>
|
||||
</option>
|
||||
</t>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="mac_address">Device MAC Address</label>
|
||||
<input type="text" class="form-control" name="mac_address" required="required" placeholder="00:11:22:33:44:55"/>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Restart Device</button>
|
||||
</form>
|
||||
</div>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
<!-- Generate Hosts Form Template -->
|
||||
<template id="generate_hosts_form">
|
||||
<t t-call="web.layout">
|
||||
<t t-set="title">Generate Hosts File</t>
|
||||
<div class="container">
|
||||
<h1>Generate Hosts File from UDM Pro</h1>
|
||||
<form method="post" t-att-action="'/udm_pro/generate_hosts'">
|
||||
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
|
||||
<div class="form-group">
|
||||
<label for="config_id">Configuration</label>
|
||||
<select class="form-control" name="config_id" required="required">
|
||||
<option value="">Select a Configuration</option>
|
||||
<t t-foreach="configs" t-as="config">
|
||||
<option t-att-value="config.id">
|
||||
<t t-esc="config.name"/>
|
||||
</option>
|
||||
</t>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="custom-control custom-checkbox">
|
||||
<input type="checkbox" class="custom-control-input" id="fixed_only" name="fixed_only" t-att-checked="fixed_only"/>
|
||||
<label class="custom-control-label" for="fixed_only">Fixed IP Only</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="custom-control custom-checkbox">
|
||||
<input type="checkbox" class="custom-control-input" id="lowercase_hostnames" name="lowercase_hostnames" t-att-checked="lowercase_hostnames"/>
|
||||
<label class="custom-control-label" for="lowercase_hostnames">Lowercase Hostnames</label>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Generate Hosts File</button>
|
||||
</form>
|
||||
</div>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
<!-- Network Clients Form Template -->
|
||||
<template id="network_clients_form">
|
||||
<t t-call="web.layout">
|
||||
<t t-set="title">Network Clients</t>
|
||||
<div class="container">
|
||||
<h1>UDM Pro Network Clients</h1>
|
||||
<form method="post" t-att-action="'/udm_pro/get_network_clients'">
|
||||
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
|
||||
<div class="form-group">
|
||||
<label for="config_id">Configuration</label>
|
||||
<select class="form-control" name="config_id" required="required">
|
||||
<option value="">Select a Configuration</option>
|
||||
<t t-foreach="configs" t-as="config">
|
||||
<option t-att-value="config.id">
|
||||
<t t-esc="config.name"/>
|
||||
</option>
|
||||
</t>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="custom-control custom-checkbox">
|
||||
<input type="checkbox" class="custom-control-input" id="fixed_only" name="fixed_only" t-att-checked="fixed_only"/>
|
||||
<label class="custom-control-label" for="fixed_only">Fixed IP Only</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="custom-control custom-checkbox">
|
||||
<input type="checkbox" class="custom-control-input" id="lowercase_hostnames" name="lowercase_hostnames" t-att-checked="lowercase_hostnames"/>
|
||||
<label class="custom-control-label" for="lowercase_hostnames">Lowercase Hostnames</label>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Get Network Clients</button>
|
||||
</form>
|
||||
</div>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
<!-- Network Clients Result Template -->
|
||||
<template id="network_clients_result">
|
||||
<t t-call="web.layout">
|
||||
<t t-set="title">Network Clients Result</t>
|
||||
<div class="container">
|
||||
<h1>UDM Pro Network Clients</h1>
|
||||
<div t-if="error_message" class="alert alert-danger" role="alert">
|
||||
<t t-esc="error_message"/>
|
||||
</div>
|
||||
<div t-if="clients" class="table-responsive">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Hostname</th>
|
||||
<th>IP Address</th>
|
||||
<th>MAC Address</th>
|
||||
<th>Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<t t-foreach="clients" t-as="client">
|
||||
<tr>
|
||||
<td><t t-esc="client.get('hostname', '')"/></td>
|
||||
<td><t t-esc="client.get('ip', '')"/></td>
|
||||
<td><t t-esc="client.get('mac', '')"/></td>
|
||||
<td><t t-esc="client.get('status', '')"/></td>
|
||||
</tr>
|
||||
</t>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
<!-- Import Config Form Template -->
|
||||
<template id="import_config_form">
|
||||
<t t-call="web.layout">
|
||||
<t t-set="title">Import Configuration</t>
|
||||
<div class="container">
|
||||
<h1>Import UDM Pro Configuration</h1>
|
||||
<form method="post" t-att-action="'/udm_pro/import_config'">
|
||||
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
|
||||
<div class="form-group">
|
||||
<label for="host">Host</label>
|
||||
<input type="text" class="form-control" name="host" required="required" t-att-value="host or ''"/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="username">Username</label>
|
||||
<input type="text" class="form-control" name="username" required="required" t-att-value="username or ''"/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password">Password</label>
|
||||
<input type="password" class="form-control" name="password" required="required"/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="port">Port</label>
|
||||
<input type="number" class="form-control" name="port" value="443"/>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Import Configuration</button>
|
||||
</form>
|
||||
</div>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
<!-- Restart Success Template -->
|
||||
<template id="restart_success">
|
||||
<t t-call="web.layout">
|
||||
<t t-set="title">Restart Success</t>
|
||||
<div class="container">
|
||||
<h1>Device Restart Successful</h1>
|
||||
<p>The device has been successfully restarted.</p>
|
||||
</div>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
<!-- Access Denied Template -->
|
||||
<template id="access_denied">
|
||||
<t t-call="web.layout">
|
||||
<t t-set="title">Access Denied</t>
|
||||
<div class="container">
|
||||
<h1>Access Denied</h1>
|
||||
<p>You do not have permission to access this page.</p>
|
||||
</div>
|
||||
</t>
|
||||
</template>
|
||||
</odoo>
|
||||
|
|
@ -1,9 +1,25 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<!-- Settings List View -->
|
||||
<record id="view_udm_settings_list" model="ir.ui.view">
|
||||
<field name="name">udm.settings.list</field>
|
||||
<field name="model">udm.settings</field>
|
||||
<field name="type">list</field>
|
||||
<field name="arch" type="xml">
|
||||
<list string="UDM Pro Settings">
|
||||
<field name="timezone"/>
|
||||
<field name="ntp_servers"/>
|
||||
<field name="dns_servers"/>
|
||||
<field name="config_id" optional="show"/>
|
||||
</list>
|
||||
</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="type">form</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="UDM Pro Settings">
|
||||
<sheet>
|
||||
|
|
@ -19,11 +35,28 @@
|
|||
</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"/>
|
||||
<field name="raw_data" widget="ace" options="{'mode': 'json'}" help="Raw JSON data of the configuration"/>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
<!-- Settings Search View -->
|
||||
<record id="view_udm_settings_search" model="ir.ui.view">
|
||||
<field name="name">udm.settings.search</field>
|
||||
<field name="model">udm.settings</field>
|
||||
<field name="type">search</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Search Settings">
|
||||
<field name="timezone"/>
|
||||
<field name="ntp_servers"/>
|
||||
<field name="dns_servers"/>
|
||||
<group expand="0" string="Group By">
|
||||
<filter string="Timezone" name="group_by_timezone" domain="[]" context="{'group_by': 'timezone'}"/>
|
||||
<filter string="Configuration" name="group_by_config" domain="[]" context="{'group_by': 'config_id'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
|
|
|
|||
|
|
@ -1,16 +1,58 @@
|
|||
<?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>
|
||||
<!-- Import Site Action -->
|
||||
<record id="action_import_udm_site" model="ir.actions.act_window">
|
||||
<field name="name">Import UDM Pro Site</field>
|
||||
<field name="res_model">udm.site.import.wizard</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="target">new</field>
|
||||
</record>
|
||||
|
||||
<!-- Import Site Wizard Form View -->
|
||||
<record id="view_udm_site_import_wizard_form" model="ir.ui.view">
|
||||
<field name="name">udm.site.import.wizard.form</field>
|
||||
<field name="model">udm.site.import.wizard</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="UDM Pro Sites">
|
||||
<form string="Import UDM Pro Site">
|
||||
<sheet>
|
||||
<group>
|
||||
<group>
|
||||
<field name="name" placeholder="e.g., Bureau Principal"/>
|
||||
<field name="site_id"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="host" placeholder="e.g., 192.168.1.1"/>
|
||||
<field name="port"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="username" placeholder="Admin username"/>
|
||||
<field name="password" password="True" placeholder="Admin password"/>
|
||||
</group>
|
||||
</group>
|
||||
</sheet>
|
||||
<footer>
|
||||
<button name="action_import_site" string="Import" type="object" class="btn-primary"/>
|
||||
<button string="Cancel" class="btn-secondary" special="cancel"/>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- UDM Site List View -->
|
||||
<record id="view_udm_site_list" model="ir.ui.view">
|
||||
<field name="name">udm.site.list</field>
|
||||
<field name="model">udm.site</field>
|
||||
<field name="type">list</field>
|
||||
<field name="arch" type="xml">
|
||||
<list string="UDM Pro Sites">
|
||||
<header>
|
||||
<button name="%(action_import_udm_site)d" string="Import UDM Pro Site" type="action" class="btn-primary"/>
|
||||
</header>
|
||||
<field name="name"/>
|
||||
<field name="site_id"/>
|
||||
<field name="configuration_count"/>
|
||||
<field name="active"/>
|
||||
</tree>
|
||||
<field name="create_date" optional="show"/>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
|
@ -18,6 +60,7 @@
|
|||
<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="type">form</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="UDM Pro Site">
|
||||
<header>
|
||||
|
|
@ -27,7 +70,9 @@
|
|||
<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"/>
|
||||
<div class="o_stat_info">
|
||||
<span class="o_stat_text">Configurations</span>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
<div class="oe_title">
|
||||
|
|
@ -42,7 +87,7 @@
|
|||
</group>
|
||||
<group>
|
||||
<field name="create_date" readonly="1"/>
|
||||
<field name="last_refresh" readonly="1"/>
|
||||
|
||||
</group>
|
||||
</group>
|
||||
<notebook>
|
||||
|
|
@ -58,14 +103,25 @@
|
|||
<page string="Dashboard Metrics" name="dashboard_metrics">
|
||||
<field name="dashboard_ids">
|
||||
<tree>
|
||||
<field name="name"/>
|
||||
<field name="value"/>
|
||||
<field name="unit"/>
|
||||
<field name="metric_type"/>
|
||||
<field name="formatted_value"/>
|
||||
<field name="status"/>
|
||||
<field name="last_update"/>
|
||||
</tree>
|
||||
</field>
|
||||
</page>
|
||||
<page string="Statistics" name="statistics">
|
||||
<field name="statistic_ids">
|
||||
<tree>
|
||||
<field name="date"/>
|
||||
<field name="stat_type"/>
|
||||
<field name="value"/>
|
||||
<field name="unit"/>
|
||||
<field name="is_aggregate"/>
|
||||
<field name="aggregate_type" invisible="not is_aggregate"/>
|
||||
</tree>
|
||||
</field>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
</form>
|
||||
|
|
@ -78,7 +134,7 @@
|
|||
<field name="model">udm.site</field>
|
||||
<field name="mode">primary</field>
|
||||
<field name="arch" type="xml">
|
||||
<dashboard string="UDM Pro Dashboard" sample="1">
|
||||
<form string="UDM Pro Dashboard" create="false" edit="false" delete="false">
|
||||
<div class="d-flex flex-column">
|
||||
<!-- Dashboard Header -->
|
||||
<div class="d-flex justify-content-between align-items-center pb-2 mb-3 border-bottom">
|
||||
|
|
@ -103,16 +159,15 @@
|
|||
<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>
|
||||
<t t-set="cpu_metric" t-value="dashboard_ids.filtered(lambda m: m.metric_type == 'cpu_usage')"/>
|
||||
<h3 class="mb-0" t-att-class="cpu_metric and cpu_metric.status == 'warning' and 'text-warning' or cpu_metric.status == 'critical' and 'text-danger' or ''">
|
||||
<t t-esc="cpu_metric and cpu_metric.formatted_value or '0%'"/>
|
||||
</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">
|
||||
t-att-style="'width: ' + (cpu_metric and cpu_metric.current_value or '0') + '%;'"
|
||||
t-att-class="cpu_metric and cpu_metric.status == 'warning' and 'bg-warning' or cpu_metric.status == 'critical' and 'bg-danger' or 'bg-success'"
|
||||
t-att-aria-valuenow="cpu_metric and cpu_metric.current_value or 0" aria-valuemin="0" aria-valuemax="100">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -127,16 +182,15 @@
|
|||
<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>
|
||||
<t t-set="memory_metric" t-value="dashboard_ids.filtered(lambda m: m.metric_type == 'memory_usage')"/>
|
||||
<h3 class="mb-0" t-att-class="memory_metric and memory_metric.status == 'warning' and 'text-warning' or memory_metric.status == 'critical' and 'text-danger' or ''">
|
||||
<t t-esc="memory_metric and memory_metric.formatted_value or '0%'"/>
|
||||
</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">
|
||||
t-att-style="'width: ' + (memory_metric and memory_metric.current_value or '0') + '%;'"
|
||||
t-att-class="memory_metric and memory_metric.status == 'warning' and 'bg-warning' or memory_metric.status == 'critical' and 'bg-danger' or 'bg-success'"
|
||||
t-att-aria-valuenow="memory_metric and memory_metric.current_value or 0" aria-valuemin="0" aria-valuemax="100">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -151,10 +205,9 @@
|
|||
<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>
|
||||
<t t-set="bandwidth_metric" t-value="dashboard_ids.filtered(lambda m: m.metric_type == 'bandwidth_usage')"/>
|
||||
<h3 class="mb-0" t-att-class="bandwidth_metric and bandwidth_metric.status == 'warning' and 'text-warning' or bandwidth_metric.status == 'critical' and 'text-danger' or ''">
|
||||
<t t-esc="bandwidth_metric and bandwidth_metric.formatted_value or '0 Mbps'"/>
|
||||
</h3>
|
||||
<div class="text-muted">Current Network Load</div>
|
||||
</div>
|
||||
|
|
@ -235,6 +288,7 @@
|
|||
<!-- UDM Site Search View -->
|
||||
<record id="view_udm_site_search" model="ir.ui.view">
|
||||
<field name="name">udm.site.search</field>
|
||||
<field name="type">search</field>
|
||||
<field name="model">udm.site</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Search UDM Pro Sites">
|
||||
|
|
|
|||
|
|
@ -1,9 +1,27 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<!-- System Info List View -->
|
||||
<record id="view_udm_system_info_list" model="ir.ui.view">
|
||||
<field name="name">udm.system.info.list</field>
|
||||
<field name="model">udm.system.info</field>
|
||||
<field name="type">list</field>
|
||||
<field name="arch" type="xml">
|
||||
<list string="UDM Pro System Information">
|
||||
<field name="hostname"/>
|
||||
<field name="version"/>
|
||||
<field name="model"/>
|
||||
<field name="serial" optional="show"/>
|
||||
<field name="mac_address" optional="show"/>
|
||||
<field name="uptime_human"/>
|
||||
</list>
|
||||
</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="type">form</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="UDM Pro System Information">
|
||||
<sheet>
|
||||
|
|
@ -23,11 +41,31 @@
|
|||
</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"/>
|
||||
<field name="raw_data" widget="ace" options="{'mode': 'json'}" help="Raw JSON data of the configuration"/>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
<!-- System Info Search View -->
|
||||
<record id="view_udm_system_info_search" model="ir.ui.view">
|
||||
<field name="name">udm.system.info.search</field>
|
||||
<field name="model">udm.system.info</field>
|
||||
<field name="type">search</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Search System Information">
|
||||
<field name="hostname"/>
|
||||
<field name="version"/>
|
||||
<field name="model"/>
|
||||
<field name="serial"/>
|
||||
<field name="mac_address"/>
|
||||
<group expand="0" string="Group By">
|
||||
<filter string="Model" name="group_by_model" domain="[]" context="{'group_by': 'model'}"/>
|
||||
<filter string="Version" name="group_by_version" domain="[]" context="{'group_by': 'version'}"/>
|
||||
<filter string="Configuration" name="group_by_config" domain="[]" context="{'group_by': 'config_id'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,27 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<!-- User List View -->
|
||||
<record id="view_udm_user_list" model="ir.ui.view">
|
||||
<field name="name">udm.user.list</field>
|
||||
<field name="model">udm.user</field>
|
||||
<field name="type">list</field>
|
||||
<field name="arch" type="xml">
|
||||
<list string="UDM Pro Users">
|
||||
<field name="name"/>
|
||||
<field name="email"/>
|
||||
<field name="role"/>
|
||||
<field name="enabled"/>
|
||||
<field name="is_admin"/>
|
||||
<field name="config_id" optional="show"/>
|
||||
</list>
|
||||
</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="type">form</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="UDM Pro User">
|
||||
<sheet>
|
||||
|
|
@ -25,11 +43,30 @@
|
|||
</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"/>
|
||||
<field name="raw_data" widget="ace" options="{'mode': 'json'}" help="Raw JSON data of the configuration"/>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
<!-- User Search View -->
|
||||
<record id="view_udm_user_search" model="ir.ui.view">
|
||||
<field name="name">udm.user.search</field>
|
||||
<field name="model">udm.user</field>
|
||||
<field name="type">search</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Search Users">
|
||||
<field name="name"/>
|
||||
<field name="email"/>
|
||||
<field name="role"/>
|
||||
<filter string="Enabled" name="enabled" domain="[('enabled', '=', True)]"/>
|
||||
<filter string="Administrators" name="is_admin" domain="[('is_admin', '=', True)]"/>
|
||||
<group expand="0" string="Group By">
|
||||
<filter string="Role" name="group_by_role" domain="[]" context="{'group_by': 'role'}"/>
|
||||
<filter string="Configuration" name="group_by_config" domain="[]" context="{'group_by': 'config_id'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,26 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<!-- VLAN List View -->
|
||||
<record id="view_udm_vlan_list" model="ir.ui.view">
|
||||
<field name="name">udm.vlan.list</field>
|
||||
<field name="model">udm.vlan</field>
|
||||
<field name="type">list</field>
|
||||
<field name="arch" type="xml">
|
||||
<list string="UDM Pro VLANs">
|
||||
<field name="vlan_id"/>
|
||||
<field name="name"/>
|
||||
<field name="enabled"/>
|
||||
<field name="config_id" optional="show"/>
|
||||
<field name="network_count" optional="show"/>
|
||||
</list>
|
||||
</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="type">form</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="UDM Pro VLAN">
|
||||
<sheet>
|
||||
|
|
@ -21,19 +38,35 @@
|
|||
<notebook>
|
||||
<page string="Networks" name="networks">
|
||||
<field name="network_ids" nolabel="1">
|
||||
<tree>
|
||||
<list>
|
||||
<field name="name"/>
|
||||
<field name="purpose"/>
|
||||
<field name="subnet"/>
|
||||
</tree>
|
||||
</list>
|
||||
</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"/>
|
||||
<field name="raw_data" widget="ace" options="{'mode': 'json'}" help="Raw JSON data of the configuration"/>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
<!-- VLAN Search View -->
|
||||
<record id="view_udm_vlan_search" model="ir.ui.view">
|
||||
<field name="name">udm.vlan.search</field>
|
||||
<field name="model">udm.vlan</field>
|
||||
<field name="type">search</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Search VLANs">
|
||||
<field name="vlan_id"/>
|
||||
<field name="name"/>
|
||||
<filter string="Enabled" name="enabled" domain="[('enabled', '=', True)]"/>
|
||||
<group expand="0" string="Group By">
|
||||
<filter string="Configuration" name="group_by_config" domain="[]" context="{'group_by': 'config_id'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
|
|
|
|||
4
unifi_integration/wizards/__init__.py
Normal file
4
unifi_integration/wizards/__init__.py
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import udm_site_import_wizard
|
||||
from . import udm_mfa_wizard
|
||||
24
unifi_integration/wizards/udm_mfa_wizard.py
Normal file
24
unifi_integration/wizards/udm_mfa_wizard.py
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from odoo import models, fields, api, _
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
class UdmMfaWizard(models.TransientModel):
|
||||
"""Assistant pour entrer le code MFA"""
|
||||
_name = 'udm.mfa.wizard'
|
||||
_description = 'Assistant Code MFA'
|
||||
|
||||
mfa_code = fields.Char(string='Code MFA', required=True, help="Entrez le code d'authentification reçu par courriel")
|
||||
config_id = fields.Many2one('udm.configuration', string='Configuration')
|
||||
|
||||
def action_validate_mfa(self):
|
||||
"""Valide le code MFA et continue l'importation"""
|
||||
self.ensure_one()
|
||||
if not self.config_id:
|
||||
raise UserError(_("Configuration non trouvée"))
|
||||
|
||||
# Met à jour le code MFA dans la configuration
|
||||
self.config_id.write({'mfa_token': self.mfa_code})
|
||||
|
||||
# Relance l'importation
|
||||
return self.config_id.action_import_configuration()
|
||||
82
unifi_integration/wizards/udm_site_import_wizard.py
Normal file
82
unifi_integration/wizards/udm_site_import_wizard.py
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from odoo import models, fields, api, _
|
||||
from odoo.exceptions import UserError
|
||||
import requests
|
||||
import urllib3
|
||||
from requests.exceptions import RequestException
|
||||
|
||||
class UdmSiteImportWizard(models.TransientModel):
|
||||
"""Wizard to create a new site from a UDM Pro device"""
|
||||
_name = 'udm.site.import.wizard'
|
||||
_description = 'Import UDM Pro Site Wizard'
|
||||
|
||||
name = fields.Char(string='Site Name', required=True)
|
||||
host = fields.Char(string='UDM Pro IP/Hostname', required=True)
|
||||
port = fields.Integer(string='Port', default=443)
|
||||
username = fields.Char(string='Username', required=True)
|
||||
password = fields.Char(string='Password', required=True)
|
||||
site_id = fields.Char(string='Site ID', default='default',
|
||||
help="Site identifier in UniFi (usually 'default' unless configured otherwise)")
|
||||
|
||||
def action_import_site(self):
|
||||
"""Create a new site and configuration from UDM Pro"""
|
||||
self.ensure_one()
|
||||
|
||||
try:
|
||||
# Disable SSL verification warnings - UDM Pro often uses self-signed certs
|
||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||
|
||||
# Test connection to UDM Pro
|
||||
login_url = f'https://{self.host}:{self.port}/api/auth/login'
|
||||
login_data = {
|
||||
'username': self.username,
|
||||
'password': self.password
|
||||
}
|
||||
headers = {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
|
||||
response = requests.post(
|
||||
login_url,
|
||||
json=login_data,
|
||||
headers=headers,
|
||||
verify=False
|
||||
)
|
||||
response.raise_for_status()
|
||||
|
||||
# Create the site
|
||||
site = self.env['udm.site'].create({
|
||||
'name': self.name,
|
||||
'site_id': self.site_id,
|
||||
'active': True
|
||||
})
|
||||
|
||||
# Create the configuration
|
||||
config = self.env['udm.configuration'].create({
|
||||
'site_id': site.id,
|
||||
'host': self.host,
|
||||
'port': self.port,
|
||||
'username': self.username,
|
||||
'password': self.password,
|
||||
'active': True
|
||||
})
|
||||
|
||||
# Import the configuration
|
||||
config.action_import_configuration()
|
||||
|
||||
# Return action to view the new site
|
||||
return {
|
||||
'name': _('Site'),
|
||||
'view_mode': 'form',
|
||||
'res_model': 'udm.site',
|
||||
'res_id': site.id,
|
||||
'type': 'ir.actions.act_window',
|
||||
'target': 'current',
|
||||
}
|
||||
|
||||
except RequestException as e:
|
||||
raise UserError(_('Failed to connect to UDM Pro: %s') % str(e))
|
||||
except Exception as e:
|
||||
raise UserError(_('Failed to create site: %s') % str(e))
|
||||
40
unifi_integration/wizards/udm_site_import_wizard_views.xml
Normal file
40
unifi_integration/wizards/udm_site_import_wizard_views.xml
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<!-- Import Site Action -->
|
||||
<record id="action_import_udm_site" model="ir.actions.act_window">
|
||||
<field name="name">Import UDM Pro Site</field>
|
||||
<field name="res_model">udm.site.import.wizard</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="target">new</field>
|
||||
</record>
|
||||
|
||||
<!-- Import Site Wizard Form View -->
|
||||
<record id="view_udm_site_import_wizard_form" model="ir.ui.view">
|
||||
<field name="name">udm.site.import.wizard.form</field>
|
||||
<field name="model">udm.site.import.wizard</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Import UDM Pro Site">
|
||||
<sheet>
|
||||
<group>
|
||||
<group>
|
||||
<field name="name" placeholder="e.g., Bureau Principal"/>
|
||||
<field name="site_id"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="host" placeholder="e.g., 192.168.1.1"/>
|
||||
<field name="port"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="username" placeholder="Admin username"/>
|
||||
<field name="password" password="True" placeholder="Admin password"/>
|
||||
</group>
|
||||
</group>
|
||||
</sheet>
|
||||
<footer>
|
||||
<button name="action_import_site" string="Import" type="object" class="btn-primary"/>
|
||||
<button string="Cancel" class="btn-secondary" special="cancel"/>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
22
unifi_integration/wizards/views/udm_mfa_wizard_views.xml
Normal file
22
unifi_integration/wizards/views/udm_mfa_wizard_views.xml
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<!-- MFA Wizard Form View -->
|
||||
<record id="view_udm_mfa_wizard_form" model="ir.ui.view">
|
||||
<field name="name">udm.mfa.wizard.form</field>
|
||||
<field name="model">udm.mfa.wizard</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Code d'authentification à deux facteurs">
|
||||
<p>Un code d'authentification a été envoyé à votre adresse courriel.</p>
|
||||
<p>Veuillez entrer ce code ci-dessous pour continuer l'importation.</p>
|
||||
<group>
|
||||
<field name="mfa_code" placeholder="Entrez le code reçu par courriel"/>
|
||||
<field name="config_id" invisible="1"/>
|
||||
</group>
|
||||
<footer>
|
||||
<button string="Valider" name="action_validate_mfa" type="object" class="btn-primary"/>
|
||||
<button string="Annuler" class="btn-secondary" special="cancel"/>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
Loading…
Reference in a new issue