unifi get datas
This commit is contained in:
parent
5390b6308e
commit
ccf211c554
41 changed files with 5601 additions and 4014 deletions
1
odoo_rsync_backup/__init__.py
Normal file
1
odoo_rsync_backup/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
|||
from . import models
|
||||
20
odoo_rsync_backup/__manifest__.py
Normal file
20
odoo_rsync_backup/__manifest__.py
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
'name': 'Odoo Rsync Backup',
|
||||
'version': '18.0.1.0.0',
|
||||
'category': 'Administration/Backup',
|
||||
'summary': 'Rsync backup for filestore and configuration',
|
||||
'sequence': 1,
|
||||
'author': 'Bemade',
|
||||
'website': 'https://bemade.org',
|
||||
'license': 'LGPL-3',
|
||||
'depends': [
|
||||
'base',
|
||||
'auto_database_backup'
|
||||
],
|
||||
'data': [
|
||||
'views/rsync_config_views.xml',
|
||||
],
|
||||
'installable': True,
|
||||
'application': True,
|
||||
'auto_install': False,
|
||||
}
|
||||
14
odoo_rsync_backup/data/backup_cron.xml
Normal file
14
odoo_rsync_backup/data/backup_cron.xml
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
<record id="ir_cron_rsync_backup" model="ir.cron">
|
||||
<field name="name">Rsync Backup: Daily Backup</field>
|
||||
<field name="model_id" ref="model_rsync_backup_config"/>
|
||||
<field name="state">code</field>
|
||||
<field name="code">model._perform_backup()</field>
|
||||
<field name="interval_type">days</field>
|
||||
<field name="interval_number">1</field>
|
||||
<field name="active" eval="True"/>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
1
odoo_rsync_backup/models/__init__.py
Normal file
1
odoo_rsync_backup/models/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
|||
from . import rsync_config
|
||||
185
odoo_rsync_backup/models/rsync_config.py
Normal file
185
odoo_rsync_backup/models/rsync_config.py
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
import logging
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
from odoo import models, fields, tools, _
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
class DbBackupConfigInherit(models.Model):
|
||||
"""
|
||||
Extend db.backup.configure to add rsync functionality
|
||||
"""
|
||||
_inherit = 'db.backup.configure'
|
||||
|
||||
# Rsync configuration fields
|
||||
enable_rsync = fields.Boolean(
|
||||
string='RSync the filestore',
|
||||
help='Enable rsync backup for filestore when using dump format'
|
||||
)
|
||||
|
||||
remote_host = fields.Char(string='Remote Host')
|
||||
remote_user = fields.Char(string='Remote User')
|
||||
remote_port = fields.Integer(string='SSH Port', default=22)
|
||||
ssh_key_path = fields.Char(
|
||||
string='SSH Key Path',
|
||||
default='/home/odoo/.ssh/id_rsa',
|
||||
help='Path to the SSH private key file'
|
||||
)
|
||||
filestore_dest_path = fields.Char(
|
||||
string='Filestore Destination Path',
|
||||
help='Remote path where filestore will be backed up'
|
||||
)
|
||||
|
||||
|
||||
def _sync_filestore_with_rsync(self):
|
||||
"""
|
||||
Synchronize filestore directory using rsync after backup is completed
|
||||
"""
|
||||
if not (self.enable_rsync and self.backup_format == 'dump'):
|
||||
return
|
||||
|
||||
try:
|
||||
# Get filestore path
|
||||
filestore_path = os.path.join(tools.config['data_dir'], 'filestore', self.env.cr.dbname)
|
||||
|
||||
# Build rsync command
|
||||
filestore_cmd = [
|
||||
'rsync', '-avz', '-e',
|
||||
f'ssh -p {self.remote_port} -i {self.ssh_key_path}',
|
||||
filestore_path + '/', # Add trailing slash to sync contents
|
||||
f'{self.remote_user}@{self.remote_host}:{self.filestore_dest_path}'
|
||||
]
|
||||
|
||||
# Execute rsync command
|
||||
subprocess.run(filestore_cmd, check=True)
|
||||
|
||||
_logger.info('Rsync backup completed successfully')
|
||||
return True
|
||||
|
||||
except subprocess.CalledProcessError as e:
|
||||
_logger.error('Rsync backup failed: %s', str(e))
|
||||
raise UserError(_('Rsync backup failed. Check the logs for details.'))
|
||||
except Exception as e:
|
||||
_logger.error('Unexpected error during rsync backup: %s', str(e))
|
||||
raise UserError(_('Unexpected error during rsync backup. Check the logs for details.'))
|
||||
|
||||
def _schedule_auto_backup(self, frequency):
|
||||
"""
|
||||
Override _schedule_auto_backup to add rsync synchronization after backup
|
||||
"""
|
||||
res = super(DbBackupConfigInherit, self)._schedule_auto_backup(frequency)
|
||||
self._sync_filestore_with_rsync()
|
||||
return res
|
||||
|
||||
name = fields.Char(
|
||||
string='Name',
|
||||
required=True
|
||||
)
|
||||
|
||||
remote_host = fields.Char(
|
||||
string='Remote Host',
|
||||
required=True
|
||||
)
|
||||
|
||||
remote_user = fields.Char(
|
||||
string='Remote User',
|
||||
required=True
|
||||
)
|
||||
|
||||
remote_port = fields.Integer(
|
||||
string='SSH Port',
|
||||
default=22
|
||||
)
|
||||
|
||||
ssh_key_path = fields.Char(
|
||||
string='SSH Key Path',
|
||||
default='/home/odoo/.ssh/id_rsa',
|
||||
help='Path to the SSH private key file'
|
||||
)
|
||||
|
||||
filestore_dest_path = fields.Char(
|
||||
string='Filestore Destination Path',
|
||||
required=True,
|
||||
help='Remote path where filestore will be backed up'
|
||||
)
|
||||
|
||||
config_dest_path = fields.Char(
|
||||
string='Config Destination Path',
|
||||
required=True,
|
||||
help='Remote path where odoo.conf will be backed up'
|
||||
)
|
||||
|
||||
active = fields.Boolean(
|
||||
default=True
|
||||
)
|
||||
|
||||
last_backup = fields.Datetime(
|
||||
string='Last Backup',
|
||||
readonly=True
|
||||
)
|
||||
|
||||
def _run_rsync_command(self, source_path, dest_path):
|
||||
"""
|
||||
Execute rsync command with specified parameters
|
||||
"""
|
||||
try:
|
||||
cmd = [
|
||||
'rsync',
|
||||
'-avz',
|
||||
'-e', f'ssh -p {self.remote_port} -i {self.ssh_key_path}',
|
||||
source_path,
|
||||
f'{self.remote_user}@{self.remote_host}:{dest_path}'
|
||||
]
|
||||
|
||||
process = subprocess.run(
|
||||
cmd,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=True
|
||||
)
|
||||
_logger.info(f'Rsync successful: {process.stdout}')
|
||||
return True
|
||||
except subprocess.CalledProcessError as e:
|
||||
_logger.error(f'Rsync failed: {e.stderr}')
|
||||
return False
|
||||
|
||||
def action_backup_now(self):
|
||||
"""
|
||||
Manually trigger the backup process
|
||||
"""
|
||||
self.ensure_one()
|
||||
self._perform_backup()
|
||||
return True
|
||||
|
||||
def _perform_backup(self):
|
||||
"""
|
||||
Perform the actual backup operation for filestore and config
|
||||
"""
|
||||
success = True
|
||||
|
||||
# Get filestore path from Odoo config
|
||||
filestore_path = os.path.join(
|
||||
self.env['ir.config_parameter'].get_param('data_dir'),
|
||||
'filestore',
|
||||
self.env.cr.dbname
|
||||
)
|
||||
|
||||
# Get odoo.conf path
|
||||
odoo_conf_path = os.environ.get('ODOO_RC', '/etc/odoo/odoo.conf')
|
||||
|
||||
# Backup filestore
|
||||
if os.path.exists(filestore_path):
|
||||
if not self._run_rsync_command(filestore_path + '/', self.filestore_dest_path):
|
||||
success = False
|
||||
|
||||
# Backup odoo.conf
|
||||
if os.path.exists(odoo_conf_path):
|
||||
if not self._run_rsync_command(odoo_conf_path, self.config_dest_path):
|
||||
success = False
|
||||
|
||||
if success:
|
||||
self.write({'last_backup': fields.Datetime.now()})
|
||||
|
||||
return success
|
||||
2
odoo_rsync_backup/security/ir.model.access.csv
Normal file
2
odoo_rsync_backup/security/ir.model.access.csv
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_rsync_backup_config_admin,rsync.backup.config.admin,model_rsync_backup_config,base.group_system,1,1,1,1
|
||||
|
33
odoo_rsync_backup/views/rsync_config_views.xml
Normal file
33
odoo_rsync_backup/views/rsync_config_views.xml
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<!-- Inherit Form View -->
|
||||
<record id="view_db_backup_configure_form_inherit_rsync" model="ir.ui.view">
|
||||
<field name="name">db.backup.configure.form.inherit.rsync</field>
|
||||
<field name="model">db.backup.configure</field>
|
||||
<field name="inherit_id" ref="auto_database_backup.db_backup_configure_view_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="backup_format" position="after">
|
||||
<field name="enable_rsync" attrs="{'invisible': [('backup_format', '!=', 'dump')]}"/>
|
||||
<field name="remote_host" attrs="{
|
||||
'invisible': ['|', ('backup_format', '!=', 'dump'), ('enable_rsync', '=', False)],
|
||||
'required': [('enable_rsync', '=', True)]
|
||||
}"/>
|
||||
<field name="remote_user" attrs="{
|
||||
'invisible': ['|', ('backup_format', '!=', 'dump'), ('enable_rsync', '=', False)],
|
||||
'required': [('enable_rsync', '=', True)]
|
||||
}"/>
|
||||
<field name="remote_port" attrs="{
|
||||
'invisible': ['|', ('backup_format', '!=', 'dump'), ('enable_rsync', '=', False)]
|
||||
}"/>
|
||||
<field name="ssh_key_path" attrs="{
|
||||
'invisible': ['|', ('backup_format', '!=', 'dump'), ('enable_rsync', '=', False)]
|
||||
}"/>
|
||||
<field name="filestore_dest_path" attrs="{
|
||||
'invisible': ['|', ('backup_format', '!=', 'dump'), ('enable_rsync', '=', False)],
|
||||
'required': [('enable_rsync', '=', True)]
|
||||
}"/>
|
||||
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
|
|
@ -46,6 +46,8 @@ This module allows you to:
|
|||
'views/unifi_dns_config_views.xml',
|
||||
'views/unifi_routing_views.xml',
|
||||
'views/unifi_routing_config_views.xml',
|
||||
'views/unifi_wifi_views.xml',
|
||||
'views/unifi_vpn_views.xml',
|
||||
'views/unifi_api_config_views.xml',
|
||||
'views/unifi_dashboard_views.xml',
|
||||
# Templates
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Import module files
|
||||
# Common functionality
|
||||
from . import unifi_common
|
||||
|
||||
# UniFi models
|
||||
from . import unifi_site
|
||||
from . import unifi_site_controller
|
||||
from . import unifi_site_manager
|
||||
from . import unifi_auth_session
|
||||
from . import unifi_mfa
|
||||
from . import unifi_api_config
|
||||
|
|
@ -21,5 +22,7 @@ from . import unifi_dns
|
|||
from . import unifi_dns_config
|
||||
from . import unifi_routing
|
||||
from . import unifi_routing_config
|
||||
from . import unifi_wifi
|
||||
from . import unifi_vpn
|
||||
from . import unifi_dashboard_metric
|
||||
from . import unifi_dashboard_stat
|
||||
|
|
|
|||
|
|
@ -53,6 +53,17 @@ class UnifiAuthSession(models.Model):
|
|||
help='Session cookie for Controller API'
|
||||
)
|
||||
|
||||
endpoint = fields.Char(
|
||||
string='Authentication Endpoint',
|
||||
help='The endpoint used for authentication (e.g. /api/login or /api/auth/login)'
|
||||
)
|
||||
|
||||
is_udm_pro = fields.Boolean(
|
||||
string='Is UDM Pro',
|
||||
default=False,
|
||||
help='Indicates if this session is for a UDM Pro or UDM Pro SE device'
|
||||
)
|
||||
|
||||
expiry = fields.Datetime(
|
||||
string='Expiry Date',
|
||||
help='Date and time when this session expires'
|
||||
|
|
|
|||
44
unifi_integration/models/unifi_common.py
Normal file
44
unifi_integration/models/unifi_common.py
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
import json
|
||||
import logging
|
||||
from odoo import models, fields, api, _
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
class UnifiCommonMixin(models.AbstractModel):
|
||||
"""Mixin for common functionality across UniFi models
|
||||
|
||||
This mixin provides common fields and methods that are used by multiple
|
||||
UniFi models, such as formatting raw JSON data.
|
||||
"""
|
||||
_name = 'unifi.common.mixin'
|
||||
_description = 'UniFi Common Functionality Mixin'
|
||||
|
||||
def format_raw_data_json(self, raw_data):
|
||||
"""Format raw JSON data by removing outer braces and adjusting indentation
|
||||
|
||||
Args:
|
||||
raw_data (str): Raw JSON data string
|
||||
|
||||
Returns:
|
||||
str: Formatted JSON string without outer braces
|
||||
"""
|
||||
if not raw_data:
|
||||
return ''
|
||||
|
||||
try:
|
||||
# Load and format the JSON data
|
||||
data = json.loads(raw_data)
|
||||
formatted_json = json.dumps(data, indent=4)
|
||||
|
||||
# Remove the first and last line (the braces)
|
||||
lines = formatted_json.split('\n')
|
||||
if len(lines) > 2: # Ensure there are at least 3 lines
|
||||
# Remove the first and last line and adjust indentation
|
||||
inner_content = '\n'.join(line[4:] for line in lines[1:-1])
|
||||
return inner_content
|
||||
else:
|
||||
return formatted_json
|
||||
except (ValueError, json.JSONDecodeError):
|
||||
return 'Invalid JSON'
|
||||
|
|
@ -4,6 +4,7 @@
|
|||
# pylint: disable=import-error
|
||||
from odoo import models, fields, api
|
||||
from odoo.tools.translate import _
|
||||
from .unifi_common import UnifiCommonMixin
|
||||
# pylint: enable=import-error
|
||||
|
||||
import logging
|
||||
|
|
@ -12,7 +13,7 @@ from datetime import datetime, timedelta
|
|||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
class UnifiDevice(models.Model):
|
||||
class UnifiDevice(models.Model, UnifiCommonMixin):
|
||||
"""Modèle pour les appareils UniFi
|
||||
|
||||
Ce modèle stocke les informations sur les appareils UniFi, tels que les points d'accès,
|
||||
|
|
@ -114,6 +115,17 @@ class UnifiDevice(models.Model):
|
|||
help="Données JSON brutes de l'appareil provenant de l'API UniFi"
|
||||
)
|
||||
|
||||
raw_data_json = fields.Text(
|
||||
string='Données brutes (JSON)',
|
||||
compute='_compute_raw_data_json',
|
||||
help='Données brutes de l\'appareil 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)
|
||||
|
||||
active = fields.Boolean(
|
||||
string='Actif',
|
||||
default=True,
|
||||
|
|
|
|||
|
|
@ -133,6 +133,59 @@ class UnifiDns(models.Model):
|
|||
|
||||
record.last_sync = fields.Datetime.now()
|
||||
|
||||
@api.model
|
||||
def create_or_update_from_data(self, site, dns_data):
|
||||
"""Create or update DNS entry from UniFi API data
|
||||
|
||||
Args:
|
||||
site (unifi.site): Site record
|
||||
dns_data (dict): DNS entry data from UniFi API
|
||||
|
||||
Returns:
|
||||
unifi.dns: Created or updated DNS entry record
|
||||
"""
|
||||
_logger.info("Creating or updating DNS entry from data: %s", dns_data)
|
||||
|
||||
# Extract required fields
|
||||
hostname = dns_data.get('hostname')
|
||||
ip_address = dns_data.get('ip_address')
|
||||
unifi_id = dns_data.get('id') or dns_data.get('_id')
|
||||
|
||||
if not hostname or not ip_address:
|
||||
_logger.warning("Missing required fields in DNS data: %s", dns_data)
|
||||
return False
|
||||
|
||||
# Search for existing DNS entry
|
||||
domain = [
|
||||
('site_id', '=', site.id),
|
||||
'|',
|
||||
('hostname', '=', hostname),
|
||||
('unifi_id', '=', unifi_id)
|
||||
]
|
||||
|
||||
existing = self.search(domain, limit=1)
|
||||
|
||||
# Prepare values for create/write
|
||||
vals = {
|
||||
'hostname': hostname,
|
||||
'ip_address': ip_address,
|
||||
'unifi_id': unifi_id,
|
||||
'description': dns_data.get('description', ''),
|
||||
'enabled': dns_data.get('enabled', True),
|
||||
'entry_type': dns_data.get('type', 'static'),
|
||||
'last_sync': fields.Datetime.now(),
|
||||
'raw_data': json.dumps(dns_data, indent=2) if dns_data else False
|
||||
}
|
||||
|
||||
if existing:
|
||||
_logger.info("Updating existing DNS entry: %s", existing.hostname)
|
||||
existing.write(vals)
|
||||
return existing
|
||||
else:
|
||||
_logger.info("Creating new DNS entry: %s", hostname)
|
||||
vals['site_id'] = site.id
|
||||
return self.create(vals)
|
||||
|
||||
def push_to_unifi(self):
|
||||
"""Pousse les modifications vers le système UniFi"""
|
||||
for record in self:
|
||||
|
|
|
|||
|
|
@ -78,16 +78,56 @@ class UnifiFirewallRule(models.Model):
|
|||
help='Protocole réseau auquel cette règle s\'applique'
|
||||
)
|
||||
|
||||
source_type = fields.Selection(
|
||||
selection=[
|
||||
('address', 'Adresse IP'),
|
||||
('network', 'Réseau'),
|
||||
('object', 'Objet'),
|
||||
('any', 'N\'importe où')
|
||||
],
|
||||
string='Type de source',
|
||||
compute='_compute_source_type',
|
||||
store=True,
|
||||
help='Type de la source (adresse IP, réseau ou objet)'
|
||||
)
|
||||
|
||||
source = fields.Char(
|
||||
string='Source',
|
||||
help='Réseau source ou adresse IP au format CIDR'
|
||||
)
|
||||
|
||||
formatted_source = fields.Char(
|
||||
string='Source formatée',
|
||||
compute='_compute_formatted_source',
|
||||
store=True,
|
||||
help='Affichage formaté de la source en fonction de son type'
|
||||
)
|
||||
|
||||
destination_type = fields.Selection(
|
||||
selection=[
|
||||
('address', 'Adresse IP'),
|
||||
('network', 'Réseau'),
|
||||
('object', 'Objet'),
|
||||
('any', 'N\'importe où')
|
||||
],
|
||||
string='Type de destination',
|
||||
compute='_compute_destination_type',
|
||||
store=True,
|
||||
help='Type de la destination (adresse IP, réseau ou objet)'
|
||||
)
|
||||
|
||||
destination = fields.Char(
|
||||
string='Destination',
|
||||
help='Réseau de destination ou adresse IP au format CIDR'
|
||||
)
|
||||
|
||||
formatted_destination = fields.Char(
|
||||
string='Destination formatée',
|
||||
compute='_compute_formatted_destination',
|
||||
store=True,
|
||||
help='Affichage formaté de la destination en fonction de son type'
|
||||
)
|
||||
|
||||
src_port = fields.Char(
|
||||
string='Port source',
|
||||
help='Numéro de port source ou plage (ex: 80 ou 1024-2048)'
|
||||
|
|
@ -115,6 +155,34 @@ class UnifiFirewallRule(models.Model):
|
|||
help='Type de règle dans le système UniFi'
|
||||
)
|
||||
|
||||
detailed_rule_type = fields.Selection(
|
||||
selection=[
|
||||
('internet-in-ipv4', 'Internet-in (IPv4)'),
|
||||
('internet-out-ipv4', 'Internet-out (IPv4)'),
|
||||
('internet-local-ipv4', 'Internet-local (IPv4)'),
|
||||
('lan-in-ipv4', 'LAN-in (IPv4)'),
|
||||
('lan-out-ipv4', 'LAN-out (IPv4)'),
|
||||
('lan-local-ipv4', 'LAN-local (IPv4)'),
|
||||
('guest-in-ipv4', 'Guest-in (IPv4)'),
|
||||
('guest-out-ipv4', 'Guest-out (IPv4)'),
|
||||
('guest-local-ipv4', 'Guest-local (IPv4)'),
|
||||
('internet-in-ipv6', 'Internet-in (IPv6)'),
|
||||
('internet-out-ipv6', 'Internet-out (IPv6)'),
|
||||
('internet-local-ipv6', 'Internet-local (IPv6)'),
|
||||
('lan-in-ipv6', 'LAN-in (IPv6)'),
|
||||
('lan-out-ipv6', 'LAN-out (IPv6)'),
|
||||
('lan-local-ipv6', 'LAN-local (IPv6)'),
|
||||
('guest-in-ipv6', 'Guest-in (IPv6)'),
|
||||
('guest-out-ipv6', 'Guest-out (IPv6)'),
|
||||
('guest-local-ipv6', 'Guest-local (IPv6)'),
|
||||
('other', 'Autre')
|
||||
],
|
||||
string='Type détaillé',
|
||||
compute='_compute_detailed_rule_type',
|
||||
store=True,
|
||||
help='Type détaillé de la règle de pare-feu (Internet-in, LAN-out, etc.)'
|
||||
)
|
||||
|
||||
rule_index = fields.Integer(
|
||||
string='Index de la règle',
|
||||
help='Position de la règle dans la liste des règles'
|
||||
|
|
@ -144,13 +212,162 @@ class UnifiFirewallRule(models.Model):
|
|||
help='Données brutes de la règle de pare-feu au format JSON'
|
||||
)
|
||||
|
||||
raw_data_json = fields.Text(
|
||||
string='Données brutes (JSON)',
|
||||
compute='_compute_raw_data_json',
|
||||
help='Données brutes de la règle de pare-feu au format JSON'
|
||||
)
|
||||
|
||||
@api.depends('raw_data')
|
||||
def _compute_raw_data_json(self):
|
||||
for record in self:
|
||||
if record.raw_data:
|
||||
try:
|
||||
# Charger le JSON, puis le formater sans les accolades externes
|
||||
data = json.loads(record.raw_data)
|
||||
formatted_json = json.dumps(data, indent=4)
|
||||
# Enlever la première et la dernière ligne (les accolades)
|
||||
lines = formatted_json.split('\n')
|
||||
if len(lines) > 2: # S'assurer qu'il y a au moins 3 lignes
|
||||
# Enlever la première et la dernière ligne et ajuster l'indentation
|
||||
inner_content = '\n'.join(line[4:] for line in lines[1:-1])
|
||||
record.raw_data_json = inner_content
|
||||
else:
|
||||
record.raw_data_json = formatted_json
|
||||
except ValueError:
|
||||
record.raw_data_json = 'Invalid JSON'
|
||||
|
||||
|
||||
# Champs calculés
|
||||
rule_summary = fields.Char(
|
||||
string='Résumé de la règle',
|
||||
compute='_compute_rule_summary'
|
||||
)
|
||||
|
||||
@api.depends('action', 'protocol', 'source', 'destination', 'src_port', 'dst_port')
|
||||
@api.depends('raw_data')
|
||||
def _compute_detailed_rule_type(self):
|
||||
"""Détermine le type détaillé de la règle de pare-feu à partir des données brutes"""
|
||||
for record in self:
|
||||
if not record.raw_data:
|
||||
record.detailed_rule_type = 'other'
|
||||
continue
|
||||
|
||||
try:
|
||||
rule_data = json.loads(record.raw_data)
|
||||
# Extraire les informations du type de règle
|
||||
rule_interface = rule_data.get('ruleset', '')
|
||||
direction = rule_data.get('direction', '')
|
||||
ip_version = 'ipv4' if rule_data.get('ipv6', False) is False else 'ipv6'
|
||||
|
||||
# Construire le type détaillé
|
||||
if rule_interface and direction:
|
||||
detailed_type = f"{rule_interface}-{direction}-{ip_version}"
|
||||
if detailed_type in dict(self._fields['detailed_rule_type'].selection).keys():
|
||||
record.detailed_rule_type = detailed_type
|
||||
else:
|
||||
record.detailed_rule_type = 'other'
|
||||
else:
|
||||
record.detailed_rule_type = 'other'
|
||||
except (json.JSONDecodeError, AttributeError):
|
||||
record.detailed_rule_type = 'other'
|
||||
|
||||
@api.depends('source', 'raw_data')
|
||||
def _compute_source_type(self):
|
||||
"""Détermine le type de la source (adresse IP, réseau ou objet)"""
|
||||
for record in self:
|
||||
if not record.source:
|
||||
record.source_type = 'any'
|
||||
continue
|
||||
|
||||
try:
|
||||
rule_data = json.loads(record.raw_data) if record.raw_data else {}
|
||||
src_type = rule_data.get('src_type', '')
|
||||
|
||||
if src_type == 'network':
|
||||
record.source_type = 'network'
|
||||
elif src_type == 'object':
|
||||
record.source_type = 'object'
|
||||
elif record.source and '/' in record.source:
|
||||
# Si contient un slash, c'est probablement un réseau CIDR
|
||||
record.source_type = 'network'
|
||||
elif record.source:
|
||||
# Sinon, c'est probablement une adresse IP
|
||||
record.source_type = 'address'
|
||||
else:
|
||||
record.source_type = 'any'
|
||||
except (json.JSONDecodeError, AttributeError):
|
||||
if record.source:
|
||||
record.source_type = 'address'
|
||||
else:
|
||||
record.source_type = 'any'
|
||||
|
||||
@api.depends('destination', 'raw_data')
|
||||
def _compute_destination_type(self):
|
||||
"""Détermine le type de la destination (adresse IP, réseau ou objet)"""
|
||||
for record in self:
|
||||
if not record.destination:
|
||||
record.destination_type = 'any'
|
||||
continue
|
||||
|
||||
try:
|
||||
rule_data = json.loads(record.raw_data) if record.raw_data else {}
|
||||
dst_type = rule_data.get('dst_type', '')
|
||||
|
||||
if dst_type == 'network':
|
||||
record.destination_type = 'network'
|
||||
elif dst_type == 'object':
|
||||
record.destination_type = 'object'
|
||||
elif record.destination and '/' in record.destination:
|
||||
# Si contient un slash, c'est probablement un réseau CIDR
|
||||
record.destination_type = 'network'
|
||||
elif record.destination:
|
||||
# Sinon, c'est probablement une adresse IP
|
||||
record.destination_type = 'address'
|
||||
else:
|
||||
record.destination_type = 'any'
|
||||
except (json.JSONDecodeError, AttributeError):
|
||||
if record.destination:
|
||||
record.destination_type = 'address'
|
||||
else:
|
||||
record.destination_type = 'any'
|
||||
|
||||
@api.depends('source', 'source_type', 'raw_data')
|
||||
def _compute_formatted_source(self):
|
||||
"""Calcule l'affichage formaté de la source en fonction de son type"""
|
||||
for record in self:
|
||||
if record.source_type == 'any' or not record.source:
|
||||
record.formatted_source = "N'importe où"
|
||||
elif record.source_type == 'object':
|
||||
try:
|
||||
rule_data = json.loads(record.raw_data) if record.raw_data else {}
|
||||
object_name = rule_data.get('src_object_name', record.source)
|
||||
record.formatted_source = f"Objet: {object_name}"
|
||||
except (json.JSONDecodeError, AttributeError):
|
||||
record.formatted_source = record.source
|
||||
elif record.source_type == 'network':
|
||||
record.formatted_source = f"Réseau: {record.source}"
|
||||
else:
|
||||
record.formatted_source = f"IP: {record.source}"
|
||||
|
||||
@api.depends('destination', 'destination_type', 'raw_data')
|
||||
def _compute_formatted_destination(self):
|
||||
"""Calcule l'affichage formaté de la destination en fonction de son type"""
|
||||
for record in self:
|
||||
if record.destination_type == 'any' or not record.destination:
|
||||
record.formatted_destination = "N'importe où"
|
||||
elif record.destination_type == 'object':
|
||||
try:
|
||||
rule_data = json.loads(record.raw_data) if record.raw_data else {}
|
||||
object_name = rule_data.get('dst_object_name', record.destination)
|
||||
record.formatted_destination = f"Objet: {object_name}"
|
||||
except (json.JSONDecodeError, AttributeError):
|
||||
record.formatted_destination = record.destination
|
||||
elif record.destination_type == 'network':
|
||||
record.formatted_destination = f"Réseau: {record.destination}"
|
||||
else:
|
||||
record.formatted_destination = f"IP: {record.destination}"
|
||||
|
||||
@api.depends('action', 'protocol', 'formatted_source', 'formatted_destination', 'src_port', 'dst_port')
|
||||
def _compute_rule_summary(self):
|
||||
"""Calcule un résumé lisible de la règle de pare-feu
|
||||
|
||||
|
|
@ -173,8 +390,8 @@ class UnifiFirewallRule(models.Model):
|
|||
if record.protocol:
|
||||
parts.append(record.protocol.upper())
|
||||
|
||||
src_addr = record.source or 'n\'importe où'
|
||||
dst_addr = record.destination or 'n\'importe où'
|
||||
src_addr = record.formatted_source or "N'importe où"
|
||||
dst_addr = record.formatted_destination or "N'importe où"
|
||||
|
||||
src = f"de {src_addr}"
|
||||
if record.src_port:
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
# 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
|
||||
|
||||
|
|
@ -12,7 +13,7 @@ from datetime import datetime
|
|||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
class UnifiNetwork(models.Model):
|
||||
class UnifiNetwork(models.Model, UnifiCommonMixin):
|
||||
"""Modèle pour gérer les réseaux UniFi
|
||||
|
||||
Ce modèle représente les réseaux configurés dans les sites UniFi,
|
||||
|
|
@ -43,6 +44,8 @@ class UnifiNetwork(models.Model):
|
|||
('wan', 'WAN'),
|
||||
('lan', 'LAN'),
|
||||
('vpn', 'VPN'),
|
||||
('site-vpn', 'Site VPN'),
|
||||
('remote-user-vpn', 'Remote User VPN'),
|
||||
('vlan-only', 'VLAN Only'),
|
||||
('other', 'Autre')
|
||||
],
|
||||
|
|
@ -135,6 +138,17 @@ class UnifiNetwork(models.Model):
|
|||
help='Données brutes du réseau au format JSON'
|
||||
)
|
||||
|
||||
raw_data_json = fields.Text(
|
||||
string='Données brutes (JSON)',
|
||||
compute='_compute_raw_data_json',
|
||||
help='Données brutes du réseau 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',
|
||||
|
|
@ -243,25 +257,149 @@ class UnifiNetwork(models.Model):
|
|||
# Création d'un nouveau réseau
|
||||
return self.create(vals)
|
||||
|
||||
def sync_networks(self, site):
|
||||
def sync_networks(self):
|
||||
"""Synchronise les réseaux depuis l'API UniFi
|
||||
|
||||
Args:
|
||||
site: L'enregistrement du site UniFi
|
||||
|
||||
Cette méthode est conçue pour être appelée depuis un bouton dans l'interface utilisateur.
|
||||
Elle utilise le site associé à l'enregistrement en cours.
|
||||
|
||||
Returns:
|
||||
bool: True si la synchronisation a réussi, False sinon
|
||||
"""
|
||||
self.ensure_one()
|
||||
|
||||
# Utiliser le site associé à ce réseau
|
||||
site = self.site_id
|
||||
if not site:
|
||||
_logger.error("Impossible de synchroniser: aucun site associé à ce réseau")
|
||||
return False
|
||||
|
||||
# Déterminer quelle méthode utiliser en fonction du type d'API
|
||||
if site.api_type == 'controller':
|
||||
return self._sync_networks_controller(site)
|
||||
elif site.api_type == 'site_manager':
|
||||
return self._sync_networks_site_manager(site)
|
||||
|
||||
def sync_networks_from_list(self):
|
||||
"""Synchronise tous les réseaux du site actuel depuis la vue liste
|
||||
|
||||
Cette méthode vérifie si tous les réseaux affichés appartiennent au même site,
|
||||
puis appelle la méthode de synchronisation appropriée.
|
||||
|
||||
Returns:
|
||||
dict: Action de rafraîchissement de la vue ou notification d'erreur
|
||||
"""
|
||||
# Vérifier si tous les réseaux appartiennent au même site
|
||||
sites = self.mapped('site_id')
|
||||
|
||||
if not sites:
|
||||
return {
|
||||
'type': 'ir.actions.client',
|
||||
'tag': 'display_notification',
|
||||
'params': {
|
||||
'title': _('Erreur'),
|
||||
'message': _('Aucun site associé aux réseaux sélectionnés.'),
|
||||
'sticky': False,
|
||||
'type': 'danger',
|
||||
}
|
||||
}
|
||||
|
||||
if len(sites) > 1:
|
||||
return {
|
||||
'type': 'ir.actions.client',
|
||||
'tag': 'display_notification',
|
||||
'params': {
|
||||
'title': _('Erreur'),
|
||||
'message': _('Les réseaux sélectionnés appartiennent à différents sites. Veuillez filtrer par site.'),
|
||||
'sticky': False,
|
||||
'type': 'danger',
|
||||
}
|
||||
}
|
||||
|
||||
# Tous les réseaux appartiennent au même site, appeler la méthode de synchronisation
|
||||
site = sites[0]
|
||||
|
||||
# Déterminer quelle méthode utiliser en fonction du type d'API
|
||||
try:
|
||||
if site.api_type == 'controller':
|
||||
self._sync_networks_controller(site)
|
||||
elif site.api_type == 'site_manager':
|
||||
self._sync_networks_site_manager(site)
|
||||
else:
|
||||
return {
|
||||
'type': 'ir.actions.client',
|
||||
'tag': 'display_notification',
|
||||
'params': {
|
||||
'title': _('Erreur'),
|
||||
'message': _('Type d\'API non pris en charge.'),
|
||||
'sticky': False,
|
||||
'type': 'danger',
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
'type': 'ir.actions.client',
|
||||
'tag': 'display_notification',
|
||||
'params': {
|
||||
'title': _('Succès'),
|
||||
'message': _('Synchronisation des réseaux terminée avec succès.'),
|
||||
'sticky': False,
|
||||
'type': 'success',
|
||||
}
|
||||
}
|
||||
except Exception as e:
|
||||
return {
|
||||
'type': 'ir.actions.client',
|
||||
'tag': 'display_notification',
|
||||
'params': {
|
||||
'title': _('Erreur'),
|
||||
'message': _(f'Erreur lors de la synchronisation: {str(e)}'),
|
||||
'sticky': False,
|
||||
'type': 'danger',
|
||||
}
|
||||
}
|
||||
|
||||
@api.model
|
||||
def sync_site_networks(self, site_id):
|
||||
"""Synchronise tous les réseaux d'un site spécifique
|
||||
|
||||
Cette méthode est conçue pour être appelée depuis un bouton dans la vue liste
|
||||
lorsque tous les réseaux affichés appartiennent au même site.
|
||||
|
||||
Args:
|
||||
site_id: ID du site dont les réseaux doivent être synchronisés
|
||||
|
||||
Returns:
|
||||
dict: Action de rafraîchissement de la vue
|
||||
"""
|
||||
# Récupérer le site
|
||||
site = self.env['unifi.site'].browse(site_id)
|
||||
if not site.exists():
|
||||
raise UserError(_("Le site sélectionné n'existe pas."))
|
||||
|
||||
# Déterminer quelle méthode utiliser en fonction du type d'API
|
||||
if site.api_type == 'controller':
|
||||
self._sync_networks_controller(site)
|
||||
elif site.api_type == 'site_manager':
|
||||
self._sync_networks_site_manager(site)
|
||||
else:
|
||||
_logger.error(f"Type d'API non pris en charge: {site.api_type}")
|
||||
return False
|
||||
return {
|
||||
'type': 'ir.actions.client',
|
||||
'tag': 'display_notification',
|
||||
'params': {
|
||||
'title': _('Erreur'),
|
||||
'message': _('Type d\'API non pris en charge.'),
|
||||
'sticky': False,
|
||||
'type': 'danger',
|
||||
}
|
||||
}
|
||||
|
||||
# Retourner une action pour rafraîchir la vue
|
||||
return {
|
||||
'type': 'ir.actions.client',
|
||||
'tag': 'reload',
|
||||
}
|
||||
|
||||
def _sync_networks_controller(self, site):
|
||||
"""Synchronise les réseaux depuis l'API Controller
|
||||
|
|
@ -272,8 +410,8 @@ class UnifiNetwork(models.Model):
|
|||
Returns:
|
||||
bool: True si la synchronisation a réussi, False sinon
|
||||
"""
|
||||
# Obtenir les données des réseaux depuis l'API Controller
|
||||
networks_data = self.env['unifi.site.controller'].get_network_data(site)
|
||||
# Obtenir les données des réseaux directement depuis le site
|
||||
networks_data = site._get_controller_network_data()
|
||||
if not networks_data:
|
||||
return False
|
||||
|
||||
|
|
@ -292,8 +430,8 @@ class UnifiNetwork(models.Model):
|
|||
Returns:
|
||||
bool: True si la synchronisation a réussi, False sinon
|
||||
"""
|
||||
# Obtenir les données des réseaux depuis l'API Site Manager
|
||||
networks_data = self.env['unifi.site.manager'].get_network_data(site)
|
||||
# Obtenir les données des réseaux directement depuis le site
|
||||
networks_data = site._get_site_manager_network_data()
|
||||
if not networks_data:
|
||||
return False
|
||||
|
||||
|
|
|
|||
|
|
@ -4,10 +4,11 @@ import json
|
|||
import logging
|
||||
|
||||
from odoo import models, fields, api
|
||||
from .unifi_common import UnifiCommonMixin
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
class UnifiPortForward(models.Model):
|
||||
class UnifiPortForward(models.Model, UnifiCommonMixin):
|
||||
"""Représente une règle de redirection de port dans le système UniFi
|
||||
|
||||
Ce modèle stocke les règles de redirection de port qui permettent un accès externe
|
||||
|
|
@ -122,6 +123,17 @@ class UnifiPortForward(models.Model):
|
|||
help='Données brutes de la redirection de port au format JSON'
|
||||
)
|
||||
|
||||
raw_data_json = fields.Text(
|
||||
string='Données brutes (JSON)',
|
||||
compute='_compute_raw_data_json',
|
||||
help='Données brutes de la redirection de port 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)
|
||||
|
||||
# Champs calculés
|
||||
rule_summary = fields.Char(
|
||||
string='Résumé de la règle',
|
||||
|
|
|
|||
|
|
@ -27,6 +27,12 @@ class UnifiRoutingConfig(models.Model):
|
|||
help='Site this routing configuration belongs to'
|
||||
)
|
||||
|
||||
name = fields.Char(
|
||||
string='Name',
|
||||
required=True,
|
||||
help='Name of the routing configuration'
|
||||
)
|
||||
|
||||
ospf_enabled = fields.Boolean(
|
||||
string='OSPF Enabled',
|
||||
default=False,
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -62,6 +62,7 @@ class UnifiSyncJob(models.Model):
|
|||
('pending', 'Pending'),
|
||||
('running', 'Running'),
|
||||
('completed', 'Completed'),
|
||||
('partial', 'Partially Completed'),
|
||||
('failed', 'Failed'),
|
||||
('cancelled', 'Cancelled')
|
||||
],
|
||||
|
|
@ -237,7 +238,7 @@ class UnifiSyncJob(models.Model):
|
|||
|
||||
return {
|
||||
'name': _('API Logs'),
|
||||
'view_mode': 'tree,form',
|
||||
'view_mode': 'list,form',
|
||||
'res_model': 'unifi.api.log',
|
||||
'domain': [('sync_job_id', '=', self.id)],
|
||||
'type': 'ir.actions.act_window',
|
||||
|
|
|
|||
|
|
@ -4,10 +4,11 @@ import json
|
|||
import logging
|
||||
|
||||
from odoo import models, fields, api
|
||||
from .unifi_common import UnifiCommonMixin
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
class UnifiSystemInfo(models.Model):
|
||||
class UnifiSystemInfo(models.Model, UnifiCommonMixin):
|
||||
"""System information of the UniFi device"""
|
||||
_name = 'unifi.system.info'
|
||||
_description = 'UniFi System Information'
|
||||
|
|
@ -51,6 +52,17 @@ class UnifiSystemInfo(models.Model):
|
|||
# Raw data for debugging and future extensions
|
||||
raw_data = fields.Text(string='Raw Data')
|
||||
|
||||
raw_data_json = fields.Text(
|
||||
string='Données brutes (JSON)',
|
||||
compute='_compute_raw_data_json',
|
||||
help='Données brutes du système 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)
|
||||
|
||||
@api.depends('uptime')
|
||||
def _compute_uptime_human(self):
|
||||
for record in self:
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
# 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
|
||||
|
||||
|
|
@ -12,7 +13,7 @@ from datetime import datetime
|
|||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
class UnifiUser(models.Model):
|
||||
class UnifiUser(models.Model, UnifiCommonMixin):
|
||||
"""Modèle pour gérer les utilisateurs UniFi
|
||||
|
||||
Ce modèle représente les utilisateurs configurés dans les sites UniFi,
|
||||
|
|
@ -113,6 +114,17 @@ class UnifiUser(models.Model):
|
|||
help='Données brutes de l\'utilisateur au format JSON'
|
||||
)
|
||||
|
||||
raw_data_json = fields.Text(
|
||||
string='Données brutes (JSON)',
|
||||
compute='_compute_raw_data_json',
|
||||
help='Données brutes de l\'utilisateur 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',
|
||||
|
|
|
|||
|
|
@ -3,11 +3,12 @@
|
|||
import json
|
||||
import logging
|
||||
from odoo import models, fields, api, _
|
||||
from .unifi_common import UnifiCommonMixin
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
class UnifiVLAN(models.Model):
|
||||
class UnifiVLAN(models.Model, UnifiCommonMixin):
|
||||
"""Modèle pour gérer les VLANs UniFi
|
||||
|
||||
Ce modèle représente les VLANs configurés dans les sites UniFi.
|
||||
|
|
@ -99,6 +100,17 @@ class UnifiVLAN(models.Model):
|
|||
help='Données brutes JSON du VLAN depuis l\'API'
|
||||
)
|
||||
|
||||
raw_data_json = fields.Text(
|
||||
string='Données brutes (JSON)',
|
||||
compute='_compute_raw_data_json',
|
||||
help='Données brutes du VLAN 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)
|
||||
|
||||
_sql_constraints = [
|
||||
('vlan_id_site_uniq', 'unique(vlan_id, site_id)', 'L\'ID VLAN doit être unique par site!')
|
||||
]
|
||||
|
|
|
|||
241
unifi_integration/models/unifi_vpn.py
Normal file
241
unifi_integration/models/unifi_vpn.py
Normal file
|
|
@ -0,0 +1,241 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from odoo import models, fields, api, _
|
||||
from .unifi_common import UnifiCommonMixin
|
||||
from odoo.exceptions import ValidationError
|
||||
import logging
|
||||
import json
|
||||
from pprint import pformat
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
class UnifiVpn(models.Model, UnifiCommonMixin):
|
||||
"""Configuration VPN pour le système UniFi
|
||||
|
||||
Ce modèle stocke les configurations VPN pour les contrôleurs UniFi.
|
||||
Il gère les tunnels VPN IPsec et autres types de VPN.
|
||||
|
||||
Les configurations VPN sont liées à un site spécifique et sont automatiquement supprimées
|
||||
lorsque le site est supprimé (cascade).
|
||||
"""
|
||||
_name = 'unifi.vpn'
|
||||
_description = 'UniFi VPN Configuration'
|
||||
_rec_name = 'name'
|
||||
_order = 'name'
|
||||
|
||||
site_id = fields.Many2one(
|
||||
comodel_name='unifi.site',
|
||||
string='Site',
|
||||
required=True,
|
||||
ondelete='cascade',
|
||||
help='Site this VPN configuration belongs to'
|
||||
)
|
||||
|
||||
name = fields.Char(
|
||||
string='Name',
|
||||
required=True,
|
||||
help='Name of this VPN configuration'
|
||||
)
|
||||
|
||||
vpn_type = fields.Selection(
|
||||
selection=[
|
||||
('ipsec-vpn', 'IPsec VPN'),
|
||||
('l2tp-vpn', 'L2TP VPN'),
|
||||
('openvpn', 'OpenVPN'),
|
||||
('wireguard', 'WireGuard'),
|
||||
('other', 'Other')
|
||||
],
|
||||
string='VPN Type',
|
||||
default='ipsec-vpn',
|
||||
help='Type of VPN connection'
|
||||
)
|
||||
|
||||
enabled = fields.Boolean(
|
||||
string='Enabled',
|
||||
default=True,
|
||||
help='Whether this VPN configuration is active'
|
||||
)
|
||||
|
||||
peer_ip = fields.Char(
|
||||
string='Peer IP',
|
||||
help='IP address of the remote VPN peer'
|
||||
)
|
||||
|
||||
local_ip = fields.Char(
|
||||
string='Local IP',
|
||||
help='Local IP address for this VPN connection'
|
||||
)
|
||||
|
||||
remote_subnets = fields.Char(
|
||||
string='Remote Subnets',
|
||||
help='Comma-separated list of remote subnets accessible through this VPN'
|
||||
)
|
||||
|
||||
interface = fields.Char(
|
||||
string='Interface',
|
||||
help='Network interface used for this VPN'
|
||||
)
|
||||
|
||||
purpose = fields.Char(
|
||||
string='Purpose',
|
||||
help='Purpose of this VPN connection'
|
||||
)
|
||||
|
||||
unifi_id = fields.Char(
|
||||
string='UniFi ID',
|
||||
help='ID of this VPN configuration in the UniFi system'
|
||||
)
|
||||
|
||||
last_sync = fields.Datetime(
|
||||
string='Last Synchronization',
|
||||
help='Last time this VPN configuration was synchronized with the UniFi system'
|
||||
)
|
||||
|
||||
raw_data = fields.Text(
|
||||
string='Raw Data',
|
||||
help='Raw VPN configuration data in JSON format'
|
||||
)
|
||||
|
||||
raw_data_json = fields.Text(
|
||||
string='Données brutes (JSON)',
|
||||
compute='_compute_raw_data_json',
|
||||
help='Données brutes de la configuration VPN 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)
|
||||
|
||||
formatted_data = fields.Html(
|
||||
string='Formatted Configuration',
|
||||
compute='_compute_formatted_data',
|
||||
help='Formatted view of the VPN configuration data'
|
||||
)
|
||||
|
||||
@api.depends('raw_data')
|
||||
def _compute_formatted_data(self):
|
||||
"""Transforme les données brutes JSON en format HTML lisible"""
|
||||
for record in self:
|
||||
if not record.raw_data:
|
||||
record.formatted_data = "<p><em>Aucune donnée disponible</em></p>"
|
||||
continue
|
||||
|
||||
try:
|
||||
# Analyser les données JSON
|
||||
data = json.loads(record.raw_data)
|
||||
|
||||
# Créer une représentation HTML formatée
|
||||
html = "<div style='font-family: monospace;'>"
|
||||
|
||||
# Informations générales
|
||||
html += "<h3 style='color: #2C3E50;'>Informations générales</h3>"
|
||||
html += "<table style='width: 100%; border-collapse: collapse;'>"
|
||||
html += f"<tr><td style='padding: 4px; font-weight: bold;'>Nom:</td><td>{data.get('name', 'N/A')}</td></tr>"
|
||||
html += f"<tr><td style='padding: 4px; font-weight: bold;'>Type:</td><td>{data.get('vpn_type', 'N/A')}</td></tr>"
|
||||
html += f"<tr><td style='padding: 4px; font-weight: bold;'>Activé:</td><td>{'Oui' if data.get('enabled') else 'Non'}</td></tr>"
|
||||
html += f"<tr><td style='padding: 4px; font-weight: bold;'>Interface:</td><td>{data.get('ifname', 'N/A')}</td></tr>"
|
||||
html += f"<tr><td style='padding: 4px; font-weight: bold;'>But:</td><td>{data.get('purpose', 'N/A')}</td></tr>"
|
||||
html += "</table>"
|
||||
|
||||
# Configuration IPsec
|
||||
if data.get('vpn_type') == 'ipsec-vpn':
|
||||
html += "<h3 style='color: #2C3E50; margin-top: 15px;'>Configuration IPsec</h3>"
|
||||
html += "<table style='width: 100%; border-collapse: collapse;'>"
|
||||
html += f"<tr><td style='padding: 4px; font-weight: bold;'>IP Pair:</td><td>{data.get('ipsec_peer_ip', 'N/A')}</td></tr>"
|
||||
html += f"<tr><td style='padding: 4px; font-weight: bold;'>IP Locale:</td><td>{data.get('ipsec_local_ip', 'N/A')}</td></tr>"
|
||||
html += f"<tr><td style='padding: 4px; font-weight: bold;'>Interface:</td><td>{data.get('ipsec_interface', 'N/A')}</td></tr>"
|
||||
html += f"<tr><td style='padding: 4px; font-weight: bold;'>Échange de clés:</td><td>{data.get('ipsec_key_exchange', 'N/A')}</td></tr>"
|
||||
html += f"<tr><td style='padding: 4px; font-weight: bold;'>Profil:</td><td>{data.get('ipsec_profile', 'N/A')}</td></tr>"
|
||||
html += f"<tr><td style='padding: 4px; font-weight: bold;'>PFS:</td><td>{'Oui' if data.get('ipsec_pfs') else 'Non'}</td></tr>"
|
||||
html += f"<tr><td style='padding: 4px; font-weight: bold;'>Routage dynamique:</td><td>{'Oui' if data.get('ipsec_dynamic_routing') else 'Non'}</td></tr>"
|
||||
html += "</table>"
|
||||
|
||||
# Paramètres IKE
|
||||
html += "<h4 style='color: #2C3E50; margin-top: 10px;'>Paramètres IKE</h4>"
|
||||
html += "<table style='width: 100%; border-collapse: collapse;'>"
|
||||
html += f"<tr><td style='padding: 4px; font-weight: bold;'>Groupe DH:</td><td>{data.get('ipsec_ike_dh_group', 'N/A')}</td></tr>"
|
||||
html += f"<tr><td style='padding: 4px; font-weight: bold;'>Chiffrement:</td><td>{data.get('ipsec_ike_encryption', 'N/A')}</td></tr>"
|
||||
html += f"<tr><td style='padding: 4px; font-weight: bold;'>Hash:</td><td>{data.get('ipsec_ike_hash', 'N/A')}</td></tr>"
|
||||
html += f"<tr><td style='padding: 4px; font-weight: bold;'>Durée de vie (s):</td><td>{data.get('ipsec_ike_lifetime', 'N/A')}</td></tr>"
|
||||
html += "</table>"
|
||||
|
||||
# Paramètres ESP
|
||||
html += "<h4 style='color: #2C3E50; margin-top: 10px;'>Paramètres ESP</h4>"
|
||||
html += "<table style='width: 100%; border-collapse: collapse;'>"
|
||||
html += f"<tr><td style='padding: 4px; font-weight: bold;'>Groupe DH:</td><td>{data.get('ipsec_esp_dh_group', 'N/A')}</td></tr>"
|
||||
html += f"<tr><td style='padding: 4px; font-weight: bold;'>Chiffrement:</td><td>{data.get('ipsec_esp_encryption', 'N/A')}</td></tr>"
|
||||
html += f"<tr><td style='padding: 4px; font-weight: bold;'>Hash:</td><td>{data.get('ipsec_esp_hash', 'N/A')}</td></tr>"
|
||||
html += f"<tr><td style='padding: 4px; font-weight: bold;'>Durée de vie (s):</td><td>{data.get('ipsec_esp_lifetime', 'N/A')}</td></tr>"
|
||||
html += "</table>"
|
||||
|
||||
# Sous-réseaux distants
|
||||
remote_subnets = data.get('remote_vpn_subnets', [])
|
||||
if remote_subnets:
|
||||
html += "<h3 style='color: #2C3E50; margin-top: 15px;'>Sous-réseaux distants</h3>"
|
||||
html += "<ul style='margin-top: 5px;'>"
|
||||
for subnet in remote_subnets:
|
||||
html += f"<li>{subnet}</li>"
|
||||
html += "</ul>"
|
||||
|
||||
html += "</div>"
|
||||
record.formatted_data = html
|
||||
except Exception as e:
|
||||
_logger.error("Erreur lors du formatage des données VPN: %s", str(e))
|
||||
record.formatted_data = f"<p><em>Erreur lors du formatage des données: {str(e)}</em></p>"
|
||||
|
||||
@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)
|
||||
446
unifi_integration/models/unifi_wifi.py
Normal file
446
unifi_integration/models/unifi_wifi.py
Normal file
|
|
@ -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')
|
||||
|
|
@ -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
|
||||
|
|
|
@ -56,5 +56,30 @@
|
|||
<field name="perm_create" eval="True"/>
|
||||
<field name="perm_unlink" eval="True"/>
|
||||
</record>
|
||||
|
||||
<!-- Sync Job Rules -->
|
||||
<!-- User Group Rule: Read-only access to sync jobs -->
|
||||
<record id="rule_unifi_sync_job_user" model="ir.rule">
|
||||
<field name="name">UniFi Sync Job User Access</field>
|
||||
<field name="model_id" ref="unifi_integration.model_unifi_sync_job"/>
|
||||
<field name="domain_force">[(1, '=', 1)]</field>
|
||||
<field name="groups" eval="[(4, ref('unifi_integration.group_unifi_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>
|
||||
|
||||
<!-- Manager Group Rule: Full access to sync jobs -->
|
||||
<record id="rule_unifi_sync_job_manager" model="ir.rule">
|
||||
<field name="name">UniFi Sync Job Manager Access</field>
|
||||
<field name="model_id" ref="unifi_integration.model_unifi_sync_job"/>
|
||||
<field name="domain_force">[(1, '=', 1)]</field>
|
||||
<field name="groups" eval="[(4, ref('unifi_integration.group_unifi_manager'))]"/>
|
||||
<field name="perm_read" eval="True"/>
|
||||
<field name="perm_write" eval="True"/>
|
||||
<field name="perm_create" eval="True"/>
|
||||
<field name="perm_unlink" eval="True"/>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
|
|
|
|||
|
|
@ -75,10 +75,10 @@
|
|||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
<div class="oe_chatter">
|
||||
<field name="message_follower_ids" widget="mail_followers"/>
|
||||
<field name="message_ids" widget="mail_thread"/>
|
||||
</div>
|
||||
<chatter>
|
||||
<field name="message_follower_ids"/>
|
||||
<field name="message_ids"/>
|
||||
</chatter>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
|
|
|||
|
|
@ -49,24 +49,24 @@
|
|||
<notebook>
|
||||
<page string="Request" name="request">
|
||||
<group>
|
||||
<field name="request_headers" widget="ace" options="{'mode': 'json'}" readonly="1"/>
|
||||
<field name="request_params" widget="ace" options="{'mode': 'json'}" readonly="1"/>
|
||||
<field name="request_body" widget="ace" options="{'mode': 'json'}" readonly="1"/>
|
||||
<field name="request_headers" widget="ace" options="{'language': 'json'}" readonly="1"/>
|
||||
<field name="request_params" widget="ace" options="{'language': 'json'}" readonly="1"/>
|
||||
<field name="request_body" widget="ace" options="{'language': 'json'}" readonly="1"/>
|
||||
</group>
|
||||
</page>
|
||||
<page string="Response" name="response">
|
||||
<group>
|
||||
<field name="response_headers" widget="ace" options="{'mode': 'json'}" readonly="1"/>
|
||||
<field name="response_body" widget="ace" options="{'mode': 'json'}" readonly="1"/>
|
||||
<field name="response_headers" widget="ace" options="{'language': 'json'}" readonly="1"/>
|
||||
<field name="response_body" widget="ace" options="{'language': 'json'}" readonly="1"/>
|
||||
<field name="error_message" invisible="error_message == False" readonly="1"/>
|
||||
</group>
|
||||
</page>
|
||||
<page string="Formatted" name="formatted">
|
||||
<group>
|
||||
<separator string="Request"/>
|
||||
<field name="request_formatted" widget="ace" options="{'mode': 'text'}" readonly="1"/>
|
||||
<field name="request_formatted" widget="ace" options="{'language': 'text'}" readonly="1"/>
|
||||
<separator string="Response"/>
|
||||
<field name="response_formatted" widget="ace" options="{'mode': 'text'}" readonly="1"/>
|
||||
<field name="response_formatted" widget="ace" options="{'language': 'text'}" readonly="1"/>
|
||||
</group>
|
||||
</page>
|
||||
</notebook>
|
||||
|
|
|
|||
|
|
@ -54,7 +54,10 @@
|
|||
</group>
|
||||
<notebook>
|
||||
<page string="Données brutes">
|
||||
<field name="raw_data" widget="ace" options="{'mode': 'json'}"/>
|
||||
<group>
|
||||
<field name="raw_data" widget="ace" options="{'language': 'json'}" nolabel="1" invisible="1"/>
|
||||
<field name="raw_data_json" readonly="1" nolabel="1"/>
|
||||
</group>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
|
|
|
|||
|
|
@ -21,11 +21,14 @@
|
|||
<field name="protocol"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="source"/>
|
||||
<field name="src_port"/>
|
||||
<field name="destination"/>
|
||||
<field name="dst_port"/>
|
||||
<field name="detailed_rule_type"/>
|
||||
<field name="rule_type"/>
|
||||
<field name="source_type"/>
|
||||
<field name="formatted_source"/>
|
||||
<field name="src_port"/>
|
||||
<field name="destination_type"/>
|
||||
<field name="formatted_destination"/>
|
||||
<field name="dst_port"/>
|
||||
</group>
|
||||
</group>
|
||||
<notebook>
|
||||
|
|
@ -40,9 +43,10 @@
|
|||
<field name="updated_at"/>
|
||||
<field name="last_sync"/>
|
||||
</group>
|
||||
<group string="Données brutes">
|
||||
<field name="raw_data" widget="ace" options="{'mode': 'json'}"/>
|
||||
</group>
|
||||
</page>
|
||||
<page string="Données brutes">
|
||||
<field name="raw_data" widget="ace" options="{'language': 'json'}" invisible="1"/>
|
||||
<field name="raw_data_json" readonly="1"/>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
|
|
@ -62,8 +66,12 @@
|
|||
<field name="enabled"/>
|
||||
<field name="action"/>
|
||||
<field name="protocol"/>
|
||||
<field name="detailed_rule_type"/>
|
||||
<field name="source_type"/>
|
||||
<field name="formatted_source"/>
|
||||
<field name="destination_type"/>
|
||||
<field name="formatted_destination"/>
|
||||
<field name="rule_summary"/>
|
||||
<field name="rule_type"/>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
|
|
@ -76,8 +84,9 @@
|
|||
<search string="Rechercher des règles de pare-feu UniFi">
|
||||
<field name="name" string="Nom"/>
|
||||
<field name="site_id" string="Site"/>
|
||||
<field name="source" string="Source"/>
|
||||
<field name="destination" string="Destination"/>
|
||||
<field name="formatted_source" string="Source"/>
|
||||
<field name="formatted_destination" string="Destination"/>
|
||||
<field name="detailed_rule_type" string="Type détaillé"/>
|
||||
<separator/>
|
||||
<filter string="Activées" name="enabled" domain="[('enabled', '=', True)]"/>
|
||||
<filter string="Désactivées" name="disabled" domain="[('enabled', '=', False)]"/>
|
||||
|
|
@ -92,6 +101,9 @@
|
|||
<filter string="Tous" name="protocol_all" domain="[('protocol', '=', 'all')]"/>
|
||||
<group expand="0" string="Grouper par">
|
||||
<filter string="Site" name="group_by_site" context="{'group_by': 'site_id'}"/>
|
||||
<filter string="Type détaillé" name="group_by_detailed_type" context="{'group_by': 'detailed_rule_type'}"/>
|
||||
<filter string="Type de source" name="group_by_source_type" context="{'group_by': 'source_type'}"/>
|
||||
<filter string="Type de destination" name="group_by_destination_type" context="{'group_by': 'destination_type'}"/>
|
||||
<filter string="Action" name="group_by_action" context="{'group_by': 'action'}"/>
|
||||
<filter string="Protocole" name="group_by_protocol" context="{'group_by': 'protocol'}"/>
|
||||
<filter string="Type de règle" name="group_by_rule_type" context="{'group_by': 'rule_type'}"/>
|
||||
|
|
|
|||
|
|
@ -15,6 +15,9 @@
|
|||
<field name="site_id"/>
|
||||
<field name="enabled"/>
|
||||
<field name="last_sync"/>
|
||||
<header>
|
||||
<button name="sync_networks_from_list" string="Synchroniser tous les réseaux" type="object" class="btn-primary"/>
|
||||
</header>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
|
|
@ -68,7 +71,10 @@
|
|||
</group>
|
||||
</page>
|
||||
<page string="Données brutes">
|
||||
<field name="raw_data" widget="ace" options="{'mode': 'json'}"/>
|
||||
<group>
|
||||
<field name="raw_data" widget="ace" options="{'language': 'json'}" nolabel="1" invisible="1"/>
|
||||
<field name="raw_data_json" readonly="1" nolabel="1"/>
|
||||
</group>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
|
|
|
|||
|
|
@ -29,20 +29,25 @@
|
|||
<group string="Description">
|
||||
<field name="description" nolabel="1" placeholder="Description détaillée de la redirection de port..."/>
|
||||
</group>
|
||||
<group string="Informations techniques" groups="base.group_system">
|
||||
<group>
|
||||
<field name="rule_id"/>
|
||||
<field name="rule_index"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="created_at"/>
|
||||
<field name="updated_at"/>
|
||||
<field name="last_sync"/>
|
||||
</group>
|
||||
</group>
|
||||
<group string="Données brutes" groups="base.group_system">
|
||||
<field name="raw_data" nolabel="1"/>
|
||||
</group>
|
||||
<notebook>
|
||||
<page string="Informations techniques" groups="base.group_system">
|
||||
<group>
|
||||
<group>
|
||||
<field name="rule_id"/>
|
||||
<field name="rule_index"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="created_at"/>
|
||||
<field name="updated_at"/>
|
||||
<field name="last_sync"/>
|
||||
</group>
|
||||
</group>
|
||||
</page>
|
||||
<page string="Données brutes" groups="base.group_system">
|
||||
<field name="raw_data" widget="ace" options="{'language': 'json'}" readonly="1" invisible="1"/>
|
||||
<field name="raw_data_json" readonly="1"/>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
|
|
|
|||
|
|
@ -24,10 +24,7 @@
|
|||
<field name="model">unifi.site</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="UniFi Site">
|
||||
<header>
|
||||
<button name="action_sync_now" string="Synchronize Now" type="object" class="btn-primary"/>
|
||||
<button name="action_test_connection" string="Test Connection" type="object" class="btn-secondary"/>
|
||||
</header>
|
||||
|
||||
<sheet>
|
||||
<div class="oe_button_box" name="button_box">
|
||||
<!-- Boutons statistiques communs à tous les types d'API -->
|
||||
|
|
@ -35,7 +32,7 @@
|
|||
type="object"
|
||||
class="oe_stat_button"
|
||||
icon="fa-history">
|
||||
<div class="o_stat_info">
|
||||
<div class="o_field_widget o_stat_info">
|
||||
<span class="o_stat_text">API Logs</span>
|
||||
</div>
|
||||
</button>
|
||||
|
|
@ -43,7 +40,7 @@
|
|||
type="object"
|
||||
class="oe_stat_button"
|
||||
icon="fa-refresh">
|
||||
<div class="o_stat_info">
|
||||
<div class="o_field_widget o_stat_info">
|
||||
<span class="o_stat_text">Sync Jobs</span>
|
||||
</div>
|
||||
</button>
|
||||
|
|
@ -53,44 +50,79 @@
|
|||
type="object"
|
||||
class="oe_stat_button"
|
||||
icon="fa-sitemap"
|
||||
invisible="context.get('api_type') != 'controller'">
|
||||
invisible="api_type != 'controller'">
|
||||
<field name="network_count" widget="statinfo" string="Networks"/>
|
||||
</button>
|
||||
<button name="action_view_devices"
|
||||
type="object"
|
||||
class="oe_stat_button"
|
||||
icon="fa-server"
|
||||
invisible="context.get('api_type') != 'controller'">
|
||||
invisible="api_type != 'controller'">
|
||||
<field name="device_count" widget="statinfo" string="Devices"/>
|
||||
</button>
|
||||
<button name="action_view_users"
|
||||
type="object"
|
||||
class="oe_stat_button"
|
||||
icon="fa-users"
|
||||
invisible="context.get('api_type') != 'controller'">
|
||||
invisible="api_type != 'controller'">
|
||||
<field name="user_count" widget="statinfo" string="Users"/>
|
||||
</button>
|
||||
<button name="action_view_vlans"
|
||||
type="object"
|
||||
class="oe_stat_button"
|
||||
icon="fa-tags"
|
||||
invisible="api_type != 'controller'">
|
||||
<field name="vlan_count" widget="statinfo" string="VLANs"/>
|
||||
</button>
|
||||
<button name="action_view_port_forwards"
|
||||
type="object"
|
||||
class="oe_stat_button"
|
||||
icon="fa-exchange"
|
||||
invisible="api_type != 'controller'">
|
||||
<field name="port_forward_count" widget="statinfo" string="Port Forwards"/>
|
||||
</button>
|
||||
<button name="action_view_system_info"
|
||||
type="object"
|
||||
class="oe_stat_button"
|
||||
icon="fa-info-circle"
|
||||
invisible="api_type != 'controller'">
|
||||
<field name="system_info_count" widget="statinfo" string="System Info"/>
|
||||
</button>
|
||||
<button name="action_view_dns"
|
||||
type="object"
|
||||
class="oe_stat_button"
|
||||
icon="fa-globe"
|
||||
invisible="api_type != 'controller'">
|
||||
<field name="dns_count" widget="statinfo" string="DNS Entries"/>
|
||||
</button>
|
||||
<button name="action_view_vpn"
|
||||
type="object"
|
||||
class="oe_stat_button"
|
||||
icon="fa-lock"
|
||||
invisible="api_type != 'controller'">
|
||||
<field name="vpn_count" widget="statinfo" string="VPN"/>
|
||||
</button>
|
||||
|
||||
<!-- Boutons spécifiques au Site Manager -->
|
||||
<button name="action_view_firewall_rules"
|
||||
type="object"
|
||||
class="oe_stat_button"
|
||||
icon="fa-shield"
|
||||
invisible="context.get('api_type') != 'site_manager'">
|
||||
<field name="firewall_rule_count" widget="statinfo" string="Firewall Rules"/>
|
||||
icon="fa-shield">
|
||||
<field name="firewall_rule_count" widget="statinfo" string="Règles pare-feu"/>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<widget name="web_ribbon" title="Inactive" bg_color="text-bg-danger" invisible="active"/>
|
||||
<div class="oe_title">
|
||||
<h1>
|
||||
<field name="name" placeholder="Site Name"/>
|
||||
<h1 class="d-flex flex-row">
|
||||
<field name="name" class="o_text_overflow" placeholder="Site Name"/>
|
||||
</h1>
|
||||
</div>
|
||||
<group>
|
||||
<group>
|
||||
<field name="site_id"/>
|
||||
<field name="api_type" widget="radio"/>
|
||||
<field name="active"/>
|
||||
<field name="active" invisible="1"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="timestamp" readonly="1"/>
|
||||
|
|
@ -100,28 +132,28 @@
|
|||
</group>
|
||||
|
||||
<!-- Informations de connexion pour Controller API -->
|
||||
<group string="Informations de connexion" invisible="context.get('api_type') != 'controller'">
|
||||
<group string="Informations de connexion" invisible="api_type != 'controller'">
|
||||
<group>
|
||||
<field name="host" required="context.get('api_type') == 'controller'"/>
|
||||
<field name="port" required="context.get('api_type') == 'controller'"/>
|
||||
<field name="host" required="api_type == 'controller'"/>
|
||||
<field name="port" required="api_type == 'controller'"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="username" required="context.get('api_type') == 'controller'"/>
|
||||
<field name="password" password="True" required="context.get('api_type') == 'controller'"/>
|
||||
<field name="username" required="api_type == 'controller'"/>
|
||||
<field name="password" password="True" required="api_type == 'controller'"/>
|
||||
<field name="verify_ssl"/>
|
||||
</group>
|
||||
</group>
|
||||
|
||||
<!-- Informations de connexion pour Site Manager API -->
|
||||
<group string="Informations de connexion" invisible="context.get('api_type') != 'site_manager'">
|
||||
<group string="Informations de connexion" invisible="api_type != 'site_manager'">
|
||||
<group>
|
||||
<field name="api_key" required="context.get('api_type') == 'site_manager'"/>
|
||||
<field name="api_key" required="api_type == 'site_manager'"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="mfa_enabled" invisible="context.get('api_type') != 'site_manager'"/>
|
||||
<field name="mfa_enabled" invisible="api_type != 'site_manager'"/>
|
||||
<field name="mfa_token" password="True"
|
||||
invisible="context.get('api_type') != 'site_manager' or not context.get('mfa_enabled')"
|
||||
required="context.get('api_type') == 'site_manager' and context.get('mfa_enabled')"/>
|
||||
invisible="api_type != 'site_manager' or not mfa_enabled"
|
||||
required="api_type == 'site_manager' and mfa_enabled"/>
|
||||
</group>
|
||||
</group>
|
||||
<notebook>
|
||||
|
|
@ -133,113 +165,298 @@
|
|||
</page>
|
||||
|
||||
<!-- Onglets spécifiques au Controller -->
|
||||
<page string="Networks" name="networks" invisible="context.get('api_type') != 'controller'">
|
||||
<div class="oe_button_box">
|
||||
<button name="action_sync_networks" string="Synchronize Networks" type="object" class="btn btn-secondary"/>
|
||||
</div>
|
||||
<field name="network_ids" readonly="1">
|
||||
<list>
|
||||
<field name="name"/>
|
||||
</list>
|
||||
</field>
|
||||
</page>
|
||||
|
||||
<page string="Devices" name="devices" invisible="context.get('api_type') != 'controller'">
|
||||
<div class="oe_button_box">
|
||||
<button name="action_sync_devices" string="Synchronize Devices" type="object" class="btn btn-secondary"/>
|
||||
</div>
|
||||
<field name="device_ids" readonly="1">
|
||||
<list>
|
||||
<field name="name"/>
|
||||
</list>
|
||||
</field>
|
||||
</page>
|
||||
|
||||
<page string="Users" name="users" invisible="context.get('api_type') != 'controller'">
|
||||
<div class="oe_button_box">
|
||||
<button name="action_sync_users" string="Synchronize Users" type="object" class="btn btn-secondary"/>
|
||||
</div>
|
||||
<field name="user_ids" readonly="1">
|
||||
<list>
|
||||
<field name="name"/>
|
||||
</list>
|
||||
</field>
|
||||
</page>
|
||||
|
||||
<!-- Onglets spécifiques au Site Manager -->
|
||||
<page string="VLANs" name="vlans" invisible="context.get('api_type') != 'site_manager'">
|
||||
<div class="oe_button_box">
|
||||
<button name="action_sync_vlans" string="Synchronize VLANs" type="object" class="btn btn-secondary"/>
|
||||
</div>
|
||||
<field name="vlan_ids" readonly="1">
|
||||
<list>
|
||||
<field name="name"/>
|
||||
</list>
|
||||
</field>
|
||||
</page>
|
||||
|
||||
<page string="Firewall Rules" name="firewall_rules" invisible="context.get('api_type') != 'site_manager'">
|
||||
<div class="oe_button_box">
|
||||
<button name="action_sync_firewall_rules" string="Synchronize Firewall Rules" type="object" class="btn btn-secondary"/>
|
||||
</div>
|
||||
<field name="firewall_rule_ids" readonly="1">
|
||||
<list>
|
||||
<field name="name"/>
|
||||
</list>
|
||||
</field>
|
||||
</page>
|
||||
|
||||
<page string="Port Forwarding" name="port_forwarding" invisible="context.get('api_type') != 'site_manager'">
|
||||
<div class="oe_button_box">
|
||||
<button name="action_sync_port_forwards" string="Synchronize Port Forwards" type="object" class="btn btn-secondary"/>
|
||||
</div>
|
||||
<field name="port_forward_ids" readonly="1">
|
||||
<list>
|
||||
<field name="name"/>
|
||||
</list>
|
||||
</field>
|
||||
</page>
|
||||
|
||||
<page string="Routing" name="routing" invisible="context.get('api_type') != 'site_manager'">
|
||||
<div class="oe_button_box">
|
||||
<button name="action_sync_routing" string="Synchronize Routing" type="object" class="btn btn-primary"/>
|
||||
</div>
|
||||
<field name="routing_config_ids" readonly="1"/>
|
||||
</page>
|
||||
<page string="Connection Information" name="connection_info">
|
||||
<group invisible="context.get('api_type') != 'controller'">
|
||||
<group string="Controller API Settings">
|
||||
<button string="Configure Controller API" name="action_configure_controller" type="object" class="btn-primary"/>
|
||||
<field name="verify_ssl"/>
|
||||
</group>
|
||||
</group>
|
||||
<group invisible="context.get('api_type') != 'site_manager'">
|
||||
<group string="Site Manager API Settings">
|
||||
<button string="Configure Site Manager API" name="action_configure_site_manager" type="object" class="btn-primary"/>
|
||||
<field name="verify_ssl"/>
|
||||
</group>
|
||||
</group>
|
||||
</page>
|
||||
<page string="Synchronization" name="sync_settings">
|
||||
<page string="Networks" name="networks" invisible="api_type != 'controller'">
|
||||
<group>
|
||||
<field name="auto_sync"/>
|
||||
<field name="sync_interval" invisible="not context.get('auto_sync')"/>
|
||||
<field name="last_update" readonly="1"/>
|
||||
</group>
|
||||
<group string="Recent Sync Jobs" invisible="not context.get('sync_job_ids')">
|
||||
<field name="sync_job_ids" nolabel="1" readonly="1">
|
||||
<div class="w-100 mb-2">
|
||||
<button name="action_sync_networks" string="Synchronize Networks" type="object" class="btn btn-secondary"/>
|
||||
</div>
|
||||
<field name="network_ids" readonly="1" nolabel="1">
|
||||
<list>
|
||||
<field name="name"/>
|
||||
<field name="state"/>
|
||||
<field name="start_time"/>
|
||||
<field name="end_time"/>
|
||||
<field name="duration" widget="float_time"/>
|
||||
<field name="purpose"/>
|
||||
<field name="subnet"/>
|
||||
</list>
|
||||
</field>
|
||||
</group>
|
||||
</page>
|
||||
|
||||
<page string="Devices" name="devices" invisible="api_type != 'controller'">
|
||||
<group>
|
||||
<div class="w-100 mb-2">
|
||||
<button name="action_sync_devices" string="Synchronize Devices" type="object" class="btn btn-secondary"/>
|
||||
</div>
|
||||
<field name="device_ids" readonly="1" nolabel="1">
|
||||
<list>
|
||||
<field name="name"/>
|
||||
<field name="model"/>
|
||||
<field name="ip_address"/>
|
||||
</list>
|
||||
</field>
|
||||
</group>
|
||||
</page>
|
||||
|
||||
<page string="Users" name="users" invisible="api_type != 'controller'">
|
||||
<group>
|
||||
<div class="w-100 mb-2">
|
||||
<button name="action_sync_users" string="Synchronize Users" type="object" class="btn btn-secondary"/>
|
||||
</div>
|
||||
<field name="user_ids" readonly="1" nolabel="1">
|
||||
<list>
|
||||
<field name="name"/>
|
||||
<field name="email"/>
|
||||
</list>
|
||||
</field>
|
||||
</group>
|
||||
</page>
|
||||
|
||||
<page string="WiFi Networks" name="wifi_networks" invisible="api_type != 'controller'">
|
||||
<group>
|
||||
<div class="w-100 mb-2">
|
||||
<button name="action_sync_wifi" string="Synchronize WiFi Networks" type="object" class="btn btn-secondary"/>
|
||||
</div>
|
||||
<field name="wifi_ids" readonly="1" nolabel="1">
|
||||
<list>
|
||||
<field name="name"/>
|
||||
<field name="security"/>
|
||||
<field name="band"/>
|
||||
<field name="is_guest"/>
|
||||
<field name="enabled"/>
|
||||
</list>
|
||||
</field>
|
||||
</group>
|
||||
</page>
|
||||
|
||||
<page string="VLANs" name="vlans_controller" invisible="api_type != 'controller'">
|
||||
<group>
|
||||
<div class="w-100 mb-2">
|
||||
<button name="action_sync_vlans" string="Synchronize VLANs" type="object" class="btn btn-secondary"/>
|
||||
</div>
|
||||
<field name="vlan_ids" readonly="1" nolabel="1">
|
||||
<list>
|
||||
<field name="name"/>
|
||||
<field name="vlan_id"/>
|
||||
</list>
|
||||
</field>
|
||||
</group>
|
||||
</page>
|
||||
|
||||
<page string="Firewall Rules" name="firewall_rules_controller" invisible="api_type != 'controller'">
|
||||
<group>
|
||||
<div class="w-100 mb-2">
|
||||
<button name="action_sync_firewall_rules" string="Synchronize Firewall Rules" type="object" class="btn btn-secondary"/>
|
||||
</div>
|
||||
<field name="firewall_rule_ids" readonly="1" nolabel="1">
|
||||
<list>
|
||||
<field name="name"/>
|
||||
<field name="rule_type"/>
|
||||
</list>
|
||||
</field>
|
||||
</group>
|
||||
</page>
|
||||
|
||||
<page string="Port Forwarding" name="port_forwarding_controller" invisible="api_type != 'controller'">
|
||||
<group>
|
||||
<div class="w-100 mb-2">
|
||||
<button name="action_sync_port_forwards" string="Synchronize Port Forwards" type="object" class="btn btn-secondary"/>
|
||||
</div>
|
||||
<field name="port_forward_ids" readonly="1" nolabel="1">
|
||||
<list>
|
||||
<field name="name"/>
|
||||
<field name="fwd_port"/>
|
||||
<field name="dst_port"/>
|
||||
</list>
|
||||
</field>
|
||||
</group>
|
||||
</page>
|
||||
|
||||
<page string="Routing" name="routing_controller" invisible="api_type != 'controller'">
|
||||
<group>
|
||||
<div class="w-100 mb-2">
|
||||
<button name="action_sync_routing" string="Synchronize Routing" type="object" class="btn btn-secondary"/>
|
||||
</div>
|
||||
<field name="routing_config_ids" readonly="1" nolabel="1">
|
||||
<list>
|
||||
<field name="name"/>
|
||||
</list>
|
||||
</field>
|
||||
</group>
|
||||
</page>
|
||||
|
||||
<page string="DNS" name="dns_controller" invisible="api_type != 'controller'">
|
||||
<group>
|
||||
<div class="w-100 mb-2">
|
||||
<button name="action_sync_dns" string="Synchronize DNS" type="object" class="btn btn-secondary"/>
|
||||
</div>
|
||||
<field name="dns_ids" readonly="1" nolabel="1">
|
||||
<list>
|
||||
<field name="hostname"/>
|
||||
<field name="ip_address"/>
|
||||
</list>
|
||||
</field>
|
||||
</group>
|
||||
</page>
|
||||
|
||||
<page string="System Info" name="system_info_controller" invisible="api_type != 'controller'">
|
||||
<group>
|
||||
<div class="w-100 mb-2">
|
||||
<button name="action_sync_system_info" string="Synchronize System Info" type="object" class="btn btn-secondary"/>
|
||||
</div>
|
||||
<field name="system_info_ids" readonly="1" nolabel="1">
|
||||
<list>
|
||||
<field name="hostname"/>
|
||||
<field name="version"/>
|
||||
</list>
|
||||
</field>
|
||||
</group>
|
||||
</page>
|
||||
|
||||
<!-- Onglets spécifiques au Site Manager -->
|
||||
<page string="VLANs" name="vlans" invisible="api_type != 'site_manager'">
|
||||
<group>
|
||||
<div class="w-100 mb-2">
|
||||
<button name="action_sync_vlans" string="Synchronize VLANs" type="object" class="btn btn-secondary"/>
|
||||
</div>
|
||||
<field name="vlan_ids" readonly="1" nolabel="1">
|
||||
<list>
|
||||
<field name="name"/>
|
||||
<field name="vlan_id"/>
|
||||
</list>
|
||||
</field>
|
||||
</group>
|
||||
</page>
|
||||
|
||||
<page string="Firewall Rules" name="firewall_rules" invisible="api_type != 'site_manager'">
|
||||
<group>
|
||||
<div class="w-100 mb-2">
|
||||
<button name="action_sync_firewall_rules" string="Synchronize Firewall Rules" type="object" class="btn btn-secondary"/>
|
||||
</div>
|
||||
<field name="firewall_rule_ids" readonly="1" nolabel="1">
|
||||
<list>
|
||||
<field name="name"/>
|
||||
<field name="rule_type"/>
|
||||
</list>
|
||||
</field>
|
||||
</group>
|
||||
</page>
|
||||
|
||||
<page string="Port Forwarding" name="port_forwarding" invisible="api_type != 'site_manager'">
|
||||
<group>
|
||||
<div class="w-100 mb-2">
|
||||
<button name="action_sync_port_forwards" string="Synchronize Port Forwards" type="object" class="btn btn-secondary"/>
|
||||
</div>
|
||||
<field name="port_forward_ids" readonly="1" nolabel="1">
|
||||
<list>
|
||||
<field name="name"/>
|
||||
<field name="fwd_port"/>
|
||||
<field name="dst_port"/>
|
||||
</list>
|
||||
</field>
|
||||
</group>
|
||||
</page>
|
||||
|
||||
<page string="Routing" name="routing" invisible="api_type != 'site_manager'">
|
||||
<group>
|
||||
<div class="w-100 mb-2">
|
||||
<button name="action_sync_routing" string="Synchronize Routing" type="object" class="btn btn-secondary"/>
|
||||
</div>
|
||||
<field name="routing_config_ids" readonly="1" nolabel="1">
|
||||
<list>
|
||||
<field name="name"/>
|
||||
</list>
|
||||
</field>
|
||||
</group>
|
||||
</page>
|
||||
|
||||
<page string="DNS" name="dns" invisible="api_type != 'site_manager'">
|
||||
<group>
|
||||
<div class="w-100 mb-2">
|
||||
<button name="action_sync_dns" string="Synchronize DNS" type="object" class="btn btn-secondary"/>
|
||||
</div>
|
||||
<field name="dns_ids" readonly="1" nolabel="1">
|
||||
<list>
|
||||
<field name="hostname"/>
|
||||
<field name="ip_address"/>
|
||||
</list>
|
||||
</field>
|
||||
</group>
|
||||
</page>
|
||||
|
||||
<page string="System Info" name="system_info" invisible="api_type != 'site_manager'">
|
||||
<group>
|
||||
<div class="w-100 mb-2">
|
||||
<button name="action_sync_system_info" string="Synchronize System Info" type="object" class="btn btn-secondary"/>
|
||||
</div>
|
||||
<field name="system_info_ids" readonly="1" nolabel="1">
|
||||
<list>
|
||||
<field name="hostname"/>
|
||||
<field name="version"/>
|
||||
</list>
|
||||
</field>
|
||||
</group>
|
||||
</page>
|
||||
|
||||
<page string="DNS Config" name="dns_config">
|
||||
<group>
|
||||
<field name="dns_config_ids" readonly="1" nolabel="1">
|
||||
<list>
|
||||
<field name="site_id"/>
|
||||
<field name="enabled"/>
|
||||
<field name="filters_enabled"/>
|
||||
<field name="custom_dns"/>
|
||||
<field name="forwarding_enabled"/>
|
||||
</list>
|
||||
</field>
|
||||
</group>
|
||||
</page>
|
||||
|
||||
<page string="Auth Sessions" name="auth_sessions">
|
||||
<group>
|
||||
<field name="auth_session_id" readonly="1"/>
|
||||
</group>
|
||||
</page>
|
||||
|
||||
|
||||
<page string="Settings" name="settings">
|
||||
<group>
|
||||
<group string="API Connection Settings" name="api_settings">
|
||||
<field name="verify_ssl"/>
|
||||
<field name="timeout" widget="float_time"/>
|
||||
<field name="max_retries"/>
|
||||
</group>
|
||||
<group string="Sync Settings" name="sync_settings">
|
||||
<field name="auto_sync"/>
|
||||
<field name="sync_interval" invisible="not auto_sync"/>
|
||||
<div class="text-muted o_row ps-1" invisible="not auto_sync">
|
||||
<i class="fa fa-info-circle"/> L'automatisation s'exécutera tous les <field name="sync_interval" class="oe_inline" nolabel="1"/> minutes
|
||||
</div>
|
||||
<field name="last_update" readonly="1"/>
|
||||
</group>
|
||||
</group>
|
||||
|
||||
<group>
|
||||
<group string="Controller API Settings" invisible="api_type != 'controller'">
|
||||
<button string="Configure Controller API" name="action_configure_controller" type="object" class="btn-primary"/>
|
||||
</group>
|
||||
<group string="Site Manager API Settings" invisible="api_type != 'site_manager'">
|
||||
<button string="Configure Site Manager API" name="action_configure_site_manager" type="object" class="btn-primary"/>
|
||||
</group>
|
||||
</group>
|
||||
</page>
|
||||
|
||||
<page string="Sync Jobs" name="sync_jobs">
|
||||
<field name="sync_job_ids" readonly="1" nolabel="1">
|
||||
<list>
|
||||
<field name="name"/>
|
||||
<field name="state"/>
|
||||
<field name="start_time"/>
|
||||
<field name="end_time"/>
|
||||
<field name="duration" widget="float_time"/>
|
||||
</list>
|
||||
</field>
|
||||
</page>
|
||||
|
||||
<page string="API Logs" name="api_logs">
|
||||
<field name="api_log_ids" readonly="1">
|
||||
<field name="api_log_ids" readonly="1" nolabel="1">
|
||||
<list>
|
||||
<field name="create_date"/>
|
||||
<field name="api_type"/>
|
||||
|
|
@ -251,16 +468,17 @@
|
|||
</list>
|
||||
</field>
|
||||
</page>
|
||||
<page string="Raw Data" name="raw_data" groups="base.group_system">
|
||||
<field name="raw_data" widget="ace" options="{'mode': 'json'}"/>
|
||||
|
||||
<page string="Données brutes" name="raw_data" groups="base.group_system">
|
||||
<field name="raw_data" widget="ace" options="{'language': 'json'}" invisible="1"/>
|
||||
<field name="raw_data_json" readonly="1"/>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
<div class="oe_chatter">
|
||||
<field name="message_follower_ids" widget="mail_followers"/>
|
||||
<field name="activity_ids" widget="mail_activity"/>
|
||||
<field name="message_ids" widget="mail_thread"/>
|
||||
</div>
|
||||
<chatter>
|
||||
<field name="message_follower_ids"/>
|
||||
<field name="message_ids"/>
|
||||
</chatter>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
|
@ -277,13 +495,13 @@
|
|||
<field name="api_type" widget="radio" required="1"/>
|
||||
</group>
|
||||
|
||||
<group invisible="context.get('api_type') != 'controller'">
|
||||
<group invisible="api_type != 'controller'">
|
||||
<group string="Controller API Settings">
|
||||
<button string="Configure Controller API" name="action_configure_controller" type="object" class="btn-primary"/>
|
||||
</group>
|
||||
</group>
|
||||
|
||||
<group invisible="context.get('api_type') != 'site_manager'">
|
||||
<group invisible="api_type != 'site_manager'">
|
||||
<group string="Site Manager API Settings">
|
||||
<button string="Configure Site Manager API" name="action_configure_site_manager" type="object" class="btn-primary"/>
|
||||
</group>
|
||||
|
|
|
|||
|
|
@ -1,285 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<!-- UniFi Site List View -->
|
||||
<record id="view_unifi_site_list" model="ir.ui.view">
|
||||
<field name="name">unifi.site.list</field>
|
||||
<field name="model">unifi.site</field>
|
||||
<field name="type">list</field>
|
||||
<field name="arch" type="xml">
|
||||
<list string="UniFi Sites" js_class="unifi_site_list">
|
||||
<field name="name"/>
|
||||
<field name="site_id"/>
|
||||
<field name="api_type"/>
|
||||
<field name="active"/>
|
||||
<field name="last_sync" optional="show"/>
|
||||
<field name="create_date" optional="show"/>
|
||||
<button name="action_import_site" string="Importer" type="object" icon="fa-download" class="oe_highlight"/>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- UniFi Site Form View -->
|
||||
<record id="view_unifi_site_form" model="ir.ui.view">
|
||||
<field name="name">unifi.site.form</field>
|
||||
<field name="model">unifi.site</field>
|
||||
<field name="type">form</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="UniFi Site">
|
||||
<header>
|
||||
<button name="action_sync_now" string="Synchronize Now" type="object" class="btn-primary"/>
|
||||
<button name="action_test_connection" string="Test Connection" type="object" class="btn-secondary"/>
|
||||
</header>
|
||||
<sheet>
|
||||
<div class="oe_button_box" name="button_box">
|
||||
<!-- Boutons statistiques communs à tous les types d'API -->
|
||||
<button name="action_view_api_logs"
|
||||
type="object"
|
||||
class="oe_stat_button"
|
||||
icon="fa-history">
|
||||
<div class="o_stat_info">
|
||||
<span class="o_stat_text">API Logs</span>
|
||||
</div>
|
||||
</button>
|
||||
<button name="action_view_sync_jobs"
|
||||
type="object"
|
||||
class="oe_stat_button"
|
||||
icon="fa-refresh">
|
||||
<div class="o_stat_info">
|
||||
<span class="o_stat_text">Sync Jobs</span>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<!-- Boutons spécifiques au Controller -->
|
||||
<button name="action_view_networks"
|
||||
type="object"
|
||||
class="oe_stat_button"
|
||||
icon="fa-sitemap"
|
||||
invisible="api_type != 'controller'">
|
||||
<field name="network_count" widget="statinfo" string="Networks"/>
|
||||
</button>
|
||||
<button name="action_view_devices"
|
||||
type="object"
|
||||
class="oe_stat_button"
|
||||
icon="fa-server"
|
||||
invisible="api_type != 'controller'">
|
||||
<field name="device_count" widget="statinfo" string="Devices"/>
|
||||
</button>
|
||||
<button name="action_view_users"
|
||||
type="object"
|
||||
class="oe_stat_button"
|
||||
icon="fa-users"
|
||||
invisible="api_type != 'controller'">
|
||||
<field name="user_count" widget="statinfo" string="Users"/>
|
||||
</button>
|
||||
|
||||
<!-- Boutons spécifiques au Site Manager -->
|
||||
<button name="action_view_firewall_rules"
|
||||
type="object"
|
||||
class="oe_stat_button"
|
||||
icon="fa-shield"
|
||||
invisible="api_type != 'site_manager'">
|
||||
<field name="firewall_rule_count" widget="statinfo" string="Firewall Rules"/>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="oe_title">
|
||||
<h1>
|
||||
<field name="name" placeholder="Site Name"/>
|
||||
</h1>
|
||||
</div>
|
||||
<group>
|
||||
<group>
|
||||
<field name="site_id"/>
|
||||
<field name="api_type" widget="radio"/>
|
||||
<field name="active"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="timestamp" readonly="1"/>
|
||||
<field name="last_sync" readonly="1"/>
|
||||
<field name="import_status" readonly="1"/>
|
||||
</group>
|
||||
</group>
|
||||
<notebook>
|
||||
<page string="Site Information" name="site_info">
|
||||
<group>
|
||||
<field name="description" placeholder="Description of this site"/>
|
||||
<field name="address" placeholder="Physical address of this site"/>
|
||||
</group>
|
||||
</page>
|
||||
|
||||
<!-- Onglets spécifiques au Controller -->
|
||||
<page string="Networks" name="networks" invisible="api_type != 'controller'">
|
||||
<div class="oe_button_box">
|
||||
<button name="action_sync_networks" string="Synchronize Networks" type="object" class="btn btn-secondary"/>
|
||||
</div>
|
||||
<field name="network_ids" readonly="1">
|
||||
<tree>
|
||||
<field name="name"/>
|
||||
</tree>
|
||||
</field>
|
||||
</page>
|
||||
|
||||
<page string="Devices" name="devices" invisible="api_type != 'controller'">
|
||||
<div class="oe_button_box">
|
||||
<button name="action_sync_devices" string="Synchronize Devices" type="object" class="btn btn-secondary"/>
|
||||
</div>
|
||||
<field name="device_ids" readonly="1">
|
||||
<tree>
|
||||
<field name="name"/>
|
||||
</tree>
|
||||
</field>
|
||||
</page>
|
||||
|
||||
<page string="Users" name="users" invisible="api_type != 'controller'">
|
||||
<div class="oe_button_box">
|
||||
<button name="action_sync_users" string="Synchronize Users" type="object" class="btn btn-secondary"/>
|
||||
</div>
|
||||
<field name="user_ids" readonly="1">
|
||||
<tree>
|
||||
<field name="name"/>
|
||||
</tree>
|
||||
</field>
|
||||
</page>
|
||||
|
||||
<!-- Onglets spécifiques au Site Manager -->
|
||||
<page string="VLANs" name="vlans" invisible="api_type != 'site_manager'">
|
||||
<div class="oe_button_box">
|
||||
<button name="action_sync_vlans" string="Synchronize VLANs" type="object" class="btn btn-secondary"/>
|
||||
</div>
|
||||
<field name="vlan_ids" readonly="1">
|
||||
<tree>
|
||||
<field name="name"/>
|
||||
</tree>
|
||||
</field>
|
||||
</page>
|
||||
|
||||
<page string="Firewall Rules" name="firewall_rules" invisible="api_type != 'site_manager'">
|
||||
<div class="oe_button_box">
|
||||
<button name="action_sync_firewall_rules" string="Synchronize Firewall Rules" type="object" class="btn btn-secondary"/>
|
||||
</div>
|
||||
<field name="firewall_rule_ids" readonly="1">
|
||||
<tree>
|
||||
<field name="name"/>
|
||||
</tree>
|
||||
</field>
|
||||
</page>
|
||||
|
||||
<page string="Port Forwarding" name="port_forwarding" invisible="api_type != 'site_manager'">
|
||||
<div class="oe_button_box">
|
||||
<button name="action_sync_port_forwards" string="Synchronize Port Forwards" type="object" class="btn btn-secondary"/>
|
||||
</div>
|
||||
<field name="port_forward_ids" readonly="1">
|
||||
<tree>
|
||||
<field name="name"/>
|
||||
</tree>
|
||||
</field>
|
||||
</page>
|
||||
|
||||
<page string="Routing" name="routing" invisible="api_type != 'site_manager'">
|
||||
<div class="oe_button_box">
|
||||
<button name="action_sync_routing" string="Synchronize Routing" type="object" class="btn btn-secondary"/>
|
||||
</div>
|
||||
<field name="routing_config_ids" readonly="1">
|
||||
<tree>
|
||||
<field name="ospf_enabled"/>
|
||||
<field name="bgp_enabled"/>
|
||||
<field name="rip_enabled"/>
|
||||
<field name="static_routes"/>
|
||||
</tree>
|
||||
</field>
|
||||
</page>
|
||||
<page string="Connection Information" name="connection_info">
|
||||
<group invisible="api_type != 'controller'">
|
||||
<group string="Controller API Settings">
|
||||
<button string="Configure Controller API" name="action_configure_controller" type="object" class="btn-primary"/>
|
||||
<field name="verify_ssl" invisible="api_type != 'controller'"/>
|
||||
</group>
|
||||
</group>
|
||||
<group invisible="api_type != 'site_manager'">
|
||||
<group string="Site Manager API Settings">
|
||||
<button string="Configure Site Manager API" name="action_configure_site_manager" type="object" class="btn-primary"/>
|
||||
<field name="verify_ssl" invisible="api_type != 'site_manager'"/>
|
||||
</group>
|
||||
</group>
|
||||
</page>
|
||||
<page string="Synchronization" name="sync_settings">
|
||||
<group>
|
||||
<field name="auto_sync"/>
|
||||
<field name="sync_interval" invisible="auto_sync == False"/>
|
||||
<field name="last_update" readonly="1"/>
|
||||
</group>
|
||||
<group string="Recent Sync Jobs" invisible="sync_job_ids == []">
|
||||
<field name="sync_job_ids" nolabel="1" readonly="1">
|
||||
<list>
|
||||
<field name="name"/>
|
||||
<field name="state"/>
|
||||
<field name="start_time"/>
|
||||
<field name="end_time"/>
|
||||
<field name="duration" widget="float_time"/>
|
||||
</list>
|
||||
</field>
|
||||
</group>
|
||||
</page>
|
||||
<page string="API Logs" name="api_logs">
|
||||
<field name="api_log_ids" readonly="1">
|
||||
<list>
|
||||
<field name="create_date"/>
|
||||
<field name="api_type"/>
|
||||
<field name="method"/>
|
||||
<field name="endpoint"/>
|
||||
<field name="status_code"/>
|
||||
<field name="success"/>
|
||||
<field name="duration" widget="float_time"/>
|
||||
</list>
|
||||
</field>
|
||||
</page>
|
||||
<page string="Raw Data" name="raw_data" groups="base.group_system">
|
||||
<field name="raw_data" widget="ace" options="{'mode': 'json'}"/>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
<div class="oe_chatter">
|
||||
<field name="message_follower_ids" widget="mail_followers"/>
|
||||
<field name="activity_ids" widget="mail_activity"/>
|
||||
<field name="message_ids" widget="mail_thread"/>
|
||||
</div>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Import UniFi Site Form View -->
|
||||
<record id="view_unifi_site_import_form" model="ir.ui.view">
|
||||
<field name="name">unifi.site.import.form</field>
|
||||
<field name="model">unifi.site</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Import UniFi Site">
|
||||
<group>
|
||||
<field name="name" required="1" placeholder="Site Name"/>
|
||||
<field name="site_id" required="1" default="default"/>
|
||||
<field name="api_type" widget="radio" required="1"/>
|
||||
</group>
|
||||
|
||||
<group invisible="api_type != 'controller'">
|
||||
<group string="Controller API Settings">
|
||||
<button string="Configure Controller API" name="action_configure_controller" type="object" class="btn-primary"/>
|
||||
</group>
|
||||
</group>
|
||||
|
||||
<group invisible="api_type != 'site_manager'">
|
||||
<group string="Site Manager API Settings">
|
||||
<button string="Configure Site Manager API" name="action_configure_site_manager" type="object" class="btn-primary"/>
|
||||
</group>
|
||||
</group>
|
||||
|
||||
<footer>
|
||||
<button string="Import" name="action_test_connection" type="object" class="btn-primary"/>
|
||||
<button string="Cancel" class="btn-secondary" special="cancel"/>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Actions moved to unifi_actions.xml -->
|
||||
|
||||
</odoo>
|
||||
|
|
@ -51,8 +51,9 @@
|
|||
<field name="last_sync" readonly="1"/>
|
||||
</group>
|
||||
<notebook>
|
||||
<page string="Raw Data" name="raw_data">
|
||||
<field name="raw_data" widget="ace" options="{'mode': 'json'}" help="Raw JSON data of the configuration"/>
|
||||
<page string="Données brutes" name="raw_data">
|
||||
<field name="raw_data" widget="ace" options="{'language': 'json'}" help="Raw JSON data of the configuration" invisible="1"/>
|
||||
<field name="raw_data_json" readonly="1"/>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
|
|
|
|||
|
|
@ -39,9 +39,10 @@
|
|||
<field name="updated_at"/>
|
||||
<field name="last_sync"/>
|
||||
</group>
|
||||
<group string="Données brutes">
|
||||
<field name="raw_data" widget="ace" options="{'mode': 'json'}"/>
|
||||
</group>
|
||||
</page>
|
||||
<page string="Données brutes">
|
||||
<field name="raw_data" widget="ace" options="{'language': 'json'}" invisible="1"/>
|
||||
<field name="raw_data_json" readonly="1"/>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
|
|
|
|||
|
|
@ -49,9 +49,10 @@
|
|||
<field name="created_at"/>
|
||||
<field name="updated_at"/>
|
||||
</group>
|
||||
<group string="Données brutes">
|
||||
<field name="raw_data" widget="ace" options="{'mode': 'json'}" readonly="1"/>
|
||||
</group>
|
||||
</page>
|
||||
<page string="Données brutes">
|
||||
<field name="raw_data" widget="ace" options="{'language': 'json'}" readonly="1" invisible="1"/>
|
||||
<field name="raw_data_json" readonly="1"/>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
|
|
|
|||
113
unifi_integration/views/unifi_vpn_views.xml
Normal file
113
unifi_integration/views/unifi_vpn_views.xml
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<!-- UniFi VPN List View -->
|
||||
<record id="view_unifi_vpn_list" model="ir.ui.view">
|
||||
<field name="name">unifi.vpn.list</field>
|
||||
<field name="model">unifi.vpn</field>
|
||||
<field name="arch" type="xml">
|
||||
<list string="VPN Configurations">
|
||||
<field name="name"/>
|
||||
<field name="vpn_type"/>
|
||||
<field name="site_id"/>
|
||||
<field name="peer_ip"/>
|
||||
<field name="enabled"/>
|
||||
<field name="last_sync" optional="show"/>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- UniFi VPN Form View -->
|
||||
<record id="view_unifi_vpn_form" model="ir.ui.view">
|
||||
<field name="name">unifi.vpn.form</field>
|
||||
<field name="model">unifi.vpn</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="VPN Configuration">
|
||||
<sheet>
|
||||
<div class="oe_title">
|
||||
<h1>
|
||||
<field name="name" placeholder="VPN Name"/>
|
||||
</h1>
|
||||
</div>
|
||||
<group>
|
||||
<group>
|
||||
<field name="site_id"/>
|
||||
<field name="vpn_type"/>
|
||||
<field name="enabled"/>
|
||||
<field name="purpose"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="peer_ip"/>
|
||||
<field name="local_ip"/>
|
||||
<field name="interface"/>
|
||||
<field name="remote_subnets"/>
|
||||
</group>
|
||||
</group>
|
||||
<notebook>
|
||||
<page string="Configuration formatée" name="formatted_config">
|
||||
<field name="formatted_data" nolabel="1"/>
|
||||
</page>
|
||||
<page string="Données brutes" name="raw_data">
|
||||
<group>
|
||||
<field name="raw_data" widget="ace" options="{'language': 'json'}" nolabel="1" invisible="1"/>
|
||||
<field name="raw_data_json" readonly="1" nolabel="1"/>
|
||||
</group>
|
||||
</page>
|
||||
<page string="Informations système" name="system_info">
|
||||
<group>
|
||||
<field name="unifi_id"/>
|
||||
<field name="last_sync"/>
|
||||
</group>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- UniFi VPN Search View -->
|
||||
<record id="view_unifi_vpn_search" model="ir.ui.view">
|
||||
<field name="name">unifi.vpn.search</field>
|
||||
<field name="model">unifi.vpn</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Search VPN Configurations">
|
||||
<field name="name"/>
|
||||
<field name="site_id"/>
|
||||
<field name="vpn_type"/>
|
||||
<field name="peer_ip"/>
|
||||
<field name="local_ip"/>
|
||||
<separator/>
|
||||
<filter string="Actifs" name="active" domain="[('enabled', '=', True)]"/>
|
||||
<filter string="Inactifs" name="inactive" domain="[('enabled', '=', False)]"/>
|
||||
<group expand="0" string="Grouper par">
|
||||
<filter string="Site" name="group_by_site" context="{'group_by': 'site_id'}"/>
|
||||
<filter string="Type" name="group_by_type" context="{'group_by': 'vpn_type'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- UniFi VPN Action -->
|
||||
<record id="action_unifi_vpn" model="ir.actions.act_window">
|
||||
<field name="name">VPN Configurations</field>
|
||||
<field name="res_model">unifi.vpn</field>
|
||||
<field name="view_mode">list,form</field>
|
||||
<field name="search_view_id" ref="view_unifi_vpn_search"/>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
Aucune configuration VPN trouvée
|
||||
</p>
|
||||
<p>
|
||||
Les configurations VPN sont synchronisées depuis le contrôleur UniFi.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Menu Item -->
|
||||
<menuitem id="menu_unifi_vpn"
|
||||
name="VPN"
|
||||
parent="menu_unifi_network_config"
|
||||
action="action_unifi_vpn"
|
||||
sequence="40"/>
|
||||
|
||||
</odoo>
|
||||
152
unifi_integration/views/unifi_wifi_views.xml
Normal file
152
unifi_integration/views/unifi_wifi_views.xml
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<!-- Form View -->
|
||||
<record id="view_unifi_wifi_form" model="ir.ui.view">
|
||||
<field name="name">unifi.wifi.form</field>
|
||||
<field name="model">unifi.wifi</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="WiFi Network">
|
||||
<sheet>
|
||||
<div class="oe_button_box" name="button_box">
|
||||
<button name="toggle_active" type="object" class="oe_stat_button" icon="fa-archive">
|
||||
<field name="active" widget="boolean_button" options="{'terminology': 'archive'}"/>
|
||||
</button>
|
||||
</div>
|
||||
<div class="oe_title">
|
||||
<h1>
|
||||
<field name="name" placeholder="WiFi Network Name (SSID)"/>
|
||||
</h1>
|
||||
</div>
|
||||
<group>
|
||||
<group string="Basic Information">
|
||||
<field name="wifi_id"/>
|
||||
<field name="site_id"/>
|
||||
<field name="enabled"/>
|
||||
<field name="hidden"/>
|
||||
<field name="is_guest"/>
|
||||
</group>
|
||||
<group string="Security">
|
||||
<field name="security"/>
|
||||
<field name="password" password="True" invisible="security == 'open'"/>
|
||||
<field name="wpa_mode" invisible="security not in ('wpapsk', 'wpa2psk', 'wpapskwpa2psk', 'wpa3')"/>
|
||||
<field name="wpa_encryption" invisible="security not in ('wpapsk', 'wpa2psk', 'wpapskwpa2psk', 'wpa3')"/>
|
||||
<field name="pmf_mode" invisible="security not in ('wpa2psk', 'wpapskwpa2psk', 'wpa3')"/>
|
||||
</group>
|
||||
</group>
|
||||
<notebook>
|
||||
<page string="Network Configuration">
|
||||
<group>
|
||||
<group string="Network">
|
||||
<field name="network_id"/>
|
||||
<field name="vlan_id"/>
|
||||
</group>
|
||||
<group string="Radio">
|
||||
<field name="band"/>
|
||||
<field name="channel"/>
|
||||
<field name="channel_width"/>
|
||||
<field name="tx_power"/>
|
||||
<field name="tx_power_custom" invisible="tx_power != 'custom'"/>
|
||||
</group>
|
||||
</group>
|
||||
</page>
|
||||
<page string="Guest Settings" invisible="is_guest == False">
|
||||
<group>
|
||||
<field name="guest_policy"/>
|
||||
</group>
|
||||
</page>
|
||||
<page string="Technical Information">
|
||||
<group>
|
||||
<group string="Timestamps">
|
||||
<field name="created_at"/>
|
||||
<field name="updated_at"/>
|
||||
<field name="last_sync"/>
|
||||
</group>
|
||||
<group string="Raw Data">
|
||||
<field name="raw_data" widget="ace" options="{'language': 'json'}" readonly="1" nolabel="1" invisible="1"/>
|
||||
<field name="raw_data_json" readonly="1" nolabel="1"/>
|
||||
</group>
|
||||
</group>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- List View -->
|
||||
<record id="view_unifi_wifi_list" model="ir.ui.view">
|
||||
<field name="name">unifi.wifi.list</field>
|
||||
<field name="model">unifi.wifi</field>
|
||||
<field name="arch" type="xml">
|
||||
<list string="WiFi Networks">
|
||||
<field name="name"/>
|
||||
<field name="security"/>
|
||||
<field name="band"/>
|
||||
<field name="is_guest"/>
|
||||
<field name="enabled"/>
|
||||
<field name="hidden"/>
|
||||
<field name="site_id"/>
|
||||
<field name="network_id"/>
|
||||
<field name="vlan_id"/>
|
||||
<field name="last_sync"/>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Search View -->
|
||||
<record id="view_unifi_wifi_search" model="ir.ui.view">
|
||||
<field name="name">unifi.wifi.search</field>
|
||||
<field name="model">unifi.wifi</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Search WiFi Networks">
|
||||
<field name="name"/>
|
||||
<field name="wifi_id"/>
|
||||
<field name="site_id"/>
|
||||
<field name="network_id"/>
|
||||
<field name="vlan_id"/>
|
||||
<separator/>
|
||||
<filter string="Enabled" name="enabled" domain="[('enabled', '=', True)]"/>
|
||||
<filter string="Disabled" name="disabled" domain="[('enabled', '=', False)]"/>
|
||||
<separator/>
|
||||
<filter string="Guest Networks" name="guest" domain="[('is_guest', '=', True)]"/>
|
||||
<filter string="Hidden Networks" name="hidden" domain="[('hidden', '=', True)]"/>
|
||||
<separator/>
|
||||
<filter string="2.4 GHz" name="band_2g" domain="[('band', 'in', ['2g', 'both'])]"/>
|
||||
<filter string="5 GHz" name="band_5g" domain="[('band', 'in', ['5g', 'both'])]"/>
|
||||
<separator/>
|
||||
<filter string="Open Networks" name="open" domain="[('security', '=', 'open')]"/>
|
||||
<filter string="Secured Networks" name="secured" domain="[('security', '!=', 'open')]"/>
|
||||
<group expand="0" string="Group By">
|
||||
<filter string="Site" name="group_by_site" context="{'group_by': 'site_id'}"/>
|
||||
<filter string="Security Type" name="group_by_security" context="{'group_by': 'security'}"/>
|
||||
<filter string="Band" name="group_by_band" context="{'group_by': 'band'}"/>
|
||||
<filter string="Network" name="group_by_network" context="{'group_by': 'network_id'}"/>
|
||||
<filter string="VLAN" name="group_by_vlan" context="{'group_by': 'vlan_id'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Action -->
|
||||
<record id="action_unifi_wifi" model="ir.actions.act_window">
|
||||
<field name="name">WiFi Networks</field>
|
||||
<field name="res_model">unifi.wifi</field>
|
||||
<field name="view_mode">list,form</field>
|
||||
<field name="search_view_id" ref="view_unifi_wifi_search"/>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
No WiFi networks found
|
||||
</p>
|
||||
<p>
|
||||
WiFi networks will appear here after synchronizing with your UniFi controller.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Menu Item -->
|
||||
<menuitem id="menu_unifi_wifi"
|
||||
name="WiFi Networks"
|
||||
parent="menu_unifi_networks"
|
||||
action="action_unifi_wifi"
|
||||
sequence="30"/>
|
||||
</odoo>
|
||||
Loading…
Reference in a new issue