Aucune donnée disponible
" + continue + + try: + # Analyser les données JSON + data = json.loads(record.raw_data) + + # Créer une représentation HTML formatée + html = "| Nom: | {data.get('name', 'N/A')} |
| Type: | {data.get('vpn_type', 'N/A')} |
| Activé: | {'Oui' if data.get('enabled') else 'Non'} |
| Interface: | {data.get('ifname', 'N/A')} |
| But: | {data.get('purpose', 'N/A')} |
| IP Pair: | {data.get('ipsec_peer_ip', 'N/A')} |
| IP Locale: | {data.get('ipsec_local_ip', 'N/A')} |
| Interface: | {data.get('ipsec_interface', 'N/A')} |
| Échange de clés: | {data.get('ipsec_key_exchange', 'N/A')} |
| Profil: | {data.get('ipsec_profile', 'N/A')} |
| PFS: | {'Oui' if data.get('ipsec_pfs') else 'Non'} |
| Routage dynamique: | {'Oui' if data.get('ipsec_dynamic_routing') else 'Non'} |
| Groupe DH: | {data.get('ipsec_ike_dh_group', 'N/A')} |
| Chiffrement: | {data.get('ipsec_ike_encryption', 'N/A')} |
| Hash: | {data.get('ipsec_ike_hash', 'N/A')} |
| Durée de vie (s): | {data.get('ipsec_ike_lifetime', 'N/A')} |
| Groupe DH: | {data.get('ipsec_esp_dh_group', 'N/A')} |
| Chiffrement: | {data.get('ipsec_esp_encryption', 'N/A')} |
| Hash: | {data.get('ipsec_esp_hash', 'N/A')} |
| Durée de vie (s): | {data.get('ipsec_esp_lifetime', 'N/A')} |
Erreur lors du formatage des données: {str(e)}
" + + @api.model + def create_or_update_from_data(self, site, vpn_data): + """Create or update VPN configuration from UniFi API data + + Args: + site (unifi.site): Site record + vpn_data (dict): VPN configuration data from UniFi API + + Returns: + unifi.vpn: Created or updated VPN configuration record + """ + _logger.info("Creating or updating VPN configuration from data") + + # Extract required fields + name = vpn_data.get('name') + vpn_type = vpn_data.get('vpn_type') + unifi_id = vpn_data.get('_id') + + if not name or not vpn_type: + _logger.warning("Missing required fields in VPN data") + return False + + # Search for existing VPN configuration + domain = [ + ('site_id', '=', site.id), + '|', + ('name', '=', name), + ('unifi_id', '=', unifi_id) + ] + + existing = self.search(domain, limit=1) + + # Prepare values for create/write + vals = { + 'name': name, + 'vpn_type': vpn_type, + 'unifi_id': unifi_id, + 'enabled': vpn_data.get('enabled', True), + 'peer_ip': vpn_data.get('ipsec_peer_ip'), + 'local_ip': vpn_data.get('ipsec_local_ip'), + 'interface': vpn_data.get('ifname'), + 'purpose': vpn_data.get('purpose'), + 'remote_subnets': ', '.join(vpn_data.get('remote_vpn_subnets', [])), + 'last_sync': fields.Datetime.now(), + 'raw_data': json.dumps(vpn_data, indent=2) if vpn_data else False + } + + if existing: + _logger.info("Updating existing VPN configuration: %s", existing.name) + existing.write(vals) + return existing + else: + _logger.info("Creating new VPN configuration: %s", name) + vals['site_id'] = site.id + return self.create(vals) diff --git a/unifi_integration/models/unifi_wifi.py b/unifi_integration/models/unifi_wifi.py new file mode 100644 index 0000000..7f8052d --- /dev/null +++ b/unifi_integration/models/unifi_wifi.py @@ -0,0 +1,446 @@ +# -*- coding: utf-8 -*- + +# These imports will work in an Odoo environment, even if your IDE marks them as not found +# pylint: disable=import-error +from odoo import models, fields, api, _ +from .unifi_common import UnifiCommonMixin +from odoo.exceptions import UserError, ValidationError +# pylint: enable=import-error + +import json +import logging +from datetime import datetime + +_logger = logging.getLogger(__name__) + +class UnifiWifi(models.Model, UnifiCommonMixin): + """Model for UniFi WiFi networks + + This model represents WiFi networks configured in UniFi sites. + It stores configuration details such as SSID, password, security type, etc. + """ + _name = 'unifi.wifi' + _description = 'UniFi WiFi Network' + _order = 'name' + + active = fields.Boolean( + string='Active', + default=True, + help='Whether this record is active in Odoo' + ) + + # Basic information + name = fields.Char( + string='Name', + required=True, + help='Name of the WiFi network (SSID)' + ) + + wifi_id = fields.Char( + string='WiFi ID', + required=True, + help='Unique identifier for this WiFi network in the UniFi system' + ) + + # WiFi configuration + enabled = fields.Boolean( + string='Enabled', + default=True, + help='Whether this WiFi network is enabled' + ) + + hidden = fields.Boolean( + string='Hidden SSID', + default=False, + help='Whether this SSID is hidden from network scans' + ) + + security = fields.Selection( + selection=[ + ('open', 'Open'), + ('wep', 'WEP'), + ('wpapsk', 'WPA Personal'), + ('wpa2psk', 'WPA2 Personal'), + ('wpapskwpa2psk', 'WPA/WPA2 Personal'), + ('wpa3', 'WPA3 Personal'), + ('wpa2enterprise', 'WPA2 Enterprise'), + ('wpa3enterprise', 'WPA3 Enterprise'), + ('radius', 'RADIUS') + ], + string='Security Type', + default='wpa2psk', + help='Security protocol used by this WiFi network' + ) + + password = fields.Char( + string='Password', + help='Password for this WiFi network (stored securely)' + ) + + # Network configuration + network_id = fields.Many2one( + comodel_name='unifi.network', + string='Network', + help='Network associated with this WiFi' + ) + + vlan_id = fields.Many2one( + comodel_name='unifi.vlan', + string='VLAN', + help='VLAN associated with this WiFi' + ) + + # WiFi settings + band = fields.Selection( + selection=[ + ('2g', '2.4 GHz'), + ('5g', '5 GHz'), + ('both', '2.4 & 5 GHz') + ], + string='Band', + default='both', + help='WiFi frequency band' + ) + + channel = fields.Integer( + string='Channel', + help='WiFi channel (0 for auto)' + ) + + channel_width = fields.Selection( + selection=[ + ('20', '20 MHz'), + ('40', '40 MHz'), + ('80', '80 MHz'), + ('160', '160 MHz') + ], + string='Channel Width', + help='WiFi channel width' + ) + + tx_power = fields.Selection( + selection=[ + ('auto', 'Auto'), + ('low', 'Low'), + ('medium', 'Medium'), + ('high', 'High'), + ('custom', 'Custom') + ], + string='TX Power', + default='auto', + help='Transmission power' + ) + + tx_power_custom = fields.Integer( + string='Custom TX Power', + help='Custom transmission power in dBm' + ) + + # Guest network settings + is_guest = fields.Boolean( + string='Guest Network', + default=False, + help='Whether this is a guest WiFi network' + ) + + guest_policy = fields.Selection( + selection=[ + ('none', 'No restrictions'), + ('mac', 'MAC filtering'), + ('auth', 'Authentication required') + ], + string='Guest Policy', + default='none', + help='Access policy for guest networks' + ) + + # Advanced settings + wpa_mode = fields.Selection( + selection=[ + ('wpa', 'WPA'), + ('wpa2', 'WPA2'), + ('wpa3', 'WPA3'), + ('wpa/wpa2', 'WPA/WPA2'), + ('wpa2/wpa3', 'WPA2/WPA3') + ], + string='WPA Mode', + help='WPA mode for this network' + ) + + wpa_encryption = fields.Selection( + selection=[ + ('auto', 'Auto'), + ('ccmp', 'CCMP (AES)'), + ('tkip', 'TKIP'), + ('tkip-ccmp', 'TKIP/CCMP') + ], + string='WPA Encryption', + default='auto', + help='Encryption method used for WPA' + ) + + pmf_mode = fields.Selection( + selection=[ + ('disabled', 'Disabled'), + ('optional', 'Optional'), + ('required', 'Required') + ], + string='PMF Mode', + default='optional', + help='Protected Management Frames mode' + ) + + # Tracking fields + created_at = fields.Datetime( + string='Created At', + default=fields.Datetime.now, + help='When this record was created in Odoo' + ) + + updated_at = fields.Datetime( + string='Updated At', + help='When this record was last updated in Odoo' + ) + + last_sync = fields.Datetime( + string='Last Sync', + help='When this record was last synchronized with UniFi' + ) + + # Raw data + raw_data = fields.Text( + string='Raw Data', + help='Raw JSON data from UniFi API' + ) + + raw_data_json = fields.Text( + string='Données brutes (JSON)', + compute='_compute_raw_data_json', + help='Données brutes de la configuration WiFi au format JSON formaté' + ) + + @api.depends('raw_data') + def _compute_raw_data_json(self): + for record in self: + record.raw_data_json = self.format_raw_data_json(record.raw_data) + + # Relations + site_id = fields.Many2one( + comodel_name='unifi.site', + string='Site', + required=True, + ondelete='cascade', + help='UniFi site this WiFi network belongs to' + ) + + # Methods + def create_or_update_from_data(self, site, wifi_data): + """Create or update a WiFi network from API data + + Args: + site: The UniFi site record + wifi_data: The WiFi network data from the API + + Returns: + record: The created or updated WiFi network record + """ + # Extract the WiFi ID + wifi_id = wifi_data.get('_id') or wifi_data.get('id') + if not wifi_id: + _logger.error("Cannot create/update WiFi network: missing ID") + return False + + # Search for an existing WiFi network with this ID + existing_wifi = self.search([ + ('wifi_id', '=', wifi_id), + ('site_id', '=', site.id) + ], limit=1) + + # Prepare values for creation/update + vals = { + 'wifi_id': wifi_id, + 'name': wifi_data.get('name', wifi_data.get('ssid', f"WiFi {wifi_id}")), + 'site_id': site.id, + 'enabled': wifi_data.get('enabled', True), + 'hidden': wifi_data.get('hide_ssid', False), + 'security': self._map_security_type(wifi_data.get('security', 'wpa2psk')), + 'is_guest': wifi_data.get('is_guest', False), + 'band': self._map_band(wifi_data.get('band', 'both')), + 'last_sync': fields.Datetime.now(), + 'raw_data': json.dumps(wifi_data) + } + + # Add password if available (and not empty) + if wifi_data.get('x_passphrase') and wifi_data.get('x_passphrase') != 'null': + vals['password'] = wifi_data.get('x_passphrase') + + # Map network and VLAN if available + if wifi_data.get('networkconf_id'): + network = self.env['unifi.network'].search([ + ('network_id', '=', wifi_data.get('networkconf_id')), + ('site_id', '=', site.id) + ], limit=1) + if network: + vals['network_id'] = network.id + + if wifi_data.get('vlan_id'): + vlan = self.env['unifi.vlan'].search([ + ('vlan_id', '=', wifi_data.get('vlan_id')), + ('site_id', '=', site.id) + ], limit=1) + if vlan: + vals['vlan_id'] = vlan.id + + # Add advanced settings if available + if 'channel' in wifi_data: + vals['channel'] = wifi_data.get('channel') + + if 'channel_width' in wifi_data: + vals['channel_width'] = str(wifi_data.get('channel_width', '20')) + + if 'tx_power' in wifi_data: + vals['tx_power'] = self._map_tx_power(wifi_data.get('tx_power')) + + if 'tx_power_mode' in wifi_data and wifi_data.get('tx_power_mode') == 'custom': + vals['tx_power'] = 'custom' + vals['tx_power_custom'] = wifi_data.get('tx_power') + + # WPA settings + if 'wpa_mode' in wifi_data: + vals['wpa_mode'] = self._map_wpa_mode(wifi_data.get('wpa_mode')) + + if 'wpa_enc' in wifi_data: + vals['wpa_encryption'] = self._map_wpa_encryption(wifi_data.get('wpa_enc')) + + if 'pmf_mode' in wifi_data: + vals['pmf_mode'] = self._map_pmf_mode(wifi_data.get('pmf_mode')) + + if existing_wifi: + # Update existing WiFi network + vals['updated_at'] = fields.Datetime.now() + existing_wifi.write(vals) + return existing_wifi + else: + # Create new WiFi network + return self.create(vals) + + def _map_security_type(self, security): + """Map UniFi security type to model selection value + + Args: + security: Security type from UniFi API + + Returns: + str: Mapped security type + """ + security_map = { + 'open': 'open', + 'wep': 'wep', + 'wpapsk': 'wpapsk', + 'wpa2psk': 'wpa2psk', + 'wpapskwpa2psk': 'wpapskwpa2psk', + 'wpa3': 'wpa3', + 'wpa2enterprise': 'wpa2enterprise', + 'wpa3enterprise': 'wpa3enterprise', + 'radius': 'radius' + } + return security_map.get(security, 'wpa2psk') + + def _map_band(self, band): + """Map UniFi band to model selection value + + Args: + band: Band from UniFi API + + Returns: + str: Mapped band + """ + band_map = { + 'ng': '2g', + '2g': '2g', + 'na': '5g', + '5g': '5g', + 'both': 'both', + 'all': 'both' + } + return band_map.get(band, 'both') + + def _map_tx_power(self, tx_power): + """Map UniFi TX power to model selection value + + Args: + tx_power: TX power from UniFi API + + Returns: + str: Mapped TX power + """ + # If it's already a string value that matches our selection options + if tx_power in ['auto', 'low', 'medium', 'high', 'custom']: + return tx_power + + # If it's a numeric value, map it to a named level + try: + power = int(tx_power) + if power <= 10: + return 'low' + elif power <= 18: + return 'medium' + else: + return 'high' + except (ValueError, TypeError): + return 'auto' + + def _map_wpa_mode(self, wpa_mode): + """Map UniFi WPA mode to model selection value + + Args: + wpa_mode: WPA mode from UniFi API + + Returns: + str: Mapped WPA mode + """ + wpa_mode_map = { + '1': 'wpa', + '2': 'wpa2', + '3': 'wpa3', + '12': 'wpa/wpa2', + '23': 'wpa2/wpa3' + } + return wpa_mode_map.get(str(wpa_mode), 'wpa2') + + def _map_wpa_encryption(self, wpa_enc): + """Map UniFi WPA encryption to model selection value + + Args: + wpa_enc: WPA encryption from UniFi API + + Returns: + str: Mapped WPA encryption + """ + wpa_enc_map = { + 'auto': 'auto', + 'ccmp': 'ccmp', + 'tkip': 'tkip', + 'tkip-ccmp': 'tkip-ccmp', + 'tkip,ccmp': 'tkip-ccmp' + } + return wpa_enc_map.get(wpa_enc, 'auto') + + def _map_pmf_mode(self, pmf_mode): + """Map UniFi PMF mode to model selection value + + Args: + pmf_mode: PMF mode from UniFi API + + Returns: + str: Mapped PMF mode + """ + pmf_mode_map = { + '0': 'disabled', + '1': 'optional', + '2': 'required', + 'disabled': 'disabled', + 'optional': 'optional', + 'required': 'required' + } + return pmf_mode_map.get(str(pmf_mode), 'optional') diff --git a/unifi_integration/security/ir.model.access.csv b/unifi_integration/security/ir.model.access.csv index 60a1f84..526306e 100644 --- a/unifi_integration/security/ir.model.access.csv +++ b/unifi_integration/security/ir.model.access.csv @@ -1,24 +1,24 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink -unifi_integration.access_unifi_site_manager,access_unifi_site_manager,unifi_integration.model_unifi_site_manager,base.group_user,1,1,1,0 -unifi_integration.access_unifi_auth_session,access_unifi_auth_session,unifi_integration.model_unifi_auth_session,base.group_user,1,0,0,0 +unifi_integration.access_unifi_auth_session,access_unifi_auth_session,unifi_integration.model_unifi_auth_session,base.group_user,1,1,1,1 unifi_integration.access_unifi_mfa,access_unifi_mfa,unifi_integration.model_unifi_mfa,base.group_user,1,0,0,0 -unifi_integration.access_unifi_api_config,access_unifi_api_config,unifi_integration.model_unifi_api_config,base.group_user,1,0,0,0 -unifi_integration.access_unifi_api_log,access_unifi_api_log,unifi_integration.model_unifi_api_log,base.group_user,1,0,0,0 -unifi_integration.access_unifi_sync_job,access_unifi_sync_job,unifi_integration.model_unifi_sync_job,base.group_user,1,0,0,0 -unifi_integration.access_unifi_device,access_unifi_device,unifi_integration.model_unifi_device,base.group_user,1,0,0,0 -unifi_integration.access_unifi_network,access_unifi_network,unifi_integration.model_unifi_network,base.group_user,1,0,0,0 -unifi_integration.access_unifi_vlan,access_unifi_vlan,unifi_integration.model_unifi_vlan,base.group_user,1,0,0,0 -unifi_integration.access_unifi_user,access_unifi_user,unifi_integration.model_unifi_user,base.group_user,1,0,0,0 -unifi_integration.access_unifi_firewall_rule,access_unifi_firewall_rule,unifi_integration.model_unifi_firewall_rule,base.group_user,1,0,0,0 -unifi_integration.access_unifi_port_forward,access_unifi_port_forward,unifi_integration.model_unifi_port_forward,base.group_user,1,0,0,0 -unifi_integration.access_unifi_system_info,access_unifi_system_info,unifi_integration.model_unifi_system_info,base.group_user,1,0,0,0 -unifi_integration.access_unifi_dns,access_unifi_dns,unifi_integration.model_unifi_dns,base.group_user,1,0,0,0 -unifi_integration.access_unifi_dns_config,access_unifi_dns_config,unifi_integration.model_unifi_dns_config,base.group_user,1,0,0,0 -unifi_integration.access_unifi_routing,access_unifi_routing,unifi_integration.model_unifi_routing,base.group_user,1,0,0,0 -unifi_integration.access_unifi_routing_config,access_unifi_routing_config,unifi_integration.model_unifi_routing_config,base.group_user,1,0,0,0 +unifi_integration.access_unifi_api_config,access_unifi_api_config,unifi_integration.model_unifi_api_config,base.group_user,1,1,1,0 +unifi_integration.access_unifi_api_log,access_unifi_api_log,unifi_integration.model_unifi_api_log,base.group_user,1,1,1,0 +unifi_integration.access_unifi_sync_job,access_unifi_sync_job,unifi_integration.model_unifi_sync_job,base.group_user,1,1,1,0 +unifi_integration.access_unifi_device,access_unifi_device,unifi_integration.model_unifi_device,base.group_user,1,1,1,1 +unifi_integration.access_unifi_network,access_unifi_network,unifi_integration.model_unifi_network,base.group_user,1,1,1,1 +unifi_integration.access_unifi_vlan,access_unifi_vlan,unifi_integration.model_unifi_vlan,base.group_user,1,1,1,1 +unifi_integration.access_unifi_user,access_unifi_user,unifi_integration.model_unifi_user,base.group_user,1,1,1,1 +unifi_integration.access_unifi_firewall_rule,access_unifi_firewall_rule,unifi_integration.model_unifi_firewall_rule,base.group_user,1,1,1,1 +unifi_integration.access_unifi_port_forward,access_unifi_port_forward,unifi_integration.model_unifi_port_forward,base.group_user,1,1,1,1 +unifi_integration.access_unifi_system_info,access_unifi_system_info,unifi_integration.model_unifi_system_info,base.group_user,1,1,1,1 +unifi_integration.access_unifi_dns,access_unifi_dns,unifi_integration.model_unifi_dns,base.group_user,1,1,1,1 +unifi_integration.access_unifi_dns_config,access_unifi_dns_config,unifi_integration.model_unifi_dns_config,base.group_user,1,1,1,1 +unifi_integration.access_unifi_routing,access_unifi_routing,unifi_integration.model_unifi_routing,base.group_user,1,1,1,1 +unifi_integration.access_unifi_routing_config,access_unifi_routing_config,unifi_integration.model_unifi_routing_config,base.group_user,1,1,1,1 unifi_integration.access_unifi_site_import_wizard,access_unifi_site_import_wizard,unifi_integration.model_unifi_site_import_wizard,base.group_user,1,1,1,0 unifi_integration.access_unifi_site_discovery,access_unifi_site_discovery,unifi_integration.model_unifi_site_discovery,base.group_user,1,0,0,0 unifi_integration.access_unifi_site,access_unifi_site,unifi_integration.model_unifi_site,base.group_user,1,1,1,0 -unifi_integration.access_unifi_site_controller,access_unifi_site_controller,unifi_integration.model_unifi_site_controller,base.group_user,1,1,1,0 unifi_integration.access_unifi_dashboard_metric,access_unifi_dashboard_metric,unifi_integration.model_unifi_dashboard_metric,base.group_user,1,0,0,0 +unifi_integration.access_unifi_wifi,access_unifi_wifi,unifi_integration.model_unifi_wifi,base.group_user,1,1,1,1 +unifi_integration.access_unifi_vpn,access_unifi_vpn,unifi_integration.model_unifi_vpn,base.group_user,1,1,1,1 unifi_integration.access_unifi_dashboard_stat,access_unifi_dashboard_stat,unifi_integration.model_unifi_dashboard_stat,base.group_user,1,0,0,0 \ No newline at end of file diff --git a/unifi_integration/security/unifi_security.xml b/unifi_integration/security/unifi_security.xml index 74e56ad..c4624c0 100644 --- a/unifi_integration/security/unifi_security.xml +++ b/unifi_integration/security/unifi_security.xml @@ -56,5 +56,30 @@