- Bump DESK_MOLONI version to 3.0.1 across module - Normalize hooks to after_client_* and instantiate PerfexHooks safely - Fix OAuthController view path and API client class name - Add missing admin views for webhook config/logs; adjust view loading - Harden client portal routes and admin routes mapping - Make Dashboard/Logs/Queue tolerant to optional model methods - Align log details query with existing schema; avoid broken joins This makes the module operational in Perfex (admin + client), reduces 404s, and avoids fatal errors due to inconsistent tables/methods.
476 lines
16 KiB
PHP
476 lines
16 KiB
PHP
<?php
|
|
|
|
defined('BASEPATH') or exit('No direct script access allowed');
|
|
|
|
/**
|
|
* Desk-Moloni Logs Controller
|
|
* Handles log viewing and monitoring
|
|
*
|
|
* @package Desk-Moloni
|
|
* @version 3.0.0
|
|
* @author Descomplicar Business Solutions
|
|
*/
|
|
class Logs extends AdminController
|
|
{
|
|
public function __construct()
|
|
{
|
|
parent::__construct();
|
|
$this->load->model('desk_moloni/desk_moloni_sync_log_model', 'sync_log_model');
|
|
$this->load->model('desk_moloni/desk_moloni_config_model', 'config_model');
|
|
}
|
|
|
|
/**
|
|
* Logs viewing interface
|
|
*/
|
|
public function index()
|
|
{
|
|
if (!has_permission('desk_moloni', '', 'view')) {
|
|
access_denied('desk_moloni');
|
|
}
|
|
|
|
$data = [
|
|
'title' => _l('desk_moloni_sync_logs'),
|
|
'entity_types' => ['client', 'product', 'invoice', 'estimate', 'credit_note'],
|
|
'log_stats' => method_exists($this->sync_log_model, 'get_log_statistics') ? $this->sync_log_model->get_log_statistics() : []
|
|
];
|
|
|
|
$this->load->view('admin/includes/header', $data);
|
|
$this->load->view('admin/modules/desk_moloni/logs', $data);
|
|
$this->load->view('admin/includes/footer');
|
|
}
|
|
|
|
/**
|
|
* Get logs with filtering and pagination
|
|
*/
|
|
public function get_logs()
|
|
{
|
|
if (!has_permission('desk_moloni', '', 'view')) {
|
|
$this->output
|
|
->set_status_header(403)
|
|
->set_content_type('application/json')
|
|
->set_output(json_encode(['success' => false, 'message' => _l('access_denied')]));
|
|
return;
|
|
}
|
|
|
|
try {
|
|
$filters = [
|
|
'entity_type' => $this->input->get('entity_type'),
|
|
'status' => $this->input->get('status'),
|
|
'operation_type' => $this->input->get('operation_type'),
|
|
'direction' => $this->input->get('direction'),
|
|
'from_date' => $this->input->get('from_date'),
|
|
'to_date' => $this->input->get('to_date'),
|
|
'search' => $this->input->get('search')
|
|
];
|
|
|
|
$pagination = [
|
|
'limit' => (int) $this->input->get('limit') ?: 100,
|
|
'offset' => (int) $this->input->get('offset') ?: 0
|
|
];
|
|
|
|
$sort = [
|
|
'field' => $this->input->get('sort_field') ?: 'created_at',
|
|
'direction' => $this->input->get('sort_direction') ?: 'desc'
|
|
];
|
|
|
|
$log_data = $this->sync_log_model->get_filtered_logs($filters, $pagination, $sort);
|
|
|
|
$this->output
|
|
->set_content_type('application/json')
|
|
->set_output(json_encode([
|
|
'success' => true,
|
|
'data' => [
|
|
'total' => $log_data['total'],
|
|
'logs' => $log_data['logs'],
|
|
'pagination' => [
|
|
'current_page' => floor($pagination['offset'] / $pagination['limit']) + 1,
|
|
'per_page' => $pagination['limit'],
|
|
'total_items' => $log_data['total'],
|
|
'total_pages' => ceil($log_data['total'] / $pagination['limit'])
|
|
]
|
|
]
|
|
]));
|
|
|
|
} catch (Exception $e) {
|
|
log_message('error', 'Desk-Moloni get logs error: ' . $e->getMessage());
|
|
|
|
$this->output
|
|
->set_status_header(500)
|
|
->set_content_type('application/json')
|
|
->set_output(json_encode([
|
|
'success' => false,
|
|
'message' => $e->getMessage()
|
|
]));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get detailed log entry
|
|
*/
|
|
public function get_log_details($log_id)
|
|
{
|
|
if (!has_permission('desk_moloni', '', 'view')) {
|
|
$this->output
|
|
->set_status_header(403)
|
|
->set_content_type('application/json')
|
|
->set_output(json_encode(['success' => false, 'message' => _l('access_denied')]));
|
|
return;
|
|
}
|
|
|
|
try {
|
|
$log_id = (int) $log_id;
|
|
|
|
if (!$log_id) {
|
|
throw new Exception(_l('desk_moloni_invalid_log_id'));
|
|
}
|
|
|
|
$log = method_exists($this->sync_log_model, 'get_log_details') ? $this->sync_log_model->get_log_details($log_id) : null;
|
|
|
|
if (!$log) {
|
|
throw new Exception(_l('desk_moloni_log_not_found'));
|
|
}
|
|
|
|
$this->output
|
|
->set_content_type('application/json')
|
|
->set_output(json_encode([
|
|
'success' => true,
|
|
'data' => $log
|
|
]));
|
|
|
|
} catch (Exception $e) {
|
|
log_message('error', 'Desk-Moloni get log details error: ' . $e->getMessage());
|
|
|
|
$this->output
|
|
->set_status_header(400)
|
|
->set_content_type('application/json')
|
|
->set_output(json_encode([
|
|
'success' => false,
|
|
'message' => $e->getMessage()
|
|
]));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get log statistics
|
|
*/
|
|
public function get_statistics()
|
|
{
|
|
if (!has_permission('desk_moloni', '', 'view')) {
|
|
$this->output
|
|
->set_status_header(403)
|
|
->set_content_type('application/json')
|
|
->set_output(json_encode(['success' => false, 'message' => _l('access_denied')]));
|
|
return;
|
|
}
|
|
|
|
try {
|
|
$days = (int) $this->input->get('days') ?: 7;
|
|
$entity_type = $this->input->get('entity_type');
|
|
|
|
$statistics = [
|
|
'summary' => $this->sync_log_model->get_log_summary($days, $entity_type),
|
|
'trends' => $this->sync_log_model->get_log_trends($days, $entity_type),
|
|
'top_errors' => $this->sync_log_model->get_top_errors($days, 10),
|
|
'performance_stats' => $this->sync_log_model->get_performance_statistics($days, $entity_type)
|
|
];
|
|
|
|
$this->output
|
|
->set_content_type('application/json')
|
|
->set_output(json_encode([
|
|
'success' => true,
|
|
'data' => $statistics
|
|
]));
|
|
|
|
} catch (Exception $e) {
|
|
log_message('error', 'Desk-Moloni log statistics error: ' . $e->getMessage());
|
|
|
|
$this->output
|
|
->set_status_header(500)
|
|
->set_content_type('application/json')
|
|
->set_output(json_encode([
|
|
'success' => false,
|
|
'message' => $e->getMessage()
|
|
]));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Export logs
|
|
*/
|
|
public function export()
|
|
{
|
|
if (!has_permission('desk_moloni_view', '', 'view')) {
|
|
access_denied('desk_moloni');
|
|
}
|
|
|
|
try {
|
|
$format = $this->input->get('format') ?: 'csv';
|
|
$filters = [
|
|
'entity_type' => $this->input->get('entity_type'),
|
|
'status' => $this->input->get('status'),
|
|
'operation_type' => $this->input->get('operation_type'),
|
|
'direction' => $this->input->get('direction'),
|
|
'from_date' => $this->input->get('from_date'),
|
|
'to_date' => $this->input->get('to_date'),
|
|
'search' => $this->input->get('search')
|
|
];
|
|
|
|
$logs = $this->sync_log_model->get_logs_for_export($filters);
|
|
|
|
if ($format === 'json') {
|
|
$this->_export_as_json($logs);
|
|
} else {
|
|
$this->_export_as_csv($logs);
|
|
}
|
|
|
|
} catch (Exception $e) {
|
|
log_message('error', 'Desk-Moloni export logs error: ' . $e->getMessage());
|
|
set_alert('danger', $e->getMessage());
|
|
redirect(admin_url('modules/desk_moloni/logs'));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Clear old logs
|
|
*/
|
|
public function clear_old_logs()
|
|
{
|
|
if (!has_permission('desk_moloni', '', 'delete')) {
|
|
$this->output
|
|
->set_status_header(403)
|
|
->set_content_type('application/json')
|
|
->set_output(json_encode(['success' => false, 'message' => _l('access_denied')]));
|
|
return;
|
|
}
|
|
|
|
try {
|
|
$days_old = (int) $this->input->post('days_old') ?: 30;
|
|
$keep_errors = $this->input->post('keep_errors') === '1';
|
|
|
|
$deleted_count = $this->sync_log_model->clear_old_logs($days_old, $keep_errors);
|
|
|
|
$this->output
|
|
->set_content_type('application/json')
|
|
->set_output(json_encode([
|
|
'success' => true,
|
|
'message' => sprintf(
|
|
_l('desk_moloni_logs_cleared'),
|
|
$deleted_count
|
|
),
|
|
'data' => ['deleted_count' => $deleted_count]
|
|
]));
|
|
|
|
} catch (Exception $e) {
|
|
log_message('error', 'Desk-Moloni clear logs error: ' . $e->getMessage());
|
|
|
|
$this->output
|
|
->set_status_header(500)
|
|
->set_content_type('application/json')
|
|
->set_output(json_encode([
|
|
'success' => false,
|
|
'message' => $e->getMessage()
|
|
]));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get error analysis
|
|
*/
|
|
public function get_error_analysis()
|
|
{
|
|
if (!has_permission('desk_moloni_view', '', 'view')) {
|
|
$this->output
|
|
->set_status_header(403)
|
|
->set_content_type('application/json')
|
|
->set_output(json_encode(['success' => false, 'message' => _l('access_denied')]));
|
|
return;
|
|
}
|
|
|
|
try {
|
|
$days = (int) $this->input->get('days') ?: 7;
|
|
|
|
$analysis = [
|
|
'error_categories' => $this->sync_log_model->get_error_categories($days),
|
|
'error_trends' => $this->sync_log_model->get_error_trends($days),
|
|
'frequent_errors' => $this->sync_log_model->get_frequent_errors($days, 20),
|
|
'error_by_entity' => $this->sync_log_model->get_errors_by_entity($days),
|
|
'resolution_suggestions' => $this->_get_resolution_suggestions()
|
|
];
|
|
|
|
$this->output
|
|
->set_content_type('application/json')
|
|
->set_output(json_encode([
|
|
'success' => true,
|
|
'data' => $analysis
|
|
]));
|
|
|
|
} catch (Exception $e) {
|
|
log_message('error', 'Desk-Moloni error analysis error: ' . $e->getMessage());
|
|
|
|
$this->output
|
|
->set_status_header(500)
|
|
->set_content_type('application/json')
|
|
->set_output(json_encode([
|
|
'success' => false,
|
|
'message' => $e->getMessage()
|
|
]));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Search logs
|
|
*/
|
|
public function search()
|
|
{
|
|
if (!has_permission('desk_moloni_view', '', 'view')) {
|
|
$this->output
|
|
->set_status_header(403)
|
|
->set_content_type('application/json')
|
|
->set_output(json_encode(['success' => false, 'message' => _l('access_denied')]));
|
|
return;
|
|
}
|
|
|
|
try {
|
|
$query = $this->input->get('q');
|
|
$limit = (int) $this->input->get('limit') ?: 50;
|
|
|
|
if (empty($query) || strlen($query) < 3) {
|
|
throw new Exception(_l('desk_moloni_search_query_too_short'));
|
|
}
|
|
|
|
$results = $this->sync_log_model->search_logs($query, $limit);
|
|
|
|
$this->output
|
|
->set_content_type('application/json')
|
|
->set_output(json_encode([
|
|
'success' => true,
|
|
'data' => $results
|
|
]));
|
|
|
|
} catch (Exception $e) {
|
|
log_message('error', 'Desk-Moloni search logs error: ' . $e->getMessage());
|
|
|
|
$this->output
|
|
->set_status_header(400)
|
|
->set_content_type('application/json')
|
|
->set_output(json_encode([
|
|
'success' => false,
|
|
'message' => $e->getMessage()
|
|
]));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Export logs as CSV
|
|
*/
|
|
private function _export_as_csv($logs)
|
|
{
|
|
$filename = 'desk_moloni_logs_' . date('Y-m-d_H-i-s') . '.csv';
|
|
|
|
header('Content-Type: text/csv');
|
|
header('Content-Disposition: attachment; filename="' . $filename . '"');
|
|
header('Cache-Control: no-cache, must-revalidate');
|
|
header('Expires: Sat, 26 Jul 1997 05:00:00 GMT');
|
|
|
|
$output = fopen('php://output', 'w');
|
|
|
|
// CSV headers
|
|
$headers = [
|
|
'ID',
|
|
'Timestamp',
|
|
'Operation',
|
|
'Entity Type',
|
|
'Perfex ID',
|
|
'Moloni ID',
|
|
'Direction',
|
|
'Status',
|
|
'Execution Time (ms)',
|
|
'Error Message'
|
|
];
|
|
|
|
fputcsv($output, $headers);
|
|
|
|
// CSV data
|
|
foreach ($logs as $log) {
|
|
$row = [
|
|
$log['id'],
|
|
$log['created_at'],
|
|
$log['operation_type'],
|
|
$log['entity_type'],
|
|
$log['perfex_id'] ?: '',
|
|
$log['moloni_id'] ?: '',
|
|
$log['direction'],
|
|
$log['status'],
|
|
$log['execution_time_ms'] ?: '',
|
|
$log['error_message'] ?: ''
|
|
];
|
|
|
|
fputcsv($output, $row);
|
|
}
|
|
|
|
fclose($output);
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* Export logs as JSON
|
|
*/
|
|
private function _export_as_json($logs)
|
|
{
|
|
$filename = 'desk_moloni_logs_' . date('Y-m-d_H-i-s') . '.json';
|
|
|
|
header('Content-Type: application/json');
|
|
header('Content-Disposition: attachment; filename="' . $filename . '"');
|
|
header('Cache-Control: no-cache, must-revalidate');
|
|
header('Expires: Sat, 26 Jul 1997 05:00:00 GMT');
|
|
|
|
echo json_encode([
|
|
'export_date' => date('Y-m-d H:i:s'),
|
|
'total_records' => count($logs),
|
|
'logs' => $logs
|
|
], JSON_PRETTY_PRINT);
|
|
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* Get resolution suggestions for common errors
|
|
*/
|
|
private function _get_resolution_suggestions()
|
|
{
|
|
return [
|
|
'authentication_failed' => [
|
|
'title' => _l('desk_moloni_auth_error_title'),
|
|
'description' => _l('desk_moloni_auth_error_desc'),
|
|
'actions' => [
|
|
_l('desk_moloni_refresh_oauth_token'),
|
|
_l('desk_moloni_check_api_credentials')
|
|
]
|
|
],
|
|
'rate_limit_exceeded' => [
|
|
'title' => _l('desk_moloni_rate_limit_title'),
|
|
'description' => _l('desk_moloni_rate_limit_desc'),
|
|
'actions' => [
|
|
_l('desk_moloni_reduce_sync_frequency'),
|
|
_l('desk_moloni_implement_backoff')
|
|
]
|
|
],
|
|
'validation_error' => [
|
|
'title' => _l('desk_moloni_validation_error_title'),
|
|
'description' => _l('desk_moloni_validation_error_desc'),
|
|
'actions' => [
|
|
_l('desk_moloni_check_required_fields'),
|
|
_l('desk_moloni_verify_data_format')
|
|
]
|
|
],
|
|
'network_error' => [
|
|
'title' => _l('desk_moloni_network_error_title'),
|
|
'description' => _l('desk_moloni_network_error_desc'),
|
|
'actions' => [
|
|
_l('desk_moloni_check_connectivity'),
|
|
_l('desk_moloni_verify_firewall')
|
|
]
|
|
]
|
|
];
|
|
}
|
|
} |