diff --git a/ProcessMaker/Http/Controllers/Admin/LogsController.php b/ProcessMaker/Http/Controllers/Admin/LogsController.php
new file mode 100644
index 0000000000..37ef170c8c
--- /dev/null
+++ b/ProcessMaker/Http/Controllers/Admin/LogsController.php
@@ -0,0 +1,20 @@
+path($file->id . '/' . $file->file_name);
- // Register the Event
+ // Inline preview when requested
+ if (request()->boolean('inline', false)) {
+ if (!empty($file->file_name)) {
+ FilesDownloaded::dispatch($file);
+ }
+
+ return response()->file($path, [
+ 'Content-Type' => $file->mime_type,
+ 'Content-Disposition' => 'inline; filename="' . addslashes($file->file_name) . '"',
+ ]);
+ }
+
+ // Default: force download
if (!empty($file->file_name)) {
FilesDownloaded::dispatch($file);
}
diff --git a/ProcessMaker/Http/Middleware/GenerateMenus.php b/ProcessMaker/Http/Middleware/GenerateMenus.php
index f714b52ac5..4ffa3153e6 100644
--- a/ProcessMaker/Http/Middleware/GenerateMenus.php
+++ b/ProcessMaker/Http/Middleware/GenerateMenus.php
@@ -128,6 +128,14 @@ public function handle(Request $request, Closure $next)
'file' => "data:image/svg+xml;base64,{$devlinkIcon}",
]);
}
+ if (\Auth::user()->canAny('view-settings|edit-settings') &&
+ (hasPackage('package-email-start-event') || hasPackage('package-ai'))) {
+ $submenu->add(__('Logs'), [
+ 'route' => 'admin.logs',
+ 'icon' => 'fa-bars',
+ 'id' => 'admin-logs',
+ ]);
+ }
});
Menu::make('sidebar_task', function ($menu) {
$submenu = $menu->add(__('Tasks'));
diff --git a/ProcessMaker/ImportExport/Exporters/MediaExporter.php b/ProcessMaker/ImportExport/Exporters/MediaExporter.php
index 91c9d62b24..aee92aced0 100644
--- a/ProcessMaker/ImportExport/Exporters/MediaExporter.php
+++ b/ProcessMaker/ImportExport/Exporters/MediaExporter.php
@@ -44,10 +44,22 @@ public function import(): bool
$ref = $this->getReference(DependentType::MEDIA);
if ($ref && isset($ref['base64'])) {
- $this->model->model->addMediaFromBase64($ref['base64'])
+ $newMedia = $this->model->model->addMediaFromBase64($ref['base64'])
->usingFileName($this->model->file_name)
->withCustomProperties($this->model->custom_properties)
->toMediaCollection($this->model->collection_name);
+
+ if (hasPackage('package-ai')) {
+ $updaterClass = 'ProcessMaker\\Package\\PackageAi\\Services\\FlowGenieMediaConfigUpdater';
+ if (class_exists($updaterClass)) {
+ $updaterClass::updateForMediaImport(
+ (string) $this->model->model_type,
+ (int) $this->model->model_id,
+ (int) $this->model->id,
+ (int) $newMedia->id
+ );
+ }
+ }
}
// We should delete the model, because the Spatie library recreates it.
diff --git a/database/migrations/2025_04_08_115507_add_the_comments_field_to_the_process_request_token_table.php b/database/migrations/2025_04_08_115507_add_the_comments_field_to_the_process_request_token_table.php
index 69811599b1..4877eec6d9 100644
--- a/database/migrations/2025_04_08_115507_add_the_comments_field_to_the_process_request_token_table.php
+++ b/database/migrations/2025_04_08_115507_add_the_comments_field_to_the_process_request_token_table.php
@@ -4,8 +4,7 @@
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
-return new class extends Migration
-{
+return new class extends Migration {
/**
* Run the migrations.
*/
diff --git a/resources/js/admin/logs/components/Logs/AgentSessionDetail/AgentSessionDetail.vue b/resources/js/admin/logs/components/Logs/AgentSessionDetail/AgentSessionDetail.vue
new file mode 100644
index 0000000000..d8a45bbd3c
--- /dev/null
+++ b/resources/js/admin/logs/components/Logs/AgentSessionDetail/AgentSessionDetail.vue
@@ -0,0 +1,929 @@
+
+
+
+
+
+
+
+ {{ sessionData.flow_genie_name || sessionData.agent_name || $t('Session Details') }}
+
+
{{ session.session_id }}
+
+
+
+
+
+ {{ formatStatus(session.status) }}
+
+
+
+
+
+
+
+
+
+
{{ $t('Loading session details...') }}
+
+
+
+
+
+
+
+
+
+
+
{{ $t('Duration') }}
+
+ {{ session.duration || formatDuration(sessionData.execution_time_ms) }}
+
+
+
+
{{ $t('LLM Calls') }}
+
{{ llmCallsCount }}
+
+
+
{{ $t('Tool Calls') }}
+
{{ toolCallsCount }}
+
+
+
{{ $t('Total Tokens') }}
+
+
+
+ {{ session.tokens_used || formatNumber(sessionData.token_usage?.total_tokens) }}
+
+
+
+
+
+
+
+ {{ $t('Input') }}:
+ {{ formatNumber(sessionData.token_usage?.input_tokens) }}
+
+
+ {{ $t('Output') }}:
+ {{ formatNumber(sessionData.token_usage?.output_tokens) }}
+
+
+
+
+
+
+
+
+
+
+
+
{{ $t('No events recorded for this session') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ getEventTypeLabel(event) }}
+ {{ getEventName(event) }}
+
+
+
+ {{ formatDuration(event.duration_ms) }}
+
+
+ {{ formatEventTime(event.timestamp || event.created_at) }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{{ $t('Status') }}
+
+ {{ event.status }}
+
+
+
+
{{ $t('Arguments') }}
+
{{ formatJson(event.arguments || event.input || event.tool_arguments) }}
+
+
+
{{ $t('Output') }}
+
{{ formatJson(event.output || event.result) }}
+
+
+
+
+
+
+
{{ $t('Status') }}
+
+ {{ event.status }}
+
+
+ ({{ event.input_tokens || 0 }} in / {{ event.output_tokens || 0 }} out tokens)
+
+
+
+
{{ $t('Error') }}
+
{{ event.error }}
+
+
+
{{ $t('Input') }}
+
{{ event.input_preview || event.prompt || event.instructions }}
+
+
+
{{ $t('Output') }}
+
{{ event.output_text || event.response || event.output }}
+
+
+
+
+
+
{{ $t('Content') }}
+
{{ event.content }}
+
+
+
+
+
{{ $t('Reasoning') }}
+
{{ event.content }}
+
+
+
+
+
{{ $t('Error Message') }}
+
{{ event.error || event.error_message || event.message || event.data?.error || formatJson(event) }}
+
+
+
+
+
{{ formatJson(event.data || event.details || event) }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ $t('Properties') }}
+
+
+
+
+
{{ $t('Session ID') }}
+
{{ session.session_id }}
+
+
+
+
{{ $t('Model') }}
+
{{ sessionData.model || session.model }}
+
+
+
+
{{ $t('User') }}
+
{{ session.user_name }}
+
+
+
+
{{ $t('Started') }}
+
{{ formatFullDateTime(session.created_at) }}
+
+
+
+
{{ $t('Process') }}
+
{{ session.process_name }}
+
+
+
+
{{ $t('Node') }}
+
{{ session.node_name }}
+
+
+
+
+
+
+
+ {{ $t('Resources') }}
+
+
+
+
{{ $t('MCP Servers') }}
+
+
+ {{ server }}
+
+
+
+
+
+
{{ $t('Collections') }}
+
+
+
+
+
+
+
+
+ {{ $t('Configuration') }}
+
+
+
+
+
+
+ {{ $t('Model Settings') }}
+
+
+
+ {{ formatConfigKey(key) }}
+ {{ formatConfigValue(value) }}
+
+
+
+
+
+
+
+ {{ $t('Max Turns') }}
+ {{ sessionData.max_turns }}
+
+
+
+
+
+
+ {{ $t('Instructions') }}
+
+
+ {{ truncateText(configInstructions, 300) }}
+
+
+
+
+
+
+ {{ $t('Request Config') }}
+
+
+
+ {{ formatConfigKey(key) }}
+ {{ formatConfigValue(value) }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/resources/js/admin/logs/components/Logs/AgentSessionDetail/index.js b/resources/js/admin/logs/components/Logs/AgentSessionDetail/index.js
new file mode 100644
index 0000000000..01a900019c
--- /dev/null
+++ b/resources/js/admin/logs/components/Logs/AgentSessionDetail/index.js
@@ -0,0 +1,5 @@
+import AgentSessionDetail from "./AgentSessionDetail.vue";
+
+export { AgentSessionDetail };
+export default AgentSessionDetail;
+
diff --git a/resources/js/admin/logs/components/Logs/BaseTable/BaseTable.vue b/resources/js/admin/logs/components/Logs/BaseTable/BaseTable.vue
new file mode 100644
index 0000000000..da4b99b837
--- /dev/null
+++ b/resources/js/admin/logs/components/Logs/BaseTable/BaseTable.vue
@@ -0,0 +1,87 @@
+
+
+
+
+
+ |
+ {{ column.label }}
+ |
+
+
+
+
+ |
+
+ {{ getItemValue(item, column) }}
+
+ |
+
+
+
+
+
+
+
diff --git a/resources/js/admin/logs/components/Logs/BaseTable/index.js b/resources/js/admin/logs/components/Logs/BaseTable/index.js
new file mode 100644
index 0000000000..f8430fb194
--- /dev/null
+++ b/resources/js/admin/logs/components/Logs/BaseTable/index.js
@@ -0,0 +1,3 @@
+// eslint-disable-next-line import/prefer-default-export
+export { default as BaseTable } from './BaseTable.vue';
+
diff --git a/resources/js/admin/logs/components/Logs/HeaderBar/HeaderBar.vue b/resources/js/admin/logs/components/Logs/HeaderBar/HeaderBar.vue
new file mode 100644
index 0000000000..6878c1c6fc
--- /dev/null
+++ b/resources/js/admin/logs/components/Logs/HeaderBar/HeaderBar.vue
@@ -0,0 +1,148 @@
+
+
+
+
+
+ {{ $t('Error Logs') }}
+
+
+ {{ $t('Matched Logs') }}
+
+
+ {{ $t('Total Logs') }}
+
+
+
+
+
+
+ {{ $t('FlowGenie Studio Logs') }}
+
+
+ {{ $t('Runtime Logs') }}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/resources/js/admin/logs/components/Logs/HeaderBar/index.js b/resources/js/admin/logs/components/Logs/HeaderBar/index.js
new file mode 100644
index 0000000000..c2176bf01c
--- /dev/null
+++ b/resources/js/admin/logs/components/Logs/HeaderBar/index.js
@@ -0,0 +1,3 @@
+// eslint-disable-next-line import/prefer-default-export
+export { default as HeaderBar } from './HeaderBar.vue';
+
diff --git a/resources/js/admin/logs/components/Logs/LogContainer/LogContainer.vue b/resources/js/admin/logs/components/Logs/LogContainer/LogContainer.vue
new file mode 100644
index 0000000000..9825c0a42e
--- /dev/null
+++ b/resources/js/admin/logs/components/Logs/LogContainer/LogContainer.vue
@@ -0,0 +1,159 @@
+
+
+
+
+
+
+
+
+
+
+
+ {{ $t(title) }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/resources/js/admin/logs/components/Logs/LogContainer/index.js b/resources/js/admin/logs/components/Logs/LogContainer/index.js
new file mode 100644
index 0000000000..d0ad929b7e
--- /dev/null
+++ b/resources/js/admin/logs/components/Logs/LogContainer/index.js
@@ -0,0 +1,3 @@
+// eslint-disable-next-line import/prefer-default-export
+export { default as LogContainer } from './LogContainer.vue';
+
diff --git a/resources/js/admin/logs/components/Logs/LogTable/LogTable.vue b/resources/js/admin/logs/components/Logs/LogTable/LogTable.vue
new file mode 100644
index 0000000000..690552e324
--- /dev/null
+++ b/resources/js/admin/logs/components/Logs/LogTable/LogTable.vue
@@ -0,0 +1,317 @@
+
+
+
+
+
+
+
{{ $t('Loading...') }}
+
+
+
+
+
+
+
+
+ #{{ value }}
+
+ -
+
+
+
+
+
+ {{ formatStatus(value) }}
+
+
+
+
+
+
+
+ {{ value }}
+
+
+
+
+
+ {{ $t('Input') }}:
+ {{ item.input_tokens }}
+
+
+ {{ $t('Output') }}:
+ {{ item.output_tokens }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/resources/js/admin/logs/components/Logs/LogTable/index.js b/resources/js/admin/logs/components/Logs/LogTable/index.js
new file mode 100644
index 0000000000..d07b1072e7
--- /dev/null
+++ b/resources/js/admin/logs/components/Logs/LogTable/index.js
@@ -0,0 +1,3 @@
+// eslint-disable-next-line import/prefer-default-export
+export { default as LogTable } from './LogTable.vue';
+
diff --git a/resources/js/admin/logs/components/Logs/Pagination/Pagination.vue b/resources/js/admin/logs/components/Logs/Pagination/Pagination.vue
new file mode 100644
index 0000000000..33b47377b4
--- /dev/null
+++ b/resources/js/admin/logs/components/Logs/Pagination/Pagination.vue
@@ -0,0 +1,89 @@
+
+
+
+
+
+
+
+
+
diff --git a/resources/js/admin/logs/components/Logs/Pagination/index.js b/resources/js/admin/logs/components/Logs/Pagination/index.js
new file mode 100644
index 0000000000..d2d0ae6aa6
--- /dev/null
+++ b/resources/js/admin/logs/components/Logs/Pagination/index.js
@@ -0,0 +1,3 @@
+// eslint-disable-next-line import/prefer-default-export
+export { default as Pagination } from './Pagination.vue';
+
diff --git a/resources/js/admin/logs/components/Logs/Sidebar/Sidebar.vue b/resources/js/admin/logs/components/Logs/Sidebar/Sidebar.vue
new file mode 100644
index 0000000000..a2276bbff7
--- /dev/null
+++ b/resources/js/admin/logs/components/Logs/Sidebar/Sidebar.vue
@@ -0,0 +1,196 @@
+
+
+
+
+
diff --git a/resources/js/admin/logs/components/Logs/Sidebar/index.js b/resources/js/admin/logs/components/Logs/Sidebar/index.js
new file mode 100644
index 0000000000..a4f8b5ce94
--- /dev/null
+++ b/resources/js/admin/logs/components/Logs/Sidebar/index.js
@@ -0,0 +1,3 @@
+// eslint-disable-next-line import/prefer-default-export
+export { default as Sidebar } from './Sidebar.vue';
+
diff --git a/resources/js/admin/logs/components/Logs/routes.js b/resources/js/admin/logs/components/Logs/routes.js
new file mode 100644
index 0000000000..398d462193
--- /dev/null
+++ b/resources/js/admin/logs/components/Logs/routes.js
@@ -0,0 +1,92 @@
+import { LogTable } from "./LogTable";
+
+export default {};
+
+/**
+ * Check if a package is installed
+ * @param {string} packageName - The package name to check
+ * @returns {boolean}
+ */
+const isPackageInstalled = (packageName) => window.ProcessMaker?.packages?.includes(packageName);
+
+/**
+ * Check if email start event package is installed
+ * @returns {boolean}
+ */
+export const hasEmailPackage = () => isPackageInstalled("package-email-start-event");
+
+/**
+ * Check if AI package is installed
+ * @returns {boolean}
+ */
+export const hasAiPackage = () => isPackageInstalled("package-ai");
+
+/**
+ * Determine the default redirect path based on installed packages
+ * @returns {string}
+ */
+const getDefaultRedirectPath = () => {
+ if (hasEmailPackage()) {
+ return "/email/errors";
+ }
+ if (hasAiPackage()) {
+ return "/agents/design";
+ }
+ // Fallback - shouldn't happen if menu visibility is correct
+ return "/email/errors";
+};
+
+export const routes = [
+ {
+ name: "logs.index",
+ path: "/",
+ beforeEnter: (to, from, next) => {
+ next(getDefaultRedirectPath());
+ },
+ },
+ // Email logs routes
+ {
+ name: "logs.email",
+ path: "/email/:logType",
+ component: LogTable,
+ props(route) {
+ return {
+ category: "email",
+ logType: route.params.logType,
+ };
+ },
+ beforeEnter: (to, from, next) => {
+ if (!hasEmailPackage()) {
+ // Redirect to agents if email package not installed
+ next(hasAiPackage() ? "/agents/design" : "/");
+ } else {
+ next();
+ }
+ },
+ },
+ // FlowGenie Agents logs routes
+ {
+ name: "logs.agents.redirect",
+ path: "/agents",
+ redirect: "/agents/design",
+ },
+ {
+ name: "logs.agents",
+ path: "/agents/:logType",
+ component: LogTable,
+ props(route) {
+ return {
+ category: "agents",
+ logType: route.params.logType,
+ };
+ },
+ beforeEnter: (to, from, next) => {
+ if (!hasAiPackage()) {
+ // Redirect to email if AI package not installed
+ next(hasEmailPackage() ? "/email/errors" : "/");
+ } else {
+ next();
+ }
+ },
+ },
+];
diff --git a/resources/js/admin/logs/index.js b/resources/js/admin/logs/index.js
new file mode 100644
index 0000000000..bdf9dc220b
--- /dev/null
+++ b/resources/js/admin/logs/index.js
@@ -0,0 +1,26 @@
+import { LogContainer } from './components/Logs/LogContainer';
+import { routes } from './components/Logs/routes';
+
+// eslint-disable-next-line no-undef
+Vue.use(VueRouter);
+
+// eslint-disable-next-line no-undef
+const router = new VueRouter({
+ mode: 'history',
+ base: '/admin/logs',
+ routes,
+});
+
+window.Vue.component('admin-logs', LogContainer);
+
+document.addEventListener('DOMContentLoaded', () => {
+ new window.Vue({
+ el: '#admin-logs-main',
+ router,
+ components: {
+ LogContainer,
+ },
+ render: (h) => h(LogContainer),
+ });
+});
+
diff --git a/resources/js/admin/logs/utils/date.js b/resources/js/admin/logs/utils/date.js
new file mode 100644
index 0000000000..491fa1a8d8
--- /dev/null
+++ b/resources/js/admin/logs/utils/date.js
@@ -0,0 +1,74 @@
+/* eslint-disable import/prefer-default-export */
+import { DateTime } from 'luxon';
+
+/**
+ * Convert PHP/moment date format to Luxon format
+ * @param {string} format - PHP/moment format string
+ * @returns {string} - Luxon format string
+ */
+const convertToLuxonFormat = (format) => {
+ // Common conversions from PHP/moment to Luxon
+ const replacements = {
+ YYYY: 'yyyy',
+ YY: 'yy',
+ MM: 'LL',
+ M: 'L',
+ DD: 'dd',
+ D: 'd',
+ HH: 'HH',
+ hh: 'hh',
+ H: 'H',
+ h: 'h',
+ mm: 'mm',
+ m: 'm',
+ ss: 'ss',
+ s: 's',
+ A: 'a',
+ a: 'a',
+ };
+
+ let luxonFormat = format;
+ Object.entries(replacements).forEach(([from, to]) => {
+ luxonFormat = luxonFormat.replace(new RegExp(from, 'g'), to);
+ });
+
+ return luxonFormat;
+};
+
+/**
+ * Format date to user's date format
+ * @param {string} value - The date to format
+ * @returns {string} - The formatted date
+ */
+export const dateFormatter = (value) => {
+ let datetimeConfig = 'dd/LL/yyyy hh:mm';
+ let timezoneConfig = 'UTC';
+
+ if (
+ typeof ProcessMaker !== 'undefined'
+ && ProcessMaker.user
+ && ProcessMaker.user.datetime_format
+ ) {
+ timezoneConfig = ProcessMaker.user.timezone;
+ datetimeConfig = convertToLuxonFormat(ProcessMaker.user.datetime_format);
+ }
+
+ if (value) {
+ const date = DateTime.fromISO(value, { zone: 'utc' }).setZone(timezoneConfig);
+
+ if (date.isValid) {
+ return date.toFormat(datetimeConfig);
+ }
+
+ // Try parsing as SQL format
+ const sqlDate = DateTime.fromSQL(value, { zone: 'utc' }).setZone(timezoneConfig);
+ if (sqlDate.isValid) {
+ return sqlDate.toFormat(datetimeConfig);
+ }
+
+ return value;
+ }
+
+ return '-';
+};
+
diff --git a/resources/lang/en.json b/resources/lang/en.json
index e580e5b49b..5e07c388e7 100644
--- a/resources/lang/en.json
+++ b/resources/lang/en.json
@@ -656,6 +656,7 @@
"Description": "Description",
"Design Screen": "Design Screen",
"Design": "Design",
+ "Design Mode Logs": "Design Mode Logs",
"Designer": "Designer",
"Destination Screen": "Destination Screen",
"Destination": "Destination",
@@ -899,6 +900,7 @@
"Error Name": "Error Name",
"Error": "Error",
"Errors": "Errors",
+ "Error Logs": "Error Logs",
"Event Based Gateway": "Event Based Gateway",
"Event has multiple event definitions": "Event has multiple event definitions",
"Event-based Gateway": "Event-based Gateway",
@@ -906,6 +908,8 @@
"Event": "Event",
"Exclusive Gateway": "Exclusive Gateway",
"Execution Error": "Execution Error",
+ "Runtime Logs": "Runtime Logs",
+ "FlowGenie Studio Logs": "FlowGenie Studio Logs",
"Execution Log": "Execution Log",
"Executor Successfully Built. You can now close this window. ": "Executor Successfully Built. You can now close this window. ",
"Existing Array": "Existing Array",
@@ -979,6 +983,7 @@
"Flow splits implicitly": "Flow splits implicitly",
"Flow Variable": "Flow Variable",
"FlowGenies": "FlowGenies",
+ "FlowGenie Agents Logs": "FlowGenie Agents Logs",
"FolderCreated": "Folder Created",
"FolderUpdated": "Folder Updated",
"Font Size": "Font Size",
@@ -1255,6 +1260,7 @@
"Manually Complete Request": "Manually Complete Request",
"Manually reconfigure the dashboard using an existing one in the environment.": "Manually reconfigure the dashboard using an existing one in the environment.",
"Mark as Priority": "Mark as Priority",
+ "Matched Logs": "Matched Logs",
"Max Input": "Max Input",
"Max Length": "Max Length",
"Max": "Max",
@@ -2407,6 +2413,7 @@
"Top Right": "Top Right",
"Top": "Top",
"Total Elements": "Total Elements",
+ "Total Logs": "Total Logs",
"Translate into multiple languages.": "Translate into multiple languages.",
"Translate process screens to a desired language": "Translate process screens to a desired language",
"Translation in progress": "Translation in progress",
diff --git a/resources/views/admin/logs/index.blade.php b/resources/views/admin/logs/index.blade.php
new file mode 100644
index 0000000000..9a7ca90a61
--- /dev/null
+++ b/resources/views/admin/logs/index.blade.php
@@ -0,0 +1,28 @@
+@extends('layouts.layout')
+
+@section('title')
+ {{__('Logs')}}
+@endsection
+
+@section('sidebar')
+ @include('layouts.sidebar', ['sidebar'=> Menu::get('sidebar_admin')])
+@endsection
+
+@section('breadcrumbs')
+ @include('shared.breadcrumbs', ['routes' => [
+ __('Admin') => route('admin.index'),
+ __('Logs') => null,
+ ]])
+@endsection
+
+@section('content')
+
+@endsection
+
+@section('js')
+
+
+@endsection
+
diff --git a/resources/views/layouts/layout.blade.php b/resources/views/layouts/layout.blade.php
index 9843813b0f..253d4343c4 100644
--- a/resources/views/layouts/layout.blade.php
+++ b/resources/views/layouts/layout.blade.php
@@ -141,7 +141,8 @@ class="main flex-grow-1 h-100
window.ProcessMaker.packages = @json(\App::make(ProcessMaker\Managers\PackageManager::class)->listPackages());
window.ProcessMaker.ai = {
ragCollections: @json(config('ai.rag_collections.enabled')),
- genieClientTimeout: @json(config('ai.genie_client.timeout'))
+ genieClientTimeout: @json(config('ai.genie_client.timeout')),
+ microserviceHost: @json(config('app.ai_microservice_host'))
};
diff --git a/routes/web.php b/routes/web.php
index b7bf10904f..69abeddef1 100644
--- a/routes/web.php
+++ b/routes/web.php
@@ -1,5 +1,6 @@
name('storage.serve');
Route::middleware('auth', 'session_kill', 'sanitize', 'force_change_password', '2fa')->group(function () {
+ Route::get('files/{file}/contents', [ApiFileController::class, 'download'])
+ ->name('web.files.download')
+ ->middleware('can:view,file');
+
// Routes related to Authentication (password reset, etc)
// Auth::routes();
Route::prefix('admin')->group(function () {
@@ -81,6 +88,18 @@
// temporary, should be removed
Route::get('security-logs/download/all', [ProcessMaker\Http\Controllers\Api\SecurityLogController::class, 'downloadForAllUsers'])->middleware('can:view-security-logs');
Route::get('security-logs/download/{user}', [ProcessMaker\Http\Controllers\Api\SecurityLogController::class, 'downloadForUser'])->middleware('can:view-security-logs');
+
+ // Logs - available when package-email-start-event or package-ai is installed
+ if (hasPackage('package-email-start-event') || hasPackage('package-ai')) {
+ Route::get('logs', [LogsController::class, 'index'])->name('admin.logs')->middleware('can:view-settings');
+ // Export route must be before the wildcard route
+ if (hasPackage('package-email-start-event')) {
+ Route::get('logs/export/csv', [ProcessMaker\Package\PackageEmailStartEvent\Http\Controllers\EmailListenerLogController::class, 'exportToCsv'])
+ ->name('admin.logs.export.csv')
+ ->middleware('can:view-settings');
+ }
+ Route::get('logs/{any}', [LogsController::class, 'index'])->name('admin.logs-any')->middleware('can:view-settings')->where('any', '.*');
+ }
});
Route::get('admin', [AdminController::class, 'index'])->name('admin.index');
diff --git a/webpack.mix.js b/webpack.mix.js
index 01253ace95..c93f125c10 100644
--- a/webpack.mix.js
+++ b/webpack.mix.js
@@ -104,6 +104,7 @@ mix
.js("resources/js/admin/cssOverride/edit.js", "public/js/admin/cssOverride/edit.js")
.js("resources/js/admin/script-executors/index.js", "public/js/admin/script-executors/index.js")
.js("resources/js/admin/tenant-queues/index.js", "public/js/admin/tenant-queues/index.js")
+ .js("resources/js/admin/logs/index.js", "public/js/admin/logs/index.js")
.js("resources/js/processes/index.js", "public/js/processes")
.js("resources/js/processes/edit.js", "public/js/processes")