diff --git a/resources/js/admin/logs/components/Logs/AgentSessionDetail/AgentSessionDetail.vue b/resources/js/admin/logs/components/Logs/AgentSessionDetail/AgentSessionDetail.vue index 175622802c..d516c772c5 100644 --- a/resources/js/admin/logs/components/Logs/AgentSessionDetail/AgentSessionDetail.vue +++ b/resources/js/admin/logs/components/Logs/AgentSessionDetail/AgentSessionDetail.vue @@ -111,7 +111,7 @@
@@ -361,11 +361,11 @@
{{ $t('MCP Servers') }}
- {{ server }} + {{ formatMcpServerLabel(server) }}
@@ -374,13 +374,13 @@
{{ $t('Collections') }}
- {{ collection.name }} + {{ formatCollectionLabel(collection) }}
@@ -708,6 +708,61 @@ export default { this.$set(this.expandedEvents, index, !this.expandedEvents[index]); }, + getEventKey(event, index) { + const id = event.id ?? event.call_id ?? event.response_id; + if (typeof id === 'string' || typeof id === 'number') { + return String(id); + } + const type = event._type || event.type || 'event'; + const timestamp = event.timestamp || event.created_at || ''; + return `${type}-${timestamp}-${index}`; + }, + + getMcpServerKey(server, index) { + if (typeof server === 'string' || typeof server === 'number') { + return `mcp-${server}-${index}`; + } + if (server && typeof server === 'object') { + const name = server.name || server.id || 'server'; + const type = server.type || server.server_type || ''; + return `mcp-${name}-${type}-${index}`; + } + return `mcp-server-${index}`; + }, + + formatMcpServerLabel(server) { + if (typeof server === 'string' || typeof server === 'number') { + return String(server); + } + if (server && typeof server === 'object') { + return server.name || server.id || JSON.stringify(server); + } + return String(server ?? ''); + }, + + getCollectionKey(collection, index) { + if (typeof collection === 'string' || typeof collection === 'number') { + return `collection-${collection}-${index}`; + } + if (collection && typeof collection === 'object') { + const id = collection.uuid || collection.id; + if (typeof id === 'string' || typeof id === 'number') { + return `collection-${id}`; + } + } + return `collection-${index}`; + }, + + formatCollectionLabel(collection) { + if (typeof collection === 'string' || typeof collection === 'number') { + return String(collection); + } + if (collection && typeof collection === 'object') { + return collection.name || collection.uuid || collection.id || JSON.stringify(collection); + } + return String(collection ?? ''); + }, + isGuardrailEvent(event) { const type = event._type || event.type; return type === 'guardrail_evaluation_event'; diff --git a/resources/js/admin/logs/components/Logs/HeaderBar/HeaderBar.vue b/resources/js/admin/logs/components/Logs/HeaderBar/HeaderBar.vue index 6878c1c6fc..a7fcc7add8 100644 --- a/resources/js/admin/logs/components/Logs/HeaderBar/HeaderBar.vue +++ b/resources/js/admin/logs/components/Logs/HeaderBar/HeaderBar.vue @@ -30,7 +30,7 @@
-
+
+
+
+
+ +
+
+ +
+
+ +
+ {{ $t('Loading...') }} +
+ + +
+ + + + + diff --git a/resources/js/admin/logs/components/Logs/Monitoring/index.js b/resources/js/admin/logs/components/Logs/Monitoring/index.js new file mode 100644 index 0000000000..baa481f302 --- /dev/null +++ b/resources/js/admin/logs/components/Logs/Monitoring/index.js @@ -0,0 +1,2 @@ +// eslint-disable-next-line import/prefer-default-export +export { default as MonitoringDashboard } from './MonitoringDashboard.vue'; diff --git a/resources/js/admin/logs/components/Logs/Sidebar/Sidebar.vue b/resources/js/admin/logs/components/Logs/Sidebar/Sidebar.vue index a2276bbff7..6d947d71ca 100644 --- a/resources/js/admin/logs/components/Logs/Sidebar/Sidebar.vue +++ b/resources/js/admin/logs/components/Logs/Sidebar/Sidebar.vue @@ -104,7 +104,7 @@ :visible="isAgentsActive" > - FlowGenie Agents logs + {{ $t('Monitoring') }} + + + {{ $t('FlowGenie Agents logs') }}
@@ -153,13 +170,25 @@ export default { isAgentsActive() { return this.$route.path.startsWith("/agents"); }, + isAgentsLogsActive() { + return this.$route.path === '/agents/design' || this.$route.path === '/agents/execution'; + }, + isMonitoringActive() { + return this.$route.path.startsWith('/agents/monitoring'); + }, emailLinkClasses() { - return this.isEmailActive - ? "tw-bg-blue-50 tw-font-semibold tw-text-blue-500" - : "tw-font-medium tw-text-zinc-700 hover:tw-bg-zinc-50"; + return this.linkClasses(this.isEmailActive); + }, + agentsLogsLinkClasses() { + return this.linkClasses(this.isAgentsLogsActive); }, - agentsLinkClasses() { - return this.isAgentsActive + monitoringLinkClasses() { + return this.linkClasses(this.isMonitoringActive); + }, + }, + methods: { + linkClasses(isActive) { + return isActive ? "tw-bg-blue-50 tw-font-semibold tw-text-blue-500" : "tw-font-medium tw-text-zinc-700 hover:tw-bg-zinc-50"; }, diff --git a/resources/js/admin/logs/components/Logs/charts/BarChart.vue b/resources/js/admin/logs/components/Logs/charts/BarChart.vue new file mode 100644 index 0000000000..fd46f8cb06 --- /dev/null +++ b/resources/js/admin/logs/components/Logs/charts/BarChart.vue @@ -0,0 +1,38 @@ + diff --git a/resources/js/admin/logs/components/Logs/charts/ChartFrame.vue b/resources/js/admin/logs/components/Logs/charts/ChartFrame.vue new file mode 100644 index 0000000000..e010c4054f --- /dev/null +++ b/resources/js/admin/logs/components/Logs/charts/ChartFrame.vue @@ -0,0 +1,18 @@ + + + diff --git a/resources/js/admin/logs/components/Logs/charts/DoughnutChart.vue b/resources/js/admin/logs/components/Logs/charts/DoughnutChart.vue new file mode 100644 index 0000000000..5691a36bbd --- /dev/null +++ b/resources/js/admin/logs/components/Logs/charts/DoughnutChart.vue @@ -0,0 +1,38 @@ + diff --git a/resources/js/admin/logs/components/Logs/charts/HorizontalBarChart.vue b/resources/js/admin/logs/components/Logs/charts/HorizontalBarChart.vue new file mode 100644 index 0000000000..5ddcfa75b7 --- /dev/null +++ b/resources/js/admin/logs/components/Logs/charts/HorizontalBarChart.vue @@ -0,0 +1,38 @@ + diff --git a/resources/js/admin/logs/components/Logs/charts/LineChart.vue b/resources/js/admin/logs/components/Logs/charts/LineChart.vue new file mode 100644 index 0000000000..fa8a6674e9 --- /dev/null +++ b/resources/js/admin/logs/components/Logs/charts/LineChart.vue @@ -0,0 +1,38 @@ + diff --git a/resources/js/admin/logs/components/Logs/charts/chartTheme.js b/resources/js/admin/logs/components/Logs/charts/chartTheme.js new file mode 100644 index 0000000000..8ad3a3fa92 --- /dev/null +++ b/resources/js/admin/logs/components/Logs/charts/chartTheme.js @@ -0,0 +1,93 @@ +export const CHART_COLORS = { + pass: '#0CA442', + redact: '#EC8E00', + block: '#EC5962', + info: '#2773F3', + muted: '#728092', + completed: '#0CA442', + error: '#EC5962', +}; + +export const doughnutOptions = { + legend: { + display: true, + position: 'bottom', + }, + maintainAspectRatio: false, + responsive: true, + cutoutPercentage: 60, + layout: { + padding: 4, + }, +}; + +export const horizontalBarOptions = (valueSuffix = '') => ({ + legend: { + display: false, + }, + maintainAspectRatio: false, + responsive: true, + layout: { + padding: { + right: 12, + }, + }, + scales: { + xAxes: [{ + ticks: { + beginAtZero: true, + callback: (value) => `${value}${valueSuffix}`, + }, + }], + yAxes: [{ + gridLines: { + display: false, + }, + afterFit: (scale) => { + scale.width = Math.min(scale.width, 140); + }, + }], + }, +}); + +export const stackedBarOptions = { + legend: { + display: true, + position: 'bottom', + }, + maintainAspectRatio: false, + responsive: true, + layout: { + padding: 4, + }, + scales: { + xAxes: [{ stacked: true }], + yAxes: [{ + stacked: true, + ticks: { beginAtZero: true }, + }], + }, +}; + +export const lineOptions = { + legend: { + display: true, + position: 'bottom', + }, + maintainAspectRatio: false, + responsive: true, + layout: { + padding: 4, + }, + scales: { + yAxes: [{ + ticks: { beginAtZero: true }, + }], + }, +}; + +export const chartBoxStyles = { + width: '100%', + height: '220px', + position: 'relative', +}; diff --git a/resources/js/admin/logs/components/Logs/routes.js b/resources/js/admin/logs/components/Logs/routes.js index 398d462193..85443ea7d3 100644 --- a/resources/js/admin/logs/components/Logs/routes.js +++ b/resources/js/admin/logs/components/Logs/routes.js @@ -1,4 +1,5 @@ import { LogTable } from "./LogTable"; +import { MonitoringDashboard } from "./Monitoring"; export default {}; @@ -70,6 +71,18 @@ export const routes = [ path: "/agents", redirect: "/agents/design", }, + { + name: "logs.agents.monitoring", + path: "/agents/monitoring", + component: MonitoringDashboard, + beforeEnter: (to, from, next) => { + if (!hasAiPackage()) { + next(hasEmailPackage() ? "/email/errors" : "/"); + } else { + next(); + } + }, + }, { name: "logs.agents", path: "/agents/:logType", diff --git a/resources/lang/en.json b/resources/lang/en.json index 257686e0f1..e6a75a84e0 100644 --- a/resources/lang/en.json +++ b/resources/lang/en.json @@ -2695,5 +2695,96 @@ "Your PMQL search could not be completed.": "Your PMQL search could not be completed.", "Your profile was saved.": "Your profile was saved.", "Zoom In": "Zoom In", - "Zoom Out": "Zoom Out" + "Zoom Out": "Zoom Out", + "After tool": "After tool", + "Agent": "Agent", + "Arguments": "Arguments", + "Avg duration": "Avg duration", + "Before response": "Before response", + "Before tool": "Before tool", + "Block": "Block", + "Block rate": "Block rate", + "Block rate by FlowGenie": "Block rate by FlowGenie", + "Blocked sessions": "Blocked sessions", + "Blocked vs redacted sessions": "Blocked vs redacted sessions", + "Blocks": "Blocks", + "Connected": "Connected", + "Connection": "Connection", + "Date Email": "Date Email", + "Email From": "Email From", + "Error Code": "Error Code", + "Error rate": "Error rate", + "Execution Time": "Execution Time", + "Export to CSV": "Export to CSV", + "Files Attached": "Files Attached", + "FlowGenie": "FlowGenie", + "FlowGenie Agents logs": "FlowGenie Agents logs", + "FlowGenie Monitoring": "FlowGenie Monitoring", + "FlowGenie Name": "FlowGenie Name", + "Guardrail": "Guardrail", + "Guardrail outcomes": "Guardrail outcomes", + "Guardrails": "Guardrails", + "Input": "Input", + "Instructions": "Instructions", + "LLM": "LLM", + "LLM Calls": "LLM Calls", + "Last 30 days": "Last 30 days", + "Last 7 days": "Last 7 days", + "Last 90 days": "Last 90 days", + "Loading session details...": "Loading session details...", + "MCP Servers": "MCP Servers", + "Max Turns": "Max Turns", + "Model": "Model", + "Model Request": "Model Request", + "Model Settings": "Model Settings", + "Monitoring": "Monitoring", + "No": "No", + "No blocked sessions in this period.": "No blocked sessions in this period.", + "No events recorded for this session": "No events recorded for this session", + "No guardrail evaluations in this period.": "No guardrail evaluations in this period.", + "No sessions in this period.": "No sessions in this period.", + "No token usage in this period.": "No token usage in this period.", + "No violations in this period.": "No violations in this period.", + "Node": "Node", + "On input": "On input", + "On output": "On output", + "Pass": "Pass", + "Passed": "Passed", + "Phase": "Phase", + "Policy": "Policy", + "Processing": "Processing", + "Processing Started": "Processing Started", + "Properties": "Properties", + "Reasoning": "Reasoning", + "Redact": "Redact", + "Redact rate": "Redact rate", + "Redacted": "Redacted", + "Redacted sessions": "Redacted sessions", + "Request Config": "Request Config", + "Request ID": "Request ID", + "Response": "Response", + "Runtime": "Runtime", + "Session Details": "Session Details", + "Session ID": "Session ID", + "Session status": "Session status", + "Sessions": "Sessions", + "Sessions per day": "Sessions per day", + "Studio": "Studio", + "Thinking": "Thinking", + "Tokens Used": "Tokens Used", + "Tokens by FlowGenie": "Tokens by FlowGenie", + "Tokens in vs out": "Tokens in vs out", + "Tool": "Tool", + "Tool Call": "Tool Call", + "Tool Calls": "Tool Calls", + "Tools": "Tools", + "Top users by blocks": "Top users by blocks", + "Total Emails": "Total Emails", + "Total Matches": "Total Matches", + "Total Tokens": "Total Tokens", + "Usage": "Usage", + "User Email": "User Email", + "Violations": "Violations", + "Violations by kind": "Violations by kind", + "Where it fired": "Where it fired" }