334 lines
11 KiB
Python
334 lines
11 KiB
Python
# -*- 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, _
|
|
# pylint: enable=import-error
|
|
|
|
import logging
|
|
import json
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
class UnifiApiLog(models.Model):
|
|
"""Logs API calls to UniFi APIs
|
|
|
|
This model stores information about API calls made to both the Site Manager API
|
|
and Controller API, including request details, response status, and timing.
|
|
"""
|
|
_name = 'unifi.api.log'
|
|
_description = 'UniFi API Log'
|
|
_order = 'create_date desc'
|
|
|
|
@api.onchange('site_id')
|
|
def onchange(self, values=None, field_names=None, fields_spec=None):
|
|
"""Implémentation de la méthode abstraite onchange de BaseModel
|
|
|
|
Cette méthode est déclenchée lorsque le site_id change. Elle peut être utilisée
|
|
pour mettre à jour d'autres champs en fonction du site sélectionné.
|
|
|
|
Args:
|
|
values: Dictionnaire des valeurs modifiées
|
|
field_names: Noms des champs modifiés
|
|
fields_spec: Spécification des champs à mettre à jour
|
|
|
|
Returns:
|
|
Un dictionnaire des valeurs à mettre à jour
|
|
"""
|
|
# Actuellement, aucune action spécifique n'est nécessaire lors du changement de site
|
|
# Cette méthode est implémentée pour satisfaire l'exigence de la classe abstraite
|
|
return {}
|
|
|
|
site_id = fields.Many2one(
|
|
comodel_name='unifi.site',
|
|
string='Site',
|
|
required=True,
|
|
ondelete='cascade',
|
|
help='The site this API call was made for'
|
|
)
|
|
|
|
api_config_id = fields.Many2one(
|
|
comodel_name='unifi.api.config',
|
|
string='Configuration API',
|
|
ondelete='cascade',
|
|
help='Configuration API utilisée pour cet appel API'
|
|
)
|
|
|
|
sync_job_id = fields.Many2one(
|
|
comodel_name='unifi.sync.job',
|
|
string='Sync Job',
|
|
ondelete='cascade',
|
|
help='The synchronization job that triggered this API call'
|
|
)
|
|
|
|
api_type = fields.Selection(
|
|
selection=[
|
|
('site_manager', 'Site Manager API'),
|
|
('controller', 'Controller API')
|
|
],
|
|
string='API Type',
|
|
required=True,
|
|
help='Type of API used for this call'
|
|
)
|
|
|
|
endpoint = fields.Char(
|
|
string='Endpoint',
|
|
required=True,
|
|
help='API endpoint that was called'
|
|
)
|
|
|
|
method = fields.Selection(
|
|
selection=[
|
|
('GET', 'GET'),
|
|
('POST', 'POST'),
|
|
('PUT', 'PUT'),
|
|
('DELETE', 'DELETE'),
|
|
('PATCH', 'PATCH')
|
|
],
|
|
string='HTTP Method',
|
|
required=True,
|
|
help='HTTP method used for the API call'
|
|
)
|
|
|
|
request_params = fields.Text(
|
|
string='Request Parameters',
|
|
help='Parameters sent with the request (JSON)'
|
|
)
|
|
|
|
request_headers = fields.Text(
|
|
string='Request Headers',
|
|
help='Headers sent with the request (JSON)'
|
|
)
|
|
|
|
request_body = fields.Text(
|
|
string='Request Body',
|
|
help='Body sent with the request (JSON)'
|
|
)
|
|
|
|
status_code = fields.Integer(
|
|
string='Status Code',
|
|
help='HTTP status code returned by the API'
|
|
)
|
|
|
|
response_headers = fields.Text(
|
|
string='Response Headers',
|
|
help='Headers returned by the API (JSON)'
|
|
)
|
|
|
|
response_body = fields.Text(
|
|
string='Response Body',
|
|
help='Body returned by the API (JSON)'
|
|
)
|
|
|
|
duration = fields.Float(
|
|
string='Duration (ms)',
|
|
help='Time taken to complete the API call in milliseconds'
|
|
)
|
|
|
|
success = fields.Boolean(
|
|
string='Success',
|
|
compute='_compute_success',
|
|
store=True,
|
|
help='Whether the API call was successful'
|
|
)
|
|
|
|
status = fields.Selection(
|
|
selection=[
|
|
('pending', 'Pending'),
|
|
('success', 'Success'),
|
|
('error', 'Error')
|
|
],
|
|
string='Status',
|
|
default='pending',
|
|
help='Current status of the API call'
|
|
)
|
|
|
|
message = fields.Text(
|
|
string='Message',
|
|
help='Message related to the API call'
|
|
)
|
|
|
|
response_code = fields.Integer(
|
|
string='Response Code',
|
|
help='HTTP response code from the API call'
|
|
)
|
|
|
|
response_time = fields.Float(
|
|
string='Response Time',
|
|
help='Time taken for the API call to complete in seconds',
|
|
digits=(16, 6)
|
|
)
|
|
|
|
error_message = fields.Text(
|
|
string='Error Message',
|
|
help='Error message if the API call failed'
|
|
)
|
|
|
|
create_date = fields.Datetime(
|
|
string='Created On',
|
|
readonly=True,
|
|
help='Date and time when this log was created'
|
|
)
|
|
|
|
start_time = fields.Datetime(
|
|
string='Start Time',
|
|
help='Date and time when the API call started'
|
|
)
|
|
|
|
end_time = fields.Datetime(
|
|
string='End Time',
|
|
help='Date and time when the API call completed'
|
|
)
|
|
|
|
request_formatted = fields.Text(
|
|
string='Formatted Request',
|
|
compute='_compute_formatted_data',
|
|
store=False,
|
|
help='Formatted representation of the request'
|
|
)
|
|
|
|
response_formatted = fields.Text(
|
|
string='Formatted Response',
|
|
compute='_compute_formatted_data',
|
|
store=False,
|
|
help='Formatted representation of the response'
|
|
)
|
|
|
|
@api.depends('status_code')
|
|
def _compute_success(self):
|
|
"""Compute whether the API call was successful based on status code"""
|
|
for log in self:
|
|
log.success = log.status_code and 200 <= log.status_code < 300
|
|
|
|
@api.model
|
|
def log_api_call(self, site_id, api_type, endpoint, method, request_params=None,
|
|
request_headers=None, request_body=None, status_code=None,
|
|
response_headers=None, response_body=None, duration=0,
|
|
error_message=None, sync_job_id=None):
|
|
"""Create a new API log entry
|
|
|
|
Args:
|
|
site_id (int): ID of the site
|
|
api_type (str): Type of API ('site_manager' or 'controller')
|
|
endpoint (str): API endpoint
|
|
method (str): HTTP method
|
|
request_params (dict, optional): Request parameters
|
|
request_headers (dict, optional): Request headers
|
|
request_body (dict, optional): Request body
|
|
status_code (int, optional): HTTP status code
|
|
response_headers (dict, optional): Response headers
|
|
response_body (dict, optional): Response body
|
|
duration (float, optional): Duration in milliseconds
|
|
error_message (str, optional): Error message
|
|
sync_job_id (int, optional): ID of the sync job
|
|
|
|
Returns:
|
|
unifi.api.log: The created log record
|
|
"""
|
|
# Convert dictionaries to JSON strings
|
|
if request_params and isinstance(request_params, dict):
|
|
request_params = json.dumps(request_params)
|
|
|
|
if request_headers and isinstance(request_headers, dict):
|
|
request_headers = json.dumps(request_headers)
|
|
|
|
if request_body and isinstance(request_body, dict):
|
|
request_body = json.dumps(request_body)
|
|
|
|
if response_headers and isinstance(response_headers, dict):
|
|
response_headers = json.dumps(response_headers)
|
|
|
|
if response_body and isinstance(response_body, dict):
|
|
response_body = json.dumps(response_body)
|
|
|
|
# Create the log
|
|
return self.create({
|
|
'site_id': site_id,
|
|
'api_type': api_type,
|
|
'endpoint': endpoint,
|
|
'method': method,
|
|
'request_params': request_params,
|
|
'request_headers': request_headers,
|
|
'request_body': request_body,
|
|
'status_code': status_code,
|
|
'response_headers': response_headers,
|
|
'response_body': response_body,
|
|
'duration': duration,
|
|
'error_message': error_message,
|
|
'sync_job_id': sync_job_id,
|
|
})
|
|
|
|
def get_formatted_request(self):
|
|
"""Get a formatted representation of the request
|
|
|
|
Returns:
|
|
str: Formatted request details
|
|
"""
|
|
self.ensure_one()
|
|
|
|
result = f"{self.method} {self.endpoint}\n"
|
|
|
|
if self.request_headers:
|
|
try:
|
|
headers = json.loads(self.request_headers)
|
|
result += "Headers:\n"
|
|
for key, value in headers.items():
|
|
result += f" {key}: {value}\n"
|
|
except:
|
|
result += f"Headers: {self.request_headers}\n"
|
|
|
|
if self.request_params:
|
|
try:
|
|
params = json.loads(self.request_params)
|
|
result += "Parameters:\n"
|
|
for key, value in params.items():
|
|
result += f" {key}: {value}\n"
|
|
except:
|
|
result += f"Parameters: {self.request_params}\n"
|
|
|
|
if self.request_body:
|
|
try:
|
|
body = json.loads(self.request_body)
|
|
result += f"Body: {json.dumps(body, indent=2)}\n"
|
|
except:
|
|
result += f"Body: {self.request_body}\n"
|
|
|
|
return result
|
|
|
|
def get_formatted_response(self):
|
|
"""Get a formatted representation of the response
|
|
|
|
Returns:
|
|
str: Formatted response details
|
|
"""
|
|
self.ensure_one()
|
|
|
|
result = f"Status: {self.status_code}\n"
|
|
|
|
if self.response_headers:
|
|
try:
|
|
headers = json.loads(self.response_headers)
|
|
result += "Headers:\n"
|
|
for key, value in headers.items():
|
|
result += f" {key}: {value}\n"
|
|
except:
|
|
result += f"Headers: {self.response_headers}\n"
|
|
|
|
if self.response_body:
|
|
try:
|
|
body = json.loads(self.response_body)
|
|
result += f"Body: {json.dumps(body, indent=2)}\n"
|
|
except:
|
|
result += f"Body: {self.response_body}\n"
|
|
|
|
if self.error_message:
|
|
result += f"Error: {self.error_message}\n"
|
|
|
|
return result
|
|
|
|
@api.depends('request_headers', 'request_params', 'request_body', 'response_headers', 'response_body', 'error_message')
|
|
def _compute_formatted_data(self):
|
|
"""Compute formatted representations of request and response data"""
|
|
for log in self:
|
|
log.request_formatted = log.get_formatted_request()
|
|
log.response_formatted = log.get_formatted_response()
|