748 lines
30 KiB
Python
748 lines
30 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
# pylint: disable=import-error
|
|
from odoo import http, _, fields # IDE may report an error, but works in Odoo environment
|
|
from odoo.http import request # IDE may report an error, but works in Odoo environment
|
|
# pylint: enable=import-error
|
|
import logging
|
|
import json # Required to parse API responses and used in processing methods
|
|
import requests
|
|
from requests.exceptions import RequestException, ConnectionError
|
|
import urllib3
|
|
from urllib3.exceptions import InsecureRequestWarning
|
|
from datetime import datetime
|
|
|
|
# Remove warnings for insecure connections
|
|
try:
|
|
# For newer versions of requests
|
|
import urllib3
|
|
urllib3.disable_warnings(category=InsecureRequestWarning)
|
|
except ImportError:
|
|
# For older versions of requests
|
|
try:
|
|
# Disable pylint security warning for this specific line
|
|
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
|
except AttributeError:
|
|
# If neither works, we silently ignore
|
|
pass
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
class UnifiController(http.Controller):
|
|
"""Controller for UniFi related functionalities in Odoo"""
|
|
|
|
@http.route('/unifi/advanced_options', type='http', auth='user')
|
|
def advanced_options_form(self):
|
|
"""Display the advanced options form for the UDM Pro API"""
|
|
return request.render('unifi_integration.advanced_options_form', {
|
|
'default_site': 'default',
|
|
'fixed_only': True,
|
|
'lowercase_hostnames': True,
|
|
})
|
|
|
|
@http.route('/unifi/restart_device', type='http', auth='user')
|
|
def restart_device_form(self):
|
|
"""Display the form to restart a UDM Pro device"""
|
|
if not request.env.user.has_group('unifi_integration.group_unifi_manager'):
|
|
return request.render('unifi_integration.access_denied', {
|
|
'error_message': _("You don't have permission to restart devices.")
|
|
})
|
|
|
|
# Get saved UniFi configurations for the form
|
|
configs = request.env['unifi.site'].sudo().search([])
|
|
return request.render('unifi_integration.restart_device_form', {
|
|
'configs': configs
|
|
})
|
|
|
|
@http.route('/unifi/restart_device', type='http', auth='user', methods=['POST'])
|
|
def restart_device(self, **post):
|
|
"""Process the request to restart a UDM Pro device"""
|
|
if not request.env.user.has_group('unifi_integration.group_unifi_manager'):
|
|
return request.render('unifi_integration.access_denied', {
|
|
'error_message': _("You don't have permission to restart devices.")
|
|
})
|
|
|
|
config_id_str = post.get('config_id')
|
|
if not config_id_str:
|
|
raise ValueError(_('Configuration ID is required'))
|
|
config_id = int(config_id_str)
|
|
mac_address = post.get('mac_address')
|
|
|
|
if not mac_address:
|
|
return request.render('unifi_integration.restart_device_form', {
|
|
'error_message': _("Please provide the MAC address of the device to restart."),
|
|
'configs': request.env['unifi.site'].sudo().search([])
|
|
})
|
|
|
|
try:
|
|
# Get the configuration
|
|
config = request.env['unifi.site'].sudo().browse(config_id)
|
|
if not config.exists():
|
|
raise ValueError(_("Configuration not found"))
|
|
|
|
# Récupérer les informations du contrôleur associé
|
|
controller = request.env['unifi.site.controller'].sudo().search([('site_id', '=', config.id)], limit=1)
|
|
if not controller:
|
|
raise ValueError(_("Controller configuration not found"))
|
|
|
|
# Initialiser le client UniFi
|
|
client = UnifiClient(
|
|
host=controller.host,
|
|
username=controller.username,
|
|
password=controller.password,
|
|
port=controller.port or 443
|
|
)
|
|
|
|
# Authenticate the client
|
|
if not client.login():
|
|
return request.render('unifi_integration.restart_device_form', {
|
|
'error_message': _("Authentication failed. Please check the configuration credentials."),
|
|
'configs': request.env['unifi.site'].sudo().search([])
|
|
})
|
|
|
|
# Restart the device
|
|
success = client.restart_device(mac_address)
|
|
if not success:
|
|
return request.render('unifi_integration.restart_device_form', {
|
|
'error_message': _("Failed to restart the device. Please check logs for details."),
|
|
'configs': request.env['unifi.site'].sudo().search([])
|
|
})
|
|
|
|
return request.render('unifi_integration.restart_success', {
|
|
'mac_address': mac_address
|
|
})
|
|
|
|
except (ConnectionError, RequestException) as e:
|
|
_logger.error("Error during UniFi device restart: %s", str(e))
|
|
return request.render('unifi_integration.restart_device_form', {
|
|
'error_message': _("Error connecting to UDM Pro: %s") % str(e),
|
|
'configs': request.env['unifi.site'].sudo().search([])
|
|
})
|
|
except Exception as e: # pylint: disable=broad-except
|
|
_logger.exception("Unexpected error during UniFi device restart")
|
|
return request.render('unifi_integration.restart_device_form', {
|
|
'error_message': _("An unexpected error occurred: %s") % str(e),
|
|
'configs': request.env['unifi.site'].sudo().search([])
|
|
})
|
|
|
|
@http.route('/unifi/generate_hosts', type='http', auth='user')
|
|
def generate_hosts_form(self):
|
|
"""Display the form to generate a hosts file from UniFi"""
|
|
# Get saved UniFi configurations for the form
|
|
configs = request.env['unifi.site'].sudo().search([])
|
|
return request.render('unifi_integration.generate_hosts_form', {
|
|
'configs': configs,
|
|
'fixed_only': True,
|
|
'lowercase_hostnames': True
|
|
})
|
|
|
|
@http.route('/unifi/generate_hosts', type='http', auth='user', methods=['POST'])
|
|
def generate_hosts(self, **post):
|
|
"""Generate a hosts file from UniFi network clients"""
|
|
config_id_str = post.get('config_id')
|
|
if not config_id_str:
|
|
raise ValueError(_('Configuration ID is required'))
|
|
config_id = int(config_id_str)
|
|
fixed_only = post.get('fixed_only') == 'on'
|
|
lowercase_hostnames = post.get('lowercase_hostnames') == 'on'
|
|
|
|
try:
|
|
# Get the configuration
|
|
config = request.env['unifi.site'].sudo().browse(config_id)
|
|
if not config.exists():
|
|
raise ValueError(_("Configuration not found"))
|
|
|
|
# Récupérer les informations du contrôleur associé
|
|
controller = request.env['unifi.site.controller'].sudo().search([('site_id', '=', config.id)], limit=1)
|
|
if not controller:
|
|
raise ValueError(_("Controller configuration not found"))
|
|
|
|
# Initialize the UniFi client with advanced options
|
|
client = UnifiClient(
|
|
host=controller.host,
|
|
username=controller.username,
|
|
password=controller.password,
|
|
port=controller.port or 443,
|
|
fixed_only=fixed_only,
|
|
lowercase_hostnames=lowercase_hostnames
|
|
)
|
|
|
|
# Authenticate the client
|
|
if not client.login():
|
|
return request.render('unifi_integration.generate_hosts_form', {
|
|
'error_message': _("Authentication failed. Please check the configuration credentials."),
|
|
'configs': request.env['unifi.site'].sudo().search([]),
|
|
'fixed_only': fixed_only,
|
|
'lowercase_hostnames': lowercase_hostnames
|
|
})
|
|
|
|
# Generate the hosts file
|
|
hosts_content = client.generate_hosts_file()
|
|
|
|
# Return the content as a downloadable file
|
|
# Create and configure the HTTP response
|
|
try:
|
|
response = request.make_response(hosts_content)
|
|
if not response:
|
|
raise ValueError(_('Failed to create response'))
|
|
response.headers['Content-Type'] = 'text/plain'
|
|
response.headers['Content-Disposition'] = 'attachment; filename=unifi_hosts.txt'
|
|
return response
|
|
except (AttributeError, TypeError) as e:
|
|
_logger.error('Error creating response: %s', str(e))
|
|
raise ValueError(_('Failed to create response'))
|
|
|
|
except (ConnectionError, RequestException) as e:
|
|
_logger.error("Error during UniFi hosts file generation: %s", str(e))
|
|
return request.render('unifi_integration.generate_hosts_form', {
|
|
'error_message': _("Error connecting to UDM Pro: %s") % str(e),
|
|
'configs': request.env['unifi.site'].sudo().search([]),
|
|
'fixed_only': fixed_only,
|
|
'lowercase_hostnames': lowercase_hostnames
|
|
})
|
|
except Exception as e: # pylint: disable=broad-except
|
|
_logger.exception("Unexpected error during UniFi hosts file generation")
|
|
return request.render('unifi_integration.generate_hosts_form', {
|
|
'error_message': _("An unexpected error occurred: %s") % str(e),
|
|
'configs': request.env['unifi.site'].sudo().search([]),
|
|
'fixed_only': fixed_only,
|
|
'lowercase_hostnames': lowercase_hostnames
|
|
})
|
|
|
|
@http.route('/unifi/network_clients', type='http', auth='user')
|
|
def network_clients_form(self):
|
|
"""Display the form to view UniFi network clients"""
|
|
# Get saved UniFi configurations for the form
|
|
configs = request.env['unifi.site'].sudo().search([])
|
|
return request.render('unifi_integration.network_clients_form', {
|
|
'configs': configs,
|
|
'fixed_only': False,
|
|
'lowercase_hostnames': True
|
|
})
|
|
|
|
@http.route('/unifi/network_clients', type='http', auth='user', methods=['POST'])
|
|
def get_network_clients(self, **post):
|
|
"""Get and display the list of UniFi network clients"""
|
|
config_id_str = post.get('config_id')
|
|
if not config_id_str:
|
|
raise ValueError(_('Configuration ID is required'))
|
|
config_id = int(config_id_str)
|
|
fixed_only = post.get('fixed_only') == 'on'
|
|
lowercase_hostnames = post.get('lowercase_hostnames') == 'on'
|
|
|
|
try:
|
|
# Get the configuration
|
|
config = request.env['unifi.site'].sudo().browse(config_id)
|
|
if not config.exists():
|
|
raise ValueError(_("Configuration not found"))
|
|
|
|
# Récupérer les informations du contrôleur associé
|
|
controller = request.env['unifi.site.controller'].sudo().search([('site_id', '=', config.id)], limit=1)
|
|
if not controller:
|
|
raise ValueError(_("Controller configuration not found"))
|
|
|
|
# Initialize the UniFi client with advanced options
|
|
client = UnifiClient(
|
|
host=controller.host,
|
|
username=controller.username,
|
|
password=controller.password,
|
|
port=controller.port or 443,
|
|
fixed_only=fixed_only,
|
|
lowercase_hostnames=lowercase_hostnames
|
|
)
|
|
|
|
# Authenticate the client
|
|
if not client.login():
|
|
return request.render('unifi_integration.network_clients_form', {
|
|
'error_message': _("Authentication failed. Please check the configuration credentials."),
|
|
'configs': request.env['unifi.site'].sudo().search([]),
|
|
'fixed_only': fixed_only,
|
|
'lowercase_hostnames': lowercase_hostnames
|
|
})
|
|
|
|
# Get network clients
|
|
network_clients = client.get_network_clients()
|
|
|
|
return request.render('unifi_integration.network_clients_result', {
|
|
'clients': network_clients,
|
|
'config': config
|
|
})
|
|
|
|
except (ConnectionError, RequestException) as e:
|
|
_logger.error("Error retrieving UniFi network clients: %s", str(e))
|
|
return request.render('unifi_integration.network_clients_form', {
|
|
'error_message': _("Error connecting to UDM Pro: %s") % str(e),
|
|
'configs': request.env['unifi.site'].sudo().search([]),
|
|
'fixed_only': fixed_only,
|
|
'lowercase_hostnames': lowercase_hostnames
|
|
})
|
|
except ValueError as e:
|
|
_logger.error("Value error retrieving UniFi network clients: %s", str(e))
|
|
return request.render('unifi_integration.network_clients_form', {
|
|
'error_message': _("Configuration error: %s") % str(e),
|
|
'configs': request.env['unifi.site'].sudo().search([]),
|
|
'fixed_only': fixed_only,
|
|
'lowercase_hostnames': lowercase_hostnames
|
|
})
|
|
except (AttributeError, KeyError) as e:
|
|
_logger.error("Data format error retrieving UniFi network clients: %s", str(e))
|
|
return request.render('unifi_integration.network_clients_form', {
|
|
'error_message': _("Data error: %s") % str(e),
|
|
'configs': request.env['unifi.site'].sudo().search([]),
|
|
'fixed_only': fixed_only,
|
|
'lowercase_hostnames': lowercase_hostnames
|
|
})
|
|
except Exception as e: # pylint: disable=broad-except
|
|
# Keep this generic exception as a last resort, with an explicit warning for pylint
|
|
_logger.exception("Unexpected error retrieving UniFi network clients")
|
|
return request.render('unifi_integration.network_clients_form', {
|
|
'error_message': _("An unexpected error occurred: %s") % str(e),
|
|
'configs': request.env['unifi.site'].sudo().search([]),
|
|
'fixed_only': fixed_only,
|
|
'lowercase_hostnames': lowercase_hostnames
|
|
})
|
|
|
|
@http.route('/unifi/import_config', type='http', auth='user')
|
|
def import_config_form(self):
|
|
"""Display the UniFi configuration import form"""
|
|
return request.render('unifi_integration.import_config_form', {})
|
|
|
|
@http.route('/unifi/import_config', type='http', auth='user', methods=['POST'])
|
|
def import_config(self, **post):
|
|
"""Import UniFi configuration from the device"""
|
|
if not request.env.user.has_group('unifi_integration.group_unifi_manager'):
|
|
return request.render('unifi_integration.access_denied', {
|
|
'error_message': _("You don't have permission to import configurations.")
|
|
})
|
|
|
|
host = post.get('host')
|
|
username = post.get('username')
|
|
password = post.get('password')
|
|
port_str = post.get('port')
|
|
port = int(port_str) if port_str else 443
|
|
|
|
if not all([host, username, password]):
|
|
return request.render('unifi_integration.import_config_form', {
|
|
'error_message': _("Please provide all required fields."),
|
|
'host': host,
|
|
'username': username,
|
|
'port': port
|
|
})
|
|
|
|
try:
|
|
# Use the API client to get the configuration
|
|
client = UnifiClient(host, username, password, port)
|
|
if not client.login():
|
|
return request.render('unifi_integration.import_config_form', {
|
|
'error_message': _("Authentication failed. Please check your credentials."),
|
|
'host': host,
|
|
'username': username,
|
|
'port': port
|
|
})
|
|
|
|
config_data = client.get_full_configuration()
|
|
|
|
# Créer un nouveau site UniFi
|
|
site_vals = {
|
|
'name': host,
|
|
'api_type': 'controller',
|
|
'description': 'Imported from %s on %s' % (host, fields.Datetime.now())
|
|
}
|
|
|
|
# Créer le site
|
|
site = request.env['unifi.site'].sudo().create(site_vals)
|
|
|
|
# Créer la configuration du contrôleur associé
|
|
controller_vals = {
|
|
'site_id': site.id,
|
|
'host': host,
|
|
'username': username,
|
|
'password': password,
|
|
'port': port,
|
|
'raw_data': json.dumps(config_data)
|
|
}
|
|
|
|
# Créer le contrôleur
|
|
request.env['unifi.site.controller'].sudo().create(controller_vals)
|
|
|
|
return request.redirect('/web#id=%s&model=unifi.site&view_type=form' % site.id)
|
|
|
|
except (ConnectionError, RequestException) as e:
|
|
_logger.error("Error during UniFi configuration import: %s", str(e))
|
|
return request.render('unifi_integration.import_config_form', {
|
|
'error_message': _("Error connecting to UDM Pro: %s") % str(e),
|
|
'host': host,
|
|
'username': username,
|
|
'port': port
|
|
})
|
|
except (ValueError, TypeError, AttributeError) as e:
|
|
_logger.error("Data processing error during UniFi configuration import: %s", str(e))
|
|
return request.render('unifi_integration.import_config_form', {
|
|
'error_message': _("Error processing data: %s") % str(e),
|
|
'host': host,
|
|
'username': username,
|
|
'port': port
|
|
})
|
|
except Exception as e: # pylint: disable=broad-except
|
|
_logger.exception("Unexpected error during UniFi configuration import")
|
|
return request.render('unifi_integration.import_config_form', {
|
|
'error_message': _("An unexpected error occurred. Please check server logs."),
|
|
'host': host,
|
|
'username': username,
|
|
'port': port
|
|
})
|
|
|
|
|
|
class UnifiClient:
|
|
"""Client to interact with the UniFi API."""
|
|
|
|
# API endpoints
|
|
API_LOGIN_ENDPOINT = '/api/auth/login'
|
|
API_SYSTEM_INFO_ENDPOINT = '/api/system'
|
|
API_NETWORK_ENDPOINT = '/api/networks'
|
|
API_DEVICES_ENDPOINT = '/api/devices'
|
|
API_USERS_ENDPOINT = '/api/users'
|
|
API_SETTINGS_ENDPOINT = '/api/settings'
|
|
API_FIREWALL_ENDPOINT = '/api/firewall'
|
|
|
|
# New endpoints inspired by the Go client
|
|
API_ACTIVE_CLIENTS_ENDPOINT = '/proxy/network/api/s/{site}/stat/sta'
|
|
API_CONFIGURED_CLIENTS_ENDPOINT = '/proxy/network/api/s/{site}/list/user'
|
|
API_DEVICE_RESTART_ENDPOINT = '/proxy/network/api/s/{site}/cmd/devmgr'
|
|
|
|
def __init__(self, host, username, password, port=443, verify_ssl=False, site='default', fixed_only=True, lowercase_hostnames=True, debug=False):
|
|
"""
|
|
Initialize the UniFi API client.
|
|
|
|
Args:
|
|
host (str): IP address or hostname of the UniFi controller
|
|
username (str): Username for the API
|
|
password (str): Password for the API
|
|
port (int): Port for connection (default 443)
|
|
verify_ssl (bool): Verify SSL certificate (default False)
|
|
site (str): Site identifier for UniFi API (default 'default')
|
|
fixed_only (bool): Only consider clients with fixed IP address (default True)
|
|
lowercase_hostnames (bool): Convert hostnames to lowercase (default True)
|
|
debug (bool): Enable debug mode for HTTP requests (default False)
|
|
"""
|
|
self.host = host
|
|
self.username = username
|
|
self.password = password
|
|
self.port = port
|
|
self.verify_ssl = verify_ssl
|
|
self.site = site
|
|
self.fixed_only = fixed_only
|
|
self.lowercase_hostnames = lowercase_hostnames
|
|
self.debug = debug
|
|
self.base_url = f"https://{host}:{port}"
|
|
self.token = None
|
|
self.csrf_token = None
|
|
self.session = requests.Session()
|
|
self.session.verify = verify_ssl
|
|
|
|
def _get_auth_headers(self):
|
|
"""Return authentication headers."""
|
|
if not self.token:
|
|
raise ValueError("Not authenticated. Call login() first.")
|
|
|
|
headers = {
|
|
"Authorization": f"Bearer {self.token}",
|
|
"Content-Type": "application/json"
|
|
}
|
|
|
|
# Add CSRF token if available
|
|
if self.csrf_token:
|
|
headers["X-Csrf-Token"] = self.csrf_token
|
|
|
|
return headers
|
|
|
|
def login(self):
|
|
"""
|
|
Authenticates the client with the UDM Pro API.
|
|
|
|
Returns:
|
|
bool: True if authentication succeeded, False otherwise
|
|
"""
|
|
try:
|
|
login_url = f"{self.base_url}{self.API_LOGIN_ENDPOINT}"
|
|
payload = {
|
|
"username": self.username,
|
|
"password": self.password
|
|
}
|
|
|
|
_logger.debug("Attempting to connect to %s", login_url)
|
|
response = self.session.post(login_url, json=payload)
|
|
response.raise_for_status()
|
|
|
|
# Capture CSRF token if it exists
|
|
csrf_token = response.headers.get('X-Csrf-Token')
|
|
if csrf_token:
|
|
self.csrf_token = csrf_token
|
|
_logger.debug("CSRF token captured: %s", csrf_token)
|
|
|
|
data = response.json()
|
|
if not data.get('token'):
|
|
_logger.error("No token was returned by the API")
|
|
return False
|
|
|
|
self.token = data['token']
|
|
_logger.debug("Authentication successful")
|
|
return True
|
|
|
|
except RequestException as e:
|
|
_logger.error("Authentication error: %s", str(e))
|
|
return False
|
|
|
|
def _make_api_request(self, method, endpoint, params=None, data=None, retry=True):
|
|
"""
|
|
Makes an API request.
|
|
|
|
Args:
|
|
method (str): HTTP method (GET, POST, etc.)
|
|
endpoint (str): API endpoint
|
|
params (dict): Request parameters
|
|
data (dict): Data to send in request body
|
|
retry (bool): Retry on authentication error
|
|
|
|
Returns:
|
|
dict: JSON response from API
|
|
"""
|
|
if not self.token and retry:
|
|
_logger.debug("Not authenticated, attempting authentication")
|
|
if not self.login():
|
|
raise ConnectionError("Unable to authenticate with UDM Pro API")
|
|
|
|
url = f"{self.base_url}{endpoint}"
|
|
try:
|
|
_logger.debug("Request %s to %s", method, url)
|
|
headers = self._get_auth_headers()
|
|
response = None
|
|
|
|
try:
|
|
response = self.session.request(
|
|
method=method,
|
|
url=url,
|
|
headers=headers,
|
|
params=params,
|
|
json=data
|
|
)
|
|
|
|
# Capture CSRF token if it exists in the response
|
|
csrf_token = response.headers.get('X-Csrf-Token')
|
|
if csrf_token and csrf_token != self.csrf_token:
|
|
self.csrf_token = csrf_token
|
|
_logger.debug("CSRF token updated: %s", csrf_token)
|
|
|
|
response.raise_for_status()
|
|
return response.json()
|
|
|
|
except RequestException as request_error:
|
|
if response and (response.status_code == 401 or response.status_code == 403):
|
|
if retry:
|
|
_logger.debug("Token expired, attempting re-authentication")
|
|
self.token = None
|
|
return self._make_api_request(method, endpoint, params, data, retry=False)
|
|
_logger.error("API error (%s %s): %s", method, url, str(request_error))
|
|
raise
|
|
|
|
except Exception as e:
|
|
_logger.error("Unexpected error in API request: %s", str(e))
|
|
raise
|
|
|
|
def get_system_info(self):
|
|
"""
|
|
Gets system information from UniFi controller.
|
|
|
|
Returns:
|
|
dict: System information
|
|
"""
|
|
return self._make_api_request('GET', self.API_SYSTEM_INFO_ENDPOINT)
|
|
|
|
def get_networks(self):
|
|
"""
|
|
Gets network configuration.
|
|
|
|
Returns:
|
|
dict: Network configuration
|
|
"""
|
|
return self._make_api_request('GET', self.API_NETWORK_ENDPOINT)
|
|
|
|
def get_devices(self):
|
|
"""
|
|
Gets the list of devices.
|
|
|
|
Returns:
|
|
dict: List of devices
|
|
"""
|
|
return self._make_api_request('GET', self.API_DEVICES_ENDPOINT)
|
|
|
|
def get_users(self):
|
|
"""
|
|
Gets the list of users.
|
|
|
|
Returns:
|
|
dict: List of users
|
|
"""
|
|
return self._make_api_request('GET', self.API_USERS_ENDPOINT)
|
|
|
|
def get_settings(self):
|
|
"""
|
|
Gets general settings.
|
|
|
|
Returns:
|
|
dict: General settings
|
|
"""
|
|
return self._make_api_request('GET', self.API_SETTINGS_ENDPOINT)
|
|
|
|
def get_firewall_rules(self):
|
|
"""
|
|
Gets firewall rules.
|
|
|
|
Returns:
|
|
dict: Firewall rules
|
|
"""
|
|
return self._make_api_request('GET', self.API_FIREWALL_ENDPOINT)
|
|
|
|
def get_active_clients(self):
|
|
"""
|
|
Gets the list of currently connected clients.
|
|
|
|
Returns:
|
|
list: List of active clients
|
|
"""
|
|
endpoint = self.API_ACTIVE_CLIENTS_ENDPOINT.format(site=self.site)
|
|
response = self._make_api_request('GET', endpoint)
|
|
|
|
if not response or 'data' not in response:
|
|
return []
|
|
|
|
return response.get('data', [])
|
|
|
|
def get_configured_clients(self):
|
|
"""
|
|
Gets the list of statically configured clients.
|
|
|
|
Returns:
|
|
list: List of configured clients
|
|
"""
|
|
endpoint = self.API_CONFIGURED_CLIENTS_ENDPOINT.format(site=self.site)
|
|
response = self._make_api_request('GET', endpoint)
|
|
|
|
if not response or 'data' not in response:
|
|
return []
|
|
|
|
return response.get('data', [])
|
|
|
|
def restart_device(self, mac_address):
|
|
"""
|
|
Restarts a device managed by UniFi controller (e.g. WiFi access point).
|
|
Requires 'site admin' level permissions.
|
|
|
|
Args:
|
|
mac_address (str): MAC address of device to restart
|
|
|
|
Returns:
|
|
bool: True if operation succeeded, False otherwise
|
|
"""
|
|
endpoint = self.API_DEVICE_RESTART_ENDPOINT.format(site=self.site)
|
|
payload = {
|
|
'mac': mac_address,
|
|
'reboot_type': 'soft',
|
|
'cmd': 'restart'
|
|
}
|
|
|
|
try:
|
|
response = self._make_api_request('POST', endpoint, data=payload)
|
|
if response and response.get('meta', {}).get('rc') == 'ok':
|
|
return True
|
|
return False
|
|
except Exception as e: # pylint: disable=broad-except
|
|
_logger.error("Error while restarting device %s: %s", mac_address, str(e))
|
|
return False
|
|
|
|
def get_network_clients(self):
|
|
"""
|
|
Gets all network clients (active and/or configured according to parameters).
|
|
|
|
Returns:
|
|
list: List of network clients
|
|
"""
|
|
clients = []
|
|
|
|
# Always include statically configured clients
|
|
configured_clients = self.get_configured_clients()
|
|
clients.extend(configured_clients)
|
|
|
|
# Include active clients if fixed_only is False
|
|
if not self.fixed_only:
|
|
active_clients = self.get_active_clients()
|
|
clients.extend(active_clients)
|
|
|
|
# Process hostnames if needed
|
|
if self.lowercase_hostnames:
|
|
for client in clients:
|
|
if client.get('hostname'):
|
|
client['hostname'] = client['hostname'].lower()
|
|
if client.get('name'):
|
|
client['name'] = client['name'].lower()
|
|
|
|
return clients
|
|
|
|
def generate_hosts_file(self):
|
|
"""
|
|
Generates a hosts file from network clients.
|
|
|
|
Returns:
|
|
str: Content of hosts file
|
|
"""
|
|
clients = self.get_network_clients()
|
|
hosts_content = "# UniFi Generated Hosts File\n"
|
|
hosts_content += "# Generated on {}\n\n".format(datetime.now().strftime('%Y-%m-%d %H:%M:%S'))
|
|
|
|
for client in clients:
|
|
name = client.get('name') or client.get('hostname')
|
|
ip = client.get('fixed_ip') or client.get('ip')
|
|
mac = client.get('mac', '')
|
|
|
|
if name and ip:
|
|
hosts_content += "{:16s} {:30s} # {}\n".format(ip, name, mac)
|
|
|
|
return hosts_content
|
|
|
|
def get_full_configuration(self):
|
|
"""
|
|
Gets complete UniFi configuration.
|
|
|
|
Returns:
|
|
dict: Complete UniFi configuration
|
|
"""
|
|
# Authenticate first
|
|
if not self.token and not self.login():
|
|
raise ConnectionError("Authentication failed while retrieving complete configuration")
|
|
|
|
try:
|
|
# Get each part of the configuration
|
|
system_info = self.get_system_info()
|
|
networks = self.get_networks()
|
|
devices = self.get_devices()
|
|
users = self.get_users()
|
|
settings = self.get_settings()
|
|
firewall = self.get_firewall_rules()
|
|
|
|
# Also get network clients (new feature)
|
|
network_clients = self.get_network_clients()
|
|
|
|
# Combine all parts into a single dictionary
|
|
return {
|
|
'system_info': system_info,
|
|
'networks': networks,
|
|
'devices': devices,
|
|
'users': users,
|
|
'settings': settings,
|
|
'firewall': firewall,
|
|
'network_clients': network_clients
|
|
}
|
|
|
|
except RequestException as e:
|
|
_logger.error("Error while retrieving complete configuration: %s", str(e))
|
|
raise
|