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_dns_config_views.xml',
|
||||||
'views/unifi_routing_views.xml',
|
'views/unifi_routing_views.xml',
|
||||||
'views/unifi_routing_config_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_api_config_views.xml',
|
||||||
'views/unifi_dashboard_views.xml',
|
'views/unifi_dashboard_views.xml',
|
||||||
# Templates
|
# Templates
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Import module files
|
# Import module files
|
||||||
|
# Common functionality
|
||||||
|
from . import unifi_common
|
||||||
|
|
||||||
# UniFi models
|
# UniFi models
|
||||||
from . import unifi_site
|
from . import unifi_site
|
||||||
from . import unifi_site_controller
|
|
||||||
from . import unifi_site_manager
|
|
||||||
from . import unifi_auth_session
|
from . import unifi_auth_session
|
||||||
from . import unifi_mfa
|
from . import unifi_mfa
|
||||||
from . import unifi_api_config
|
from . import unifi_api_config
|
||||||
|
|
@ -21,5 +22,7 @@ from . import unifi_dns
|
||||||
from . import unifi_dns_config
|
from . import unifi_dns_config
|
||||||
from . import unifi_routing
|
from . import unifi_routing
|
||||||
from . import unifi_routing_config
|
from . import unifi_routing_config
|
||||||
|
from . import unifi_wifi
|
||||||
|
from . import unifi_vpn
|
||||||
from . import unifi_dashboard_metric
|
from . import unifi_dashboard_metric
|
||||||
from . import unifi_dashboard_stat
|
from . import unifi_dashboard_stat
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,17 @@ class UnifiAuthSession(models.Model):
|
||||||
help='Session cookie for Controller API'
|
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(
|
expiry = fields.Datetime(
|
||||||
string='Expiry Date',
|
string='Expiry Date',
|
||||||
help='Date and time when this session expires'
|
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
|
# pylint: disable=import-error
|
||||||
from odoo import models, fields, api
|
from odoo import models, fields, api
|
||||||
from odoo.tools.translate import _
|
from odoo.tools.translate import _
|
||||||
|
from .unifi_common import UnifiCommonMixin
|
||||||
# pylint: enable=import-error
|
# pylint: enable=import-error
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
@ -12,7 +13,7 @@ from datetime import datetime, timedelta
|
||||||
|
|
||||||
_logger = logging.getLogger(__name__)
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
class UnifiDevice(models.Model):
|
class UnifiDevice(models.Model, UnifiCommonMixin):
|
||||||
"""Modèle pour les appareils UniFi
|
"""Modèle pour les appareils UniFi
|
||||||
|
|
||||||
Ce modèle stocke les informations sur les appareils UniFi, tels que les points d'accès,
|
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"
|
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(
|
active = fields.Boolean(
|
||||||
string='Actif',
|
string='Actif',
|
||||||
default=True,
|
default=True,
|
||||||
|
|
|
||||||
|
|
@ -133,6 +133,59 @@ class UnifiDns(models.Model):
|
||||||
|
|
||||||
record.last_sync = fields.Datetime.now()
|
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):
|
def push_to_unifi(self):
|
||||||
"""Pousse les modifications vers le système UniFi"""
|
"""Pousse les modifications vers le système UniFi"""
|
||||||
for record in self:
|
for record in self:
|
||||||
|
|
|
||||||
|
|
@ -78,16 +78,56 @@ class UnifiFirewallRule(models.Model):
|
||||||
help='Protocole réseau auquel cette règle s\'applique'
|
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(
|
source = fields.Char(
|
||||||
string='Source',
|
string='Source',
|
||||||
help='Réseau source ou adresse IP au format CIDR'
|
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(
|
destination = fields.Char(
|
||||||
string='Destination',
|
string='Destination',
|
||||||
help='Réseau de destination ou adresse IP au format CIDR'
|
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(
|
src_port = fields.Char(
|
||||||
string='Port source',
|
string='Port source',
|
||||||
help='Numéro de port source ou plage (ex: 80 ou 1024-2048)'
|
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'
|
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(
|
rule_index = fields.Integer(
|
||||||
string='Index de la règle',
|
string='Index de la règle',
|
||||||
help='Position de la règle dans la liste des règles'
|
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'
|
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
|
# Champs calculés
|
||||||
rule_summary = fields.Char(
|
rule_summary = fields.Char(
|
||||||
string='Résumé de la règle',
|
string='Résumé de la règle',
|
||||||
compute='_compute_rule_summary'
|
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):
|
def _compute_rule_summary(self):
|
||||||
"""Calcule un résumé lisible de la règle de pare-feu
|
"""Calcule un résumé lisible de la règle de pare-feu
|
||||||
|
|
||||||
|
|
@ -173,8 +390,8 @@ class UnifiFirewallRule(models.Model):
|
||||||
if record.protocol:
|
if record.protocol:
|
||||||
parts.append(record.protocol.upper())
|
parts.append(record.protocol.upper())
|
||||||
|
|
||||||
src_addr = record.source or 'n\'importe où'
|
src_addr = record.formatted_source or "N'importe où"
|
||||||
dst_addr = record.destination or 'n\'importe où'
|
dst_addr = record.formatted_destination or "N'importe où"
|
||||||
|
|
||||||
src = f"de {src_addr}"
|
src = f"de {src_addr}"
|
||||||
if record.src_port:
|
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
|
# These imports will work in an Odoo environment, even if your IDE marks them as not found
|
||||||
# pylint: disable=import-error
|
# pylint: disable=import-error
|
||||||
from odoo import models, fields, api, _
|
from odoo import models, fields, api, _
|
||||||
|
from .unifi_common import UnifiCommonMixin
|
||||||
from odoo.exceptions import UserError, ValidationError
|
from odoo.exceptions import UserError, ValidationError
|
||||||
# pylint: enable=import-error
|
# pylint: enable=import-error
|
||||||
|
|
||||||
|
|
@ -12,7 +13,7 @@ from datetime import datetime
|
||||||
|
|
||||||
_logger = logging.getLogger(__name__)
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
class UnifiNetwork(models.Model):
|
class UnifiNetwork(models.Model, UnifiCommonMixin):
|
||||||
"""Modèle pour gérer les réseaux UniFi
|
"""Modèle pour gérer les réseaux UniFi
|
||||||
|
|
||||||
Ce modèle représente les réseaux configurés dans les sites 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'),
|
('wan', 'WAN'),
|
||||||
('lan', 'LAN'),
|
('lan', 'LAN'),
|
||||||
('vpn', 'VPN'),
|
('vpn', 'VPN'),
|
||||||
|
('site-vpn', 'Site VPN'),
|
||||||
|
('remote-user-vpn', 'Remote User VPN'),
|
||||||
('vlan-only', 'VLAN Only'),
|
('vlan-only', 'VLAN Only'),
|
||||||
('other', 'Autre')
|
('other', 'Autre')
|
||||||
],
|
],
|
||||||
|
|
@ -135,6 +138,17 @@ class UnifiNetwork(models.Model):
|
||||||
help='Données brutes du réseau au format JSON'
|
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
|
# Relations
|
||||||
site_id = fields.Many2one(
|
site_id = fields.Many2one(
|
||||||
comodel_name='unifi.site',
|
comodel_name='unifi.site',
|
||||||
|
|
@ -243,25 +257,149 @@ class UnifiNetwork(models.Model):
|
||||||
# Création d'un nouveau réseau
|
# Création d'un nouveau réseau
|
||||||
return self.create(vals)
|
return self.create(vals)
|
||||||
|
|
||||||
def sync_networks(self, site):
|
def sync_networks(self):
|
||||||
"""Synchronise les réseaux depuis l'API UniFi
|
"""Synchronise les réseaux depuis l'API UniFi
|
||||||
|
|
||||||
Args:
|
Cette méthode est conçue pour être appelée depuis un bouton dans l'interface utilisateur.
|
||||||
site: L'enregistrement du site UniFi
|
Elle utilise le site associé à l'enregistrement en cours.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
bool: True si la synchronisation a réussi, False sinon
|
bool: True si la synchronisation a réussi, False sinon
|
||||||
"""
|
"""
|
||||||
self.ensure_one()
|
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
|
# Déterminer quelle méthode utiliser en fonction du type d'API
|
||||||
if site.api_type == 'controller':
|
if site.api_type == 'controller':
|
||||||
return self._sync_networks_controller(site)
|
return self._sync_networks_controller(site)
|
||||||
elif site.api_type == 'site_manager':
|
elif site.api_type == 'site_manager':
|
||||||
return self._sync_networks_site_manager(site)
|
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:
|
else:
|
||||||
_logger.error(f"Type d'API non pris en charge: {site.api_type}")
|
_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):
|
def _sync_networks_controller(self, site):
|
||||||
"""Synchronise les réseaux depuis l'API Controller
|
"""Synchronise les réseaux depuis l'API Controller
|
||||||
|
|
@ -272,8 +410,8 @@ class UnifiNetwork(models.Model):
|
||||||
Returns:
|
Returns:
|
||||||
bool: True si la synchronisation a réussi, False sinon
|
bool: True si la synchronisation a réussi, False sinon
|
||||||
"""
|
"""
|
||||||
# Obtenir les données des réseaux depuis l'API Controller
|
# Obtenir les données des réseaux directement depuis le site
|
||||||
networks_data = self.env['unifi.site.controller'].get_network_data(site)
|
networks_data = site._get_controller_network_data()
|
||||||
if not networks_data:
|
if not networks_data:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
@ -292,8 +430,8 @@ class UnifiNetwork(models.Model):
|
||||||
Returns:
|
Returns:
|
||||||
bool: True si la synchronisation a réussi, False sinon
|
bool: True si la synchronisation a réussi, False sinon
|
||||||
"""
|
"""
|
||||||
# Obtenir les données des réseaux depuis l'API Site Manager
|
# Obtenir les données des réseaux directement depuis le site
|
||||||
networks_data = self.env['unifi.site.manager'].get_network_data(site)
|
networks_data = site._get_site_manager_network_data()
|
||||||
if not networks_data:
|
if not networks_data:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,11 @@ import json
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from odoo import models, fields, api
|
from odoo import models, fields, api
|
||||||
|
from .unifi_common import UnifiCommonMixin
|
||||||
|
|
||||||
_logger = logging.getLogger(__name__)
|
_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
|
"""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
|
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'
|
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
|
# Champs calculés
|
||||||
rule_summary = fields.Char(
|
rule_summary = fields.Char(
|
||||||
string='Résumé de la règle',
|
string='Résumé de la règle',
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,12 @@ class UnifiRoutingConfig(models.Model):
|
||||||
help='Site this routing configuration belongs to'
|
help='Site this routing configuration belongs to'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
name = fields.Char(
|
||||||
|
string='Name',
|
||||||
|
required=True,
|
||||||
|
help='Name of the routing configuration'
|
||||||
|
)
|
||||||
|
|
||||||
ospf_enabled = fields.Boolean(
|
ospf_enabled = fields.Boolean(
|
||||||
string='OSPF Enabled',
|
string='OSPF Enabled',
|
||||||
default=False,
|
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'),
|
('pending', 'Pending'),
|
||||||
('running', 'Running'),
|
('running', 'Running'),
|
||||||
('completed', 'Completed'),
|
('completed', 'Completed'),
|
||||||
|
('partial', 'Partially Completed'),
|
||||||
('failed', 'Failed'),
|
('failed', 'Failed'),
|
||||||
('cancelled', 'Cancelled')
|
('cancelled', 'Cancelled')
|
||||||
],
|
],
|
||||||
|
|
@ -237,7 +238,7 @@ class UnifiSyncJob(models.Model):
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'name': _('API Logs'),
|
'name': _('API Logs'),
|
||||||
'view_mode': 'tree,form',
|
'view_mode': 'list,form',
|
||||||
'res_model': 'unifi.api.log',
|
'res_model': 'unifi.api.log',
|
||||||
'domain': [('sync_job_id', '=', self.id)],
|
'domain': [('sync_job_id', '=', self.id)],
|
||||||
'type': 'ir.actions.act_window',
|
'type': 'ir.actions.act_window',
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,11 @@ import json
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from odoo import models, fields, api
|
from odoo import models, fields, api
|
||||||
|
from .unifi_common import UnifiCommonMixin
|
||||||
|
|
||||||
_logger = logging.getLogger(__name__)
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
class UnifiSystemInfo(models.Model):
|
class UnifiSystemInfo(models.Model, UnifiCommonMixin):
|
||||||
"""System information of the UniFi device"""
|
"""System information of the UniFi device"""
|
||||||
_name = 'unifi.system.info'
|
_name = 'unifi.system.info'
|
||||||
_description = 'UniFi System Information'
|
_description = 'UniFi System Information'
|
||||||
|
|
@ -51,6 +52,17 @@ class UnifiSystemInfo(models.Model):
|
||||||
# Raw data for debugging and future extensions
|
# Raw data for debugging and future extensions
|
||||||
raw_data = fields.Text(string='Raw Data')
|
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')
|
@api.depends('uptime')
|
||||||
def _compute_uptime_human(self):
|
def _compute_uptime_human(self):
|
||||||
for record in 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
|
# These imports will work in an Odoo environment, even if your IDE marks them as not found
|
||||||
# pylint: disable=import-error
|
# pylint: disable=import-error
|
||||||
from odoo import models, fields, api, _
|
from odoo import models, fields, api, _
|
||||||
|
from .unifi_common import UnifiCommonMixin
|
||||||
from odoo.exceptions import UserError, ValidationError
|
from odoo.exceptions import UserError, ValidationError
|
||||||
# pylint: enable=import-error
|
# pylint: enable=import-error
|
||||||
|
|
||||||
|
|
@ -12,7 +13,7 @@ from datetime import datetime
|
||||||
|
|
||||||
_logger = logging.getLogger(__name__)
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
class UnifiUser(models.Model):
|
class UnifiUser(models.Model, UnifiCommonMixin):
|
||||||
"""Modèle pour gérer les utilisateurs UniFi
|
"""Modèle pour gérer les utilisateurs UniFi
|
||||||
|
|
||||||
Ce modèle représente les utilisateurs configurés dans les sites 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'
|
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
|
# Relations
|
||||||
site_id = fields.Many2one(
|
site_id = fields.Many2one(
|
||||||
comodel_name='unifi.site',
|
comodel_name='unifi.site',
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,12 @@
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
from odoo import models, fields, api, _
|
from odoo import models, fields, api, _
|
||||||
|
from .unifi_common import UnifiCommonMixin
|
||||||
from odoo.exceptions import ValidationError
|
from odoo.exceptions import ValidationError
|
||||||
|
|
||||||
_logger = logging.getLogger(__name__)
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
class UnifiVLAN(models.Model):
|
class UnifiVLAN(models.Model, UnifiCommonMixin):
|
||||||
"""Modèle pour gérer les VLANs UniFi
|
"""Modèle pour gérer les VLANs UniFi
|
||||||
|
|
||||||
Ce modèle représente les VLANs configurés dans les sites 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'
|
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 = [
|
_sql_constraints = [
|
||||||
('vlan_id_site_uniq', 'unique(vlan_id, site_id)', 'L\'ID VLAN doit être unique par site!')
|
('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
|
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,1,1,1
|
||||||
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_mfa,access_unifi_mfa,unifi_integration.model_unifi_mfa,base.group_user,1,0,0,0
|
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_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,0,0,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,0,0,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,0,0,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,0,0,0
|
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,0,0,0
|
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,0,0,0
|
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,0,0,0
|
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,0,0,0
|
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,0,0,0
|
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,0,0,0
|
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,0,0,0
|
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,0,0,0
|
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,0,0,0
|
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_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_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,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_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
|
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_create" eval="True"/>
|
||||||
<field name="perm_unlink" eval="True"/>
|
<field name="perm_unlink" eval="True"/>
|
||||||
</record>
|
</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>
|
</data>
|
||||||
</odoo>
|
</odoo>
|
||||||
|
|
|
||||||
|
|
@ -75,10 +75,10 @@
|
||||||
</page>
|
</page>
|
||||||
</notebook>
|
</notebook>
|
||||||
</sheet>
|
</sheet>
|
||||||
<div class="oe_chatter">
|
<chatter>
|
||||||
<field name="message_follower_ids" widget="mail_followers"/>
|
<field name="message_follower_ids"/>
|
||||||
<field name="message_ids" widget="mail_thread"/>
|
<field name="message_ids"/>
|
||||||
</div>
|
</chatter>
|
||||||
</form>
|
</form>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
|
||||||
|
|
@ -49,24 +49,24 @@
|
||||||
<notebook>
|
<notebook>
|
||||||
<page string="Request" name="request">
|
<page string="Request" name="request">
|
||||||
<group>
|
<group>
|
||||||
<field name="request_headers" 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="{'mode': 'json'}" readonly="1"/>
|
<field name="request_params" widget="ace" options="{'language': 'json'}" readonly="1"/>
|
||||||
<field name="request_body" widget="ace" options="{'mode': 'json'}" readonly="1"/>
|
<field name="request_body" widget="ace" options="{'language': 'json'}" readonly="1"/>
|
||||||
</group>
|
</group>
|
||||||
</page>
|
</page>
|
||||||
<page string="Response" name="response">
|
<page string="Response" name="response">
|
||||||
<group>
|
<group>
|
||||||
<field name="response_headers" 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="{'mode': 'json'}" readonly="1"/>
|
<field name="response_body" widget="ace" options="{'language': 'json'}" readonly="1"/>
|
||||||
<field name="error_message" invisible="error_message == False" readonly="1"/>
|
<field name="error_message" invisible="error_message == False" readonly="1"/>
|
||||||
</group>
|
</group>
|
||||||
</page>
|
</page>
|
||||||
<page string="Formatted" name="formatted">
|
<page string="Formatted" name="formatted">
|
||||||
<group>
|
<group>
|
||||||
<separator string="Request"/>
|
<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"/>
|
<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>
|
</group>
|
||||||
</page>
|
</page>
|
||||||
</notebook>
|
</notebook>
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,10 @@
|
||||||
</group>
|
</group>
|
||||||
<notebook>
|
<notebook>
|
||||||
<page string="Données brutes">
|
<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>
|
</page>
|
||||||
</notebook>
|
</notebook>
|
||||||
</sheet>
|
</sheet>
|
||||||
|
|
|
||||||
|
|
@ -21,11 +21,14 @@
|
||||||
<field name="protocol"/>
|
<field name="protocol"/>
|
||||||
</group>
|
</group>
|
||||||
<group>
|
<group>
|
||||||
<field name="source"/>
|
<field name="detailed_rule_type"/>
|
||||||
<field name="src_port"/>
|
|
||||||
<field name="destination"/>
|
|
||||||
<field name="dst_port"/>
|
|
||||||
<field name="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>
|
||||||
</group>
|
</group>
|
||||||
<notebook>
|
<notebook>
|
||||||
|
|
@ -40,9 +43,10 @@
|
||||||
<field name="updated_at"/>
|
<field name="updated_at"/>
|
||||||
<field name="last_sync"/>
|
<field name="last_sync"/>
|
||||||
</group>
|
</group>
|
||||||
<group string="Données brutes">
|
</page>
|
||||||
<field name="raw_data" widget="ace" options="{'mode': 'json'}"/>
|
<page string="Données brutes">
|
||||||
</group>
|
<field name="raw_data" widget="ace" options="{'language': 'json'}" invisible="1"/>
|
||||||
|
<field name="raw_data_json" readonly="1"/>
|
||||||
</page>
|
</page>
|
||||||
</notebook>
|
</notebook>
|
||||||
</sheet>
|
</sheet>
|
||||||
|
|
@ -62,8 +66,12 @@
|
||||||
<field name="enabled"/>
|
<field name="enabled"/>
|
||||||
<field name="action"/>
|
<field name="action"/>
|
||||||
<field name="protocol"/>
|
<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_summary"/>
|
||||||
<field name="rule_type"/>
|
|
||||||
</list>
|
</list>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
@ -76,8 +84,9 @@
|
||||||
<search string="Rechercher des règles de pare-feu UniFi">
|
<search string="Rechercher des règles de pare-feu UniFi">
|
||||||
<field name="name" string="Nom"/>
|
<field name="name" string="Nom"/>
|
||||||
<field name="site_id" string="Site"/>
|
<field name="site_id" string="Site"/>
|
||||||
<field name="source" string="Source"/>
|
<field name="formatted_source" string="Source"/>
|
||||||
<field name="destination" string="Destination"/>
|
<field name="formatted_destination" string="Destination"/>
|
||||||
|
<field name="detailed_rule_type" string="Type détaillé"/>
|
||||||
<separator/>
|
<separator/>
|
||||||
<filter string="Activées" name="enabled" domain="[('enabled', '=', True)]"/>
|
<filter string="Activées" name="enabled" domain="[('enabled', '=', True)]"/>
|
||||||
<filter string="Désactivées" name="disabled" domain="[('enabled', '=', False)]"/>
|
<filter string="Désactivées" name="disabled" domain="[('enabled', '=', False)]"/>
|
||||||
|
|
@ -92,6 +101,9 @@
|
||||||
<filter string="Tous" name="protocol_all" domain="[('protocol', '=', 'all')]"/>
|
<filter string="Tous" name="protocol_all" domain="[('protocol', '=', 'all')]"/>
|
||||||
<group expand="0" string="Grouper par">
|
<group expand="0" string="Grouper par">
|
||||||
<filter string="Site" name="group_by_site" context="{'group_by': 'site_id'}"/>
|
<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="Action" name="group_by_action" context="{'group_by': 'action'}"/>
|
||||||
<filter string="Protocole" name="group_by_protocol" context="{'group_by': 'protocol'}"/>
|
<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'}"/>
|
<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="site_id"/>
|
||||||
<field name="enabled"/>
|
<field name="enabled"/>
|
||||||
<field name="last_sync"/>
|
<field name="last_sync"/>
|
||||||
|
<header>
|
||||||
|
<button name="sync_networks_from_list" string="Synchroniser tous les réseaux" type="object" class="btn-primary"/>
|
||||||
|
</header>
|
||||||
</list>
|
</list>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
@ -68,7 +71,10 @@
|
||||||
</group>
|
</group>
|
||||||
</page>
|
</page>
|
||||||
<page string="Données brutes">
|
<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>
|
</page>
|
||||||
</notebook>
|
</notebook>
|
||||||
</sheet>
|
</sheet>
|
||||||
|
|
|
||||||
|
|
@ -29,20 +29,25 @@
|
||||||
<group string="Description">
|
<group string="Description">
|
||||||
<field name="description" nolabel="1" placeholder="Description détaillée de la redirection de port..."/>
|
<field name="description" nolabel="1" placeholder="Description détaillée de la redirection de port..."/>
|
||||||
</group>
|
</group>
|
||||||
<group string="Informations techniques" groups="base.group_system">
|
<notebook>
|
||||||
<group>
|
<page string="Informations techniques" groups="base.group_system">
|
||||||
<field name="rule_id"/>
|
<group>
|
||||||
<field name="rule_index"/>
|
<group>
|
||||||
</group>
|
<field name="rule_id"/>
|
||||||
<group>
|
<field name="rule_index"/>
|
||||||
<field name="created_at"/>
|
</group>
|
||||||
<field name="updated_at"/>
|
<group>
|
||||||
<field name="last_sync"/>
|
<field name="created_at"/>
|
||||||
</group>
|
<field name="updated_at"/>
|
||||||
</group>
|
<field name="last_sync"/>
|
||||||
<group string="Données brutes" groups="base.group_system">
|
</group>
|
||||||
<field name="raw_data" nolabel="1"/>
|
</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>
|
</sheet>
|
||||||
</form>
|
</form>
|
||||||
</field>
|
</field>
|
||||||
|
|
|
||||||
|
|
@ -24,10 +24,7 @@
|
||||||
<field name="model">unifi.site</field>
|
<field name="model">unifi.site</field>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<form string="UniFi Site">
|
<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>
|
<sheet>
|
||||||
<div class="oe_button_box" name="button_box">
|
<div class="oe_button_box" name="button_box">
|
||||||
<!-- Boutons statistiques communs à tous les types d'API -->
|
<!-- Boutons statistiques communs à tous les types d'API -->
|
||||||
|
|
@ -35,7 +32,7 @@
|
||||||
type="object"
|
type="object"
|
||||||
class="oe_stat_button"
|
class="oe_stat_button"
|
||||||
icon="fa-history">
|
icon="fa-history">
|
||||||
<div class="o_stat_info">
|
<div class="o_field_widget o_stat_info">
|
||||||
<span class="o_stat_text">API Logs</span>
|
<span class="o_stat_text">API Logs</span>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -43,7 +40,7 @@
|
||||||
type="object"
|
type="object"
|
||||||
class="oe_stat_button"
|
class="oe_stat_button"
|
||||||
icon="fa-refresh">
|
icon="fa-refresh">
|
||||||
<div class="o_stat_info">
|
<div class="o_field_widget o_stat_info">
|
||||||
<span class="o_stat_text">Sync Jobs</span>
|
<span class="o_stat_text">Sync Jobs</span>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -53,44 +50,79 @@
|
||||||
type="object"
|
type="object"
|
||||||
class="oe_stat_button"
|
class="oe_stat_button"
|
||||||
icon="fa-sitemap"
|
icon="fa-sitemap"
|
||||||
invisible="context.get('api_type') != 'controller'">
|
invisible="api_type != 'controller'">
|
||||||
<field name="network_count" widget="statinfo" string="Networks"/>
|
<field name="network_count" widget="statinfo" string="Networks"/>
|
||||||
</button>
|
</button>
|
||||||
<button name="action_view_devices"
|
<button name="action_view_devices"
|
||||||
type="object"
|
type="object"
|
||||||
class="oe_stat_button"
|
class="oe_stat_button"
|
||||||
icon="fa-server"
|
icon="fa-server"
|
||||||
invisible="context.get('api_type') != 'controller'">
|
invisible="api_type != 'controller'">
|
||||||
<field name="device_count" widget="statinfo" string="Devices"/>
|
<field name="device_count" widget="statinfo" string="Devices"/>
|
||||||
</button>
|
</button>
|
||||||
<button name="action_view_users"
|
<button name="action_view_users"
|
||||||
type="object"
|
type="object"
|
||||||
class="oe_stat_button"
|
class="oe_stat_button"
|
||||||
icon="fa-users"
|
icon="fa-users"
|
||||||
invisible="context.get('api_type') != 'controller'">
|
invisible="api_type != 'controller'">
|
||||||
<field name="user_count" widget="statinfo" string="Users"/>
|
<field name="user_count" widget="statinfo" string="Users"/>
|
||||||
</button>
|
</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 -->
|
<!-- Boutons spécifiques au Site Manager -->
|
||||||
<button name="action_view_firewall_rules"
|
<button name="action_view_firewall_rules"
|
||||||
type="object"
|
type="object"
|
||||||
class="oe_stat_button"
|
class="oe_stat_button"
|
||||||
icon="fa-shield"
|
icon="fa-shield">
|
||||||
invisible="context.get('api_type') != 'site_manager'">
|
<field name="firewall_rule_count" widget="statinfo" string="Règles pare-feu"/>
|
||||||
<field name="firewall_rule_count" widget="statinfo" string="Firewall Rules"/>
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<widget name="web_ribbon" title="Inactive" bg_color="text-bg-danger" invisible="active"/>
|
||||||
<div class="oe_title">
|
<div class="oe_title">
|
||||||
<h1>
|
<h1 class="d-flex flex-row">
|
||||||
<field name="name" placeholder="Site Name"/>
|
<field name="name" class="o_text_overflow" placeholder="Site Name"/>
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
<group>
|
<group>
|
||||||
<group>
|
<group>
|
||||||
<field name="site_id"/>
|
<field name="site_id"/>
|
||||||
<field name="api_type" widget="radio"/>
|
<field name="api_type" widget="radio"/>
|
||||||
<field name="active"/>
|
<field name="active" invisible="1"/>
|
||||||
</group>
|
</group>
|
||||||
<group>
|
<group>
|
||||||
<field name="timestamp" readonly="1"/>
|
<field name="timestamp" readonly="1"/>
|
||||||
|
|
@ -100,28 +132,28 @@
|
||||||
</group>
|
</group>
|
||||||
|
|
||||||
<!-- Informations de connexion pour Controller API -->
|
<!-- 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>
|
<group>
|
||||||
<field name="host" required="context.get('api_type') == 'controller'"/>
|
<field name="host" required="api_type == 'controller'"/>
|
||||||
<field name="port" required="context.get('api_type') == 'controller'"/>
|
<field name="port" required="api_type == 'controller'"/>
|
||||||
</group>
|
</group>
|
||||||
<group>
|
<group>
|
||||||
<field name="username" required="context.get('api_type') == 'controller'"/>
|
<field name="username" required="api_type == 'controller'"/>
|
||||||
<field name="password" password="True" required="context.get('api_type') == 'controller'"/>
|
<field name="password" password="True" required="api_type == 'controller'"/>
|
||||||
<field name="verify_ssl"/>
|
<field name="verify_ssl"/>
|
||||||
</group>
|
</group>
|
||||||
</group>
|
</group>
|
||||||
|
|
||||||
<!-- Informations de connexion pour Site Manager API -->
|
<!-- 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>
|
<group>
|
||||||
<field name="api_key" required="context.get('api_type') == 'site_manager'"/>
|
<field name="api_key" required="api_type == 'site_manager'"/>
|
||||||
</group>
|
</group>
|
||||||
<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"
|
<field name="mfa_token" password="True"
|
||||||
invisible="context.get('api_type') != 'site_manager' or not context.get('mfa_enabled')"
|
invisible="api_type != 'site_manager' or not mfa_enabled"
|
||||||
required="context.get('api_type') == 'site_manager' and context.get('mfa_enabled')"/>
|
required="api_type == 'site_manager' and mfa_enabled"/>
|
||||||
</group>
|
</group>
|
||||||
</group>
|
</group>
|
||||||
<notebook>
|
<notebook>
|
||||||
|
|
@ -133,113 +165,298 @@
|
||||||
</page>
|
</page>
|
||||||
|
|
||||||
<!-- Onglets spécifiques au Controller -->
|
<!-- Onglets spécifiques au Controller -->
|
||||||
<page string="Networks" name="networks" invisible="context.get('api_type') != '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">
|
|
||||||
<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">
|
|
||||||
<group>
|
<group>
|
||||||
<field name="auto_sync"/>
|
<div class="w-100 mb-2">
|
||||||
<field name="sync_interval" invisible="not context.get('auto_sync')"/>
|
<button name="action_sync_networks" string="Synchronize Networks" type="object" class="btn btn-secondary"/>
|
||||||
<field name="last_update" readonly="1"/>
|
</div>
|
||||||
</group>
|
<field name="network_ids" readonly="1" nolabel="1">
|
||||||
<group string="Recent Sync Jobs" invisible="not context.get('sync_job_ids')">
|
|
||||||
<field name="sync_job_ids" nolabel="1" readonly="1">
|
|
||||||
<list>
|
<list>
|
||||||
<field name="name"/>
|
<field name="name"/>
|
||||||
<field name="state"/>
|
<field name="purpose"/>
|
||||||
<field name="start_time"/>
|
<field name="subnet"/>
|
||||||
<field name="end_time"/>
|
|
||||||
<field name="duration" widget="float_time"/>
|
|
||||||
</list>
|
</list>
|
||||||
</field>
|
</field>
|
||||||
</group>
|
</group>
|
||||||
</page>
|
</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">
|
<page string="API Logs" name="api_logs">
|
||||||
<field name="api_log_ids" readonly="1">
|
<field name="api_log_ids" readonly="1" nolabel="1">
|
||||||
<list>
|
<list>
|
||||||
<field name="create_date"/>
|
<field name="create_date"/>
|
||||||
<field name="api_type"/>
|
<field name="api_type"/>
|
||||||
|
|
@ -251,16 +468,17 @@
|
||||||
</list>
|
</list>
|
||||||
</field>
|
</field>
|
||||||
</page>
|
</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>
|
</page>
|
||||||
</notebook>
|
</notebook>
|
||||||
</sheet>
|
</sheet>
|
||||||
<div class="oe_chatter">
|
<chatter>
|
||||||
<field name="message_follower_ids" widget="mail_followers"/>
|
<field name="message_follower_ids"/>
|
||||||
<field name="activity_ids" widget="mail_activity"/>
|
<field name="message_ids"/>
|
||||||
<field name="message_ids" widget="mail_thread"/>
|
</chatter>
|
||||||
</div>
|
|
||||||
</form>
|
</form>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
@ -277,13 +495,13 @@
|
||||||
<field name="api_type" widget="radio" required="1"/>
|
<field name="api_type" widget="radio" required="1"/>
|
||||||
</group>
|
</group>
|
||||||
|
|
||||||
<group invisible="context.get('api_type') != 'controller'">
|
<group invisible="api_type != 'controller'">
|
||||||
<group string="Controller API Settings">
|
<group string="Controller API Settings">
|
||||||
<button string="Configure Controller API" name="action_configure_controller" type="object" class="btn-primary"/>
|
<button string="Configure Controller API" name="action_configure_controller" type="object" class="btn-primary"/>
|
||||||
</group>
|
</group>
|
||||||
</group>
|
</group>
|
||||||
|
|
||||||
<group invisible="context.get('api_type') != 'site_manager'">
|
<group invisible="api_type != 'site_manager'">
|
||||||
<group string="Site Manager API Settings">
|
<group string="Site Manager API Settings">
|
||||||
<button string="Configure Site Manager API" name="action_configure_site_manager" type="object" class="btn-primary"/>
|
<button string="Configure Site Manager API" name="action_configure_site_manager" type="object" class="btn-primary"/>
|
||||||
</group>
|
</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"/>
|
<field name="last_sync" readonly="1"/>
|
||||||
</group>
|
</group>
|
||||||
<notebook>
|
<notebook>
|
||||||
<page string="Raw Data" name="raw_data">
|
<page string="Données brutes" name="raw_data">
|
||||||
<field name="raw_data" widget="ace" options="{'mode': 'json'}" help="Raw JSON data of the configuration"/>
|
<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>
|
</page>
|
||||||
</notebook>
|
</notebook>
|
||||||
</sheet>
|
</sheet>
|
||||||
|
|
|
||||||
|
|
@ -39,9 +39,10 @@
|
||||||
<field name="updated_at"/>
|
<field name="updated_at"/>
|
||||||
<field name="last_sync"/>
|
<field name="last_sync"/>
|
||||||
</group>
|
</group>
|
||||||
<group string="Données brutes">
|
</page>
|
||||||
<field name="raw_data" widget="ace" options="{'mode': 'json'}"/>
|
<page string="Données brutes">
|
||||||
</group>
|
<field name="raw_data" widget="ace" options="{'language': 'json'}" invisible="1"/>
|
||||||
|
<field name="raw_data_json" readonly="1"/>
|
||||||
</page>
|
</page>
|
||||||
</notebook>
|
</notebook>
|
||||||
</sheet>
|
</sheet>
|
||||||
|
|
|
||||||
|
|
@ -49,9 +49,10 @@
|
||||||
<field name="created_at"/>
|
<field name="created_at"/>
|
||||||
<field name="updated_at"/>
|
<field name="updated_at"/>
|
||||||
</group>
|
</group>
|
||||||
<group string="Données brutes">
|
</page>
|
||||||
<field name="raw_data" widget="ace" options="{'mode': 'json'}" readonly="1"/>
|
<page string="Données brutes">
|
||||||
</group>
|
<field name="raw_data" widget="ace" options="{'language': 'json'}" readonly="1" invisible="1"/>
|
||||||
|
<field name="raw_data_json" readonly="1"/>
|
||||||
</page>
|
</page>
|
||||||
</notebook>
|
</notebook>
|
||||||
</sheet>
|
</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