bemade-addons/unifi_integration/models/udm_site.py
Benoît Vézina b2183d8601 fml unifi
2025-03-11 16:19:49 -04:00

231 lines
9.7 KiB
Python

# -*- coding: utf-8 -*-
from odoo import models, fields, api, _
from odoo.exceptions import UserError
import requests
import json
import logging
import random
from datetime import timedelta
_logger = logging.getLogger(__name__)
class UdmSite(models.Model):
"""Represents a UniFi site managed by one or more UDM Pro
This model is the central entity that groups all UniFi configurations and devices.
Each site can have multiple configurations, devices, networks, and users.
"""
_name = 'udm.site'
_description = 'UniFi Site'
_order = 'name'
_inherit = ['mail.thread']
# Basic site information
name = fields.Char(string='Name', required=True, tracking=True,
help='Site name')
site_id = fields.Char(string='Site ID',
help="Site identifier in UniFi (usually 'default')",
default='default',
readonly=True,
required=True)
description = fields.Text(string='Description', tracking=True,
help='Site description')
address = fields.Text(string='Physical Address', tracking=True,
help='Physical location of this site')
active = fields.Boolean(string='Active', default=True, tracking=True,
help='Indicates if this site is currently active')
# Controller configuration
controller_type = fields.Selection([
('udm', 'UDM/UDR'),
('software', 'Software Controller')
], string='Controller Type', required=True, default='udm', tracking=True,
help='Type of UniFi controller managing this site')
host = fields.Char(string='Host', required=True, tracking=True,
help='IP address or hostname of the controller')
port = fields.Integer(string='Port', default=443, required=True,
help='Port number (default: 443)')
username = fields.Char(string='Username', required=True, tracking=True)
password = fields.Char(string='Password', required=True)
mfa_token = fields.Char(string='MFA Token',
help='Two-factor authentication token if enabled')
# Configuration data
timestamp = fields.Datetime(string='Last Update', tracking=True)
raw_data = fields.Text(string='Raw Data',
help='Raw configuration data in JSON format')
# Related Records
configuration_ids = fields.One2many('udm.configuration', 'site_id',
string='Configurations',
help='Configurations for this site')
network_ids = fields.One2many('udm.network', 'site_id',
string='Networks',
help='Networks in this site')
vlan_ids = fields.One2many('udm.vlan', 'site_id',
string='VLANs',
help='VLANs in this site')
device_ids = fields.One2many('udm.device', 'site_id',
string='Devices',
help='Devices in this site')
user_ids = fields.One2many('udm.user', 'site_id',
string='Users',
help='Users in this site')
firewall_rule_ids = fields.One2many('udm.firewall.rule', 'site_id',
string='Firewall Rules',
help='Firewall rules for this site')
port_forward_ids = fields.One2many('udm.port.forward', 'site_id',
string='Port Forwards',
help='Port forwarding rules for this site')
dns_config_ids = fields.One2many('udm.dns.config', 'site_id',
string='DNS Configurations',
help='DNS configurations for this site')
routing_config_ids = fields.One2many('udm.routing.config', 'site_id',
string='Routing Configurations',
help='Routing configurations for this site')
# Dashboard Metrics
dashboard_metric_ids = fields.One2many('udm.dashboard.metric', 'site_id',
string='Dashboard Metrics',
help='Real-time metrics for this site')
dashboard_stat_ids = fields.One2many('udm.dashboard.stat', 'site_id',
string='Statistics',
help='Historical statistics for this site')
# Computed Fields
config_count = fields.Integer(compute='_compute_counts',
string='Configuration Count',
help='Number of configurations in this site')
device_count = fields.Integer(compute='_compute_device_count',
string='Total Devices',
help='Total number of devices in this site')
client_count = fields.Integer(compute='_compute_client_count',
string='Connected Clients',
help='Number of currently connected clients')
@api.depends('configuration_ids')
def _compute_counts(self):
"""Compute the number of configurations in this site"""
for site in self:
site.config_count = len(site.configuration_ids)
@api.depends('device_ids')
def _compute_device_count(self):
"""Compute the total number of devices in this site"""
for site in self:
site.device_count = len(site.device_ids)
@api.depends('user_ids')
def _compute_client_count(self):
"""Compute the number of connected clients"""
for site in self:
site.client_count = len(site.user_ids.filtered(lambda u: u.is_connected))
def action_view_configurations(self):
"""Open the configurations view filtered for this site"""
self.ensure_one()
return {
'name': _('Configurations'),
'type': 'ir.actions.act_window',
'res_model': 'udm.configuration',
'view_mode': 'tree,form',
'domain': [('site_id', '=', self.id)],
'context': {'default_site_id': self.id},
}
def action_view_dashboard(self):
"""Display the dashboard for this site"""
self.ensure_one()
return {
'name': _('Site Dashboard'),
'type': 'ir.actions.act_window',
'res_model': 'udm.dashboard',
'view_mode': 'kanban,form',
'domain': [('site_id', '=', self.id)],
'context': {'default_site_id': self.id},
}
def action_refresh_metrics(self):
"""Refresh the dashboard metrics and statistics for this site"""
self.ensure_one()
for config in self.configuration_ids:
config.action_update_dashboard_metrics()
_sql_constraints = [
('name_uniq', 'unique(name)',
'Site name must be unique!'),
]
@api.model_create_multi
def create(self, vals_list):
"""Override create to verify connection before saving"""
for vals in vals_list:
if not self._verify_connection(vals):
raise UserError(_('Could not connect to the UniFi controller. '
'Please verify your credentials and connection details.'))
return super(UdmSite, self).create(vals_list)
def write(self, vals):
"""Override write to verify connection if connection details change"""
if any(field in vals for field in ['host', 'port', 'username',
'password', 'mfa_token']):
for record in self:
test_vals = {**record.copy_data()[0], **vals}
if not self._verify_connection(test_vals):
raise UserError(_('Could not connect to the UniFi controller. '
'Please verify your credentials and connection details.'))
return super(UdmSite, self).write(vals)
def _verify_connection(self, vals):
"""Verify connection to the UniFi controller
Args:
vals (dict): Values to use for connection test
Returns:
bool: True if connection successful, False otherwise
"""
try:
# Build base URL based on controller type
base_url = f"https://{vals['host']}:{vals.get('port', 443)}"
if vals.get('controller_type') == 'software':
base_url += '/api'
else:
base_url += '/proxy/network'
# Disable SSL verification warning
import urllib3
urllib3.disable_warnings()
# Try to authenticate
session = requests.Session()
response = session.post(
f"{base_url}/api/auth/login",
json={
'username': vals['username'],
'password': vals['password'],
'remember': True,
'token': vals.get('mfa_token', '')
},
verify=False
)
if response.status_code == 200:
return True
_logger.error(
"Authentication failed for UniFi controller at %s: %s",
vals['host'],
response.text
)
return False
except Exception as e:
_logger.error(
"Error connecting to UniFi controller at %s: %s",
vals['host'],
str(e)
)
return False