* Copyright (C) 2026 Nick Fragoulis
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY, without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
/**
* \file htdocs/ai/admin/log_viewer.php
* \ingroup ai
* \brief AI Request Log Viewer with Payload Inspection
*/
/**
* @var Conf $conf
* @var DoliDB $db
* @var HookManager $hookmanager
* @var Translate $langs
* @var User $user
* @var Form $form
*/
require '../../main.inc.php';
require_once DOL_DOCUMENT_ROOT . '/core/lib/admin.lib.php';
require_once DOL_DOCUMENT_ROOT . '/core/lib/date.lib.php';
require_once DOL_DOCUMENT_ROOT . '/core/class/html.form.class.php';
// Access Control
if (!$user->admin) {
accessforbidden();
}
// Load translations
$langs->loadLangs(array("admin", "other"));
// Parameters
$action = GETPOST('action', 'aZ09');
$massaction = GETPOST('massaction', 'alpha');
$confirm = GETPOST('confirm', 'alpha');
$toselect = GETPOST('toselect', 'array');
$contextpage = GETPOST('contextpage', 'aZ') ? GETPOST('contextpage', 'aZ') : 'ailoglist';
$optioncss = GETPOST('optioncss', 'alpha');
$mode = GETPOST('mode', 'alpha');
// Search parameters for all columns
$search_date_start = dol_mktime(0, 0, 0, GETPOSTINT('search_date_startmonth'), GETPOSTINT('search_date_startday'), GETPOSTINT('search_date_startyear'));
$search_date_end = dol_mktime(23, 59, 59, GETPOSTINT('search_date_endmonth'), GETPOSTINT('search_date_endday'), GETPOSTINT('search_date_endyear'));
$search_user = GETPOST('search_user', 'alpha');
$search_query = GETPOST('search_query', 'alpha');
$search_tool = GETPOST('search_tool', 'alpha');
$search_provider = GETPOST('search_provider', 'alpha');
$search_time_min = GETPOST('search_time_min', 'alpha');
$search_time_max = GETPOST('search_time_max', 'alpha');
$search_status = GETPOST('search_status', 'alpha');
// Pagination parameters
$limit = GETPOST('limit', 'int') ? GETPOST('limit', 'int') : $conf->liste_limit;
$sortfield = GETPOST('sortfield', 'alpha');
$sortorder = GETPOST('sortorder', 'alpha');
$page = GETPOSTINT("page");
if (empty($page) || $page == -1) {
$page = 0;
}
$offset = $limit * $page;
if (!$sortfield) $sortfield = "l.date_request";
if (!$sortorder) $sortorder = "DESC";
// Initialize array of search criteria
$search_array = array(
'search_date_start' => $search_date_start,
'search_date_end' => $search_date_end,
'search_user' => $search_user,
'search_query' => $search_query,
'search_tool' => $search_tool,
'search_provider' => $search_provider,
'search_time_min' => $search_time_min,
'search_time_max' => $search_time_max,
'search_status' => $search_status
);
/*
* Actions
*/
$error = '';
if ($action == 'purge' && $confirm == 'yes') {
$db->begin();
$sql = "DELETE FROM " . MAIN_DB_PREFIX . "ai_request_log";
$sql .= " WHERE entity IN (" . getEntity('airequestlog') . ")";
$resql = $db->query($sql);
if ($resql) {
$nbDeleted = $db->affected_rows($resql);
$db->commit();
setEventMessages($langs->trans("LogsCleared") . " (" . $nbDeleted . ")", null, 'mesgs');
} else {
$db->rollback();
setEventMessages($db->lasterror(), null, 'errors');
}
header('Location: ' . $_SERVER["PHP_SELF"]);
exit;
}
// Purge selection
if ($massaction == 'purge' && !empty($toselect) && is_array($toselect)) {
$db->begin();
foreach ($toselect as $id) {
$sql = "DELETE FROM " . MAIN_DB_PREFIX . "ai_request_log";
$sql .= " WHERE rowid = " . ((int) $id);
$sql .= " AND entity IN (" . getEntity('airequestlog') . ")";
$resql = $db->query($sql);
if (!$resql) {
$error++;
$db->rollback();
setEventMessages($db->lasterror(), null, 'errors');
break;
}
}
if (!$error) {
$db->commit();
setEventMessages($langs->trans("SelectedLogsDeleted"), null, 'mesgs');
} else {
$db->rollback();
}
$action = 'list';
$massaction = '';
}
// Clear filter action
if (GETPOST('button_removefilter', 'alpha') || GETPOST('button_removefilter_x', 'alpha')) {
$search_date_start = '';
$search_date_end = '';
$search_user = '';
$search_query = '';
$search_tool = '';
$search_provider = '';
$search_time_min = '';
$search_time_max = '';
$search_status = '';
// Reset page
$page = 0;
}
/*
* View
*/
// Initialize array of search criteria for the view
$param = '';
if ($contextpage != $_SERVER["PHP_SELF"]) {
$param .= '&contextpage='.urlencode($contextpage);
}
if ($limit > 0 && $limit != $conf->liste_limit) {
$param .= '&limit='.urlencode($limit);
}
foreach ($search_array as $key => $val) {
if (!empty($val) || $val === '0') {
$param .= '&' . $key . '=' . urlencode($val);
}
}
llxHeader('', $langs->trans("AIRequestLogs"), '');
// Build WHERE clause
$where = array();
$where[] = "l.entity IN (" . getEntity('airequestlog') . ")";
if ($search_date_start) {
$where[] = "l.date_request >= '" . $db->escape(date('Y-m-d H:i:s', $search_date_start)) . "'";
}
if ($search_date_end) {
$where[] = "l.date_request <= '" . $db->escape(date('Y-m-d H:i:s', $search_date_end)) . "'";
}
if ($search_user) {
$where[] = "u.login LIKE '%" . $db->escape($search_user) . "%'";
}
if ($search_query) {
$where[] = "l.query_text LIKE '%" . $db->escape($search_query) . "%'";
}
if ($search_tool) {
$where[] = "l.tool_name LIKE '%" . $db->escape($search_tool) . "%'";
}
if ($search_provider) {
$where[] = "l.provider LIKE '%" . $db->escape($search_provider) . "%'";
}
if ($search_time_min) {
$where[] = "l.execution_time >= " . floatval($search_time_min);
}
if ($search_time_max) {
$where[] = "l.execution_time <= " . floatval($search_time_max);
}
if ($search_status) {
$where[] = "l.status = '" . $db->escape($search_status) . "'";
}
$whereSQL = '';
if (!empty($where)) {
$whereSQL = ' WHERE ' . implode(' AND ', $where);
}
// Get total count for pagination
$sqlCount = "SELECT COUNT(*) as total
FROM " . MAIN_DB_PREFIX . "ai_request_log as l
LEFT JOIN " . MAIN_DB_PREFIX . "user as u ON l.fk_user = u.rowid
$whereSQL";
$resCount = $db->query($sqlCount);
$totalRecords = $resCount ? $db->fetch_object($resCount)->total : 0;
$sql = "SELECT l.*, u.login
FROM " . MAIN_DB_PREFIX . "ai_request_log as l
LEFT JOIN " . MAIN_DB_PREFIX . "user as u ON l.fk_user = u.rowid
$whereSQL
ORDER BY $sortfield $sortorder
LIMIT " . $offset . ", " . $limit;
$res = $db->query($sql);
$num = $db->num_rows($res);
// Create object for list
$object = new stdClass();
$object->total = $totalRecords;
$title = $langs->trans("AIRequestLogs");
print_barre_liste($title, $page, $_SERVER["PHP_SELF"], $param, $sortfield, $sortorder, '', $num, $totalRecords, 'title_ai', 0, '', '', $limit, 1, 0, 0, '');
print '
';
// Display form for filters
print '';
// --- MODAL HTML & JS ---
?>
×
trans("LogDetails"); ?>
trans("ErrorWarning"); ?>
trans("RequestPayload"); ?>
trans("ResponsePayload"); ?>