diff --git a/packages/frontend/editor-ui/src/components/CanvasChat/future/LogsPanel.test.ts b/packages/frontend/editor-ui/src/components/CanvasChat/future/LogsPanel.test.ts
index 010854052f..22f9dc6b50 100644
--- a/packages/frontend/editor-ui/src/components/CanvasChat/future/LogsPanel.test.ts
+++ b/packages/frontend/editor-ui/src/components/CanvasChat/future/LogsPanel.test.ts
@@ -230,7 +230,7 @@ describe('LogsPanel', () => {
await fireEvent.click(rendered.getByText('Overview'));
- expect(rendered.getByText('Running')).toBeInTheDocument();
+ expect(rendered.getByText(/Running/)).toBeInTheDocument();
expect(rendered.queryByText('AI Agent')).not.toBeInTheDocument();
workflowsStore.addNodeExecutionStartedData({
@@ -247,7 +247,7 @@ describe('LogsPanel', () => {
});
expect(lastTreeItem.getByText('AI Agent')).toBeInTheDocument();
- expect(lastTreeItem.getByText('Running')).toBeInTheDocument();
+ expect(lastTreeItem.getByText(/Running/)).toBeInTheDocument();
workflowsStore.updateNodeExecutionData({
nodeName: 'AI Agent',
diff --git a/packages/frontend/editor-ui/src/components/CanvasChat/future/components/LogDetailsPanel.test.ts b/packages/frontend/editor-ui/src/components/CanvasChat/future/components/LogDetailsPanel.test.ts
index 80c6eaa18f..708baef576 100644
--- a/packages/frontend/editor-ui/src/components/CanvasChat/future/components/LogDetailsPanel.test.ts
+++ b/packages/frontend/editor-ui/src/components/CanvasChat/future/components/LogDetailsPanel.test.ts
@@ -108,6 +108,24 @@ describe('LogDetailsPanel', () => {
expect(await outputPanel.findByText('Hello!')).toBeInTheDocument();
});
+ it('should show a message in the output panel and data in the input panel when node is running', async () => {
+ const rendered = render({
+ isOpen: true,
+ logEntry: createLogEntry({
+ node: aiNode,
+ runIndex: 0,
+ runData: { ...aiNodeRunData, executionStatus: 'running' },
+ }),
+ panels: LOG_DETAILS_PANEL_STATE.BOTH,
+ });
+
+ const inputPanel = within(rendered.getByTestId('log-details-input'));
+ const outputPanel = within(rendered.getByTestId('log-details-output'));
+
+ expect(await inputPanel.findByText('hey')).toBeInTheDocument();
+ expect(await outputPanel.findByText('Executing node...')).toBeInTheDocument();
+ });
+
it('should close input panel by dragging the divider to the left end', async () => {
const rendered = render({
isOpen: true,
diff --git a/packages/frontend/editor-ui/src/components/CanvasChat/future/components/LogDetailsPanel.vue b/packages/frontend/editor-ui/src/components/CanvasChat/future/components/LogDetailsPanel.vue
index 59d27377c0..ac2c37fa17 100644
--- a/packages/frontend/editor-ui/src/components/CanvasChat/future/components/LogDetailsPanel.vue
+++ b/packages/frontend/editor-ui/src/components/CanvasChat/future/components/LogDetailsPanel.vue
@@ -88,6 +88,7 @@ function handleResizeEnd() {
:class="$style.executionSummary"
:status="logEntry.runData.executionStatus ?? 'unknown'"
:consumed-tokens="consumedTokens"
+ :start-time="logEntry.runData.startTime"
:time-took="logEntry.runData.executionTime"
/>
diff --git a/packages/frontend/editor-ui/src/components/CanvasChat/future/components/LogsOverviewPanel.vue b/packages/frontend/editor-ui/src/components/CanvasChat/future/components/LogsOverviewPanel.vue
index e6d2c8f262..2a18dcc5b1 100644
--- a/packages/frontend/editor-ui/src/components/CanvasChat/future/components/LogsOverviewPanel.vue
+++ b/packages/frontend/editor-ui/src/components/CanvasChat/future/components/LogsOverviewPanel.vue
@@ -163,6 +163,7 @@ watch(
:class="$style.summary"
:status="execution.status"
:consumed-tokens="consumedTokens"
+ :start-time="+new Date(execution.startedAt)"
:time-took="
execution.startedAt && execution.stoppedAt
? +new Date(execution.stoppedAt) - +new Date(execution.startedAt)
@@ -247,6 +248,8 @@ watch(
.tree {
padding: 0 var(--spacing-2xs) var(--spacing-2xs) var(--spacing-2xs);
+ scroll-padding-block: var(--spacing-3xs);
+
& :global(.el-icon) {
display: none;
}
diff --git a/packages/frontend/editor-ui/src/components/CanvasChat/future/components/LogsOverviewRow.vue b/packages/frontend/editor-ui/src/components/CanvasChat/future/components/LogsOverviewRow.vue
index 316f1773d4..091037b315 100644
--- a/packages/frontend/editor-ui/src/components/CanvasChat/future/components/LogsOverviewRow.vue
+++ b/packages/frontend/editor-ui/src/components/CanvasChat/future/components/LogsOverviewRow.vue
@@ -13,6 +13,7 @@ import {
type LatestNodeInfo,
type LogEntry,
} from '@/components/RunDataAi/utils';
+import { useTimestamp } from '@vueuse/core';
const props = defineProps<{
data: LogEntry;
@@ -34,12 +35,13 @@ const emit = defineEmits<{
const container = useTemplateRef('containerRef');
const locale = useI18n();
+const now = useTimestamp({ interval: 1000 });
const nodeTypeStore = useNodeTypesStore();
const type = computed(() => nodeTypeStore.getNodeType(props.data.node.type));
const isSettled = computed(
() =>
props.data.runData.executionStatus &&
- ['crashed', 'error', 'success'].includes(props.data.runData.executionStatus),
+ !['running', 'waiting'].includes(props.data.runData.executionStatus),
);
const isError = computed(() => !!props.data.runData.error);
const startedAtText = computed(() => {
@@ -51,6 +53,15 @@ const startedAtText = computed(() => {
},
});
});
+const statusText = computed(() => upperFirst(props.data.runData.executionStatus));
+const timeText = computed(() =>
+ locale.displayTimer(
+ isSettled.value
+ ? props.data.runData.executionTime
+ : Math.floor((now.value - props.data.runData.startTime) / 1000) * 1000,
+ true,
+ ),
+);
const subtreeConsumedTokens = computed(() =>
props.shouldShowTokenCountColumn ? getSubtreeTotalConsumedTokens(props.data, false) : undefined,
@@ -126,19 +137,24 @@ watch(
:is-deleted="latestInfo?.deleted ?? false"
/>
-
+
- {{
- upperFirst(props.data.runData.executionStatus)
- }}
+
+ {{ statusText }}
- {{ upperFirst(props.data.runData.executionStatus) }}
+ {{ statusText }}
- {{ locale.displayTimer(props.data.runData.executionTime, true) }}
+ {{ timeText }}
- {{ upperFirst(props.data.runData.executionStatus) }}
+
+ {{
+ locale.baseText('logs.overview.body.summaryText.for', {
+ interpolate: { status: statusText, time: timeText },
+ })
+ }}
+
+
();
const locale = useI18n();
+const now = useTimestamp({ interval: 1000 });
const executionStatusText = computed(() =>
- timeTook === undefined
- ? upperFirst(status)
- : locale.baseText('logs.overview.body.summaryText', {
+ status === 'running' || status === 'waiting'
+ ? locale.baseText('logs.overview.body.summaryText.for', {
interpolate: {
status: upperFirst(status),
- time: locale.displayTimer(timeTook, true),
+ time: locale.displayTimer(Math.floor((now.value - startTime) / 1000) * 1000, true),
},
- }),
+ })
+ : timeTook === undefined
+ ? upperFirst(status)
+ : locale.baseText('logs.overview.body.summaryText.in', {
+ interpolate: {
+ status: upperFirst(status),
+ time: locale.displayTimer(timeTook, true),
+ },
+ }),
);
diff --git a/packages/frontend/editor-ui/src/components/CanvasChat/future/components/LogsViewRunData.vue b/packages/frontend/editor-ui/src/components/CanvasChat/future/components/LogsViewRunData.vue
index 92aa91d6d7..bbcad24f0e 100644
--- a/packages/frontend/editor-ui/src/components/CanvasChat/future/components/LogsViewRunData.vue
+++ b/packages/frontend/editor-ui/src/components/CanvasChat/future/components/LogsViewRunData.vue
@@ -4,6 +4,7 @@ import { type LogEntry } from '@/components/RunDataAi/utils';
import { useI18n } from '@/composables/useI18n';
import { type IRunDataDisplayMode, type NodePanelType } from '@/Interface';
import { useNDVStore } from '@/stores/ndv.store';
+import { waitingNodeTooltip } from '@/utils/executionUtils';
import { N8nLink, N8nText } from '@n8n/design-system';
import { computed, ref } from 'vue';
import { I18nT } from 'vue-i18n';
@@ -42,6 +43,12 @@ const runDataProps = computed<
overrideOutputs: [source.previousNodeOutput ?? 0],
};
});
+const isExecuting = computed(
+ () =>
+ paneType === 'output' &&
+ (logEntry.runData.executionStatus === 'running' ||
+ logEntry.runData.executionStatus === 'waiting'),
+);
function handleClickOpenNdv() {
ndvStore.setActiveNodeName(logEntry.node.name);
@@ -69,6 +76,7 @@ function handleChangeDisplayMode(value: IRunDataDisplayMode) {
:disable-hover-highlight="true"
:display-mode="displayMode"
:disable-ai-content="logEntry.depth === 0"
+ :is-executing="isExecuting"
table-header-bg-color="light"
@display-mode-change="handleChangeDisplayMode"
>
@@ -84,6 +92,13 @@ function handleChangeDisplayMode(value: IRunDataDisplayMode) {
+
+
+ {{ locale.baseText('ndv.output.waitNodeWaiting.title') }}
+
+
+
+
diff --git a/packages/frontend/editor-ui/src/components/InputPanel.vue b/packages/frontend/editor-ui/src/components/InputPanel.vue
index 0f2813c9ae..d10e9890b7 100644
--- a/packages/frontend/editor-ui/src/components/InputPanel.vue
+++ b/packages/frontend/editor-ui/src/components/InputPanel.vue
@@ -507,7 +507,9 @@ function activatePane() {
- Waiting for input
+
+ {{ i18n.baseText('ndv.output.waitNodeWaiting.title') }}
+
diff --git a/packages/frontend/editor-ui/src/components/OutputPanel.vue b/packages/frontend/editor-ui/src/components/OutputPanel.vue
index c1704bec9e..afb41ebe4c 100644
--- a/packages/frontend/editor-ui/src/components/OutputPanel.vue
+++ b/packages/frontend/editor-ui/src/components/OutputPanel.vue
@@ -402,7 +402,9 @@ const activatePane = () => {
- Waiting for input
+
+ {{ i18n.baseText('ndv.output.waitNodeWaiting.title') }}
+
diff --git a/packages/frontend/editor-ui/src/plugins/i18n/locales/en.json b/packages/frontend/editor-ui/src/plugins/i18n/locales/en.json
index 8f171d628d..be8bc44859 100644
--- a/packages/frontend/editor-ui/src/plugins/i18n/locales/en.json
+++ b/packages/frontend/editor-ui/src/plugins/i18n/locales/en.json
@@ -1005,7 +1005,8 @@
"logs.overview.header.switch.overview": "Overview",
"logs.overview.body.empty.message": "Nothing to display yet. Execute the workflow to see execution logs.",
"logs.overview.body.empty.action": "Execute the workflow",
- "logs.overview.body.summaryText": "{status} in {time}",
+ "logs.overview.body.summaryText.for": "{status} for {time}",
+ "logs.overview.body.summaryText.in": "{status} in {time}",
"logs.overview.body.started": "Started {time}",
"logs.overview.body.run": "Execute step",
"logs.overview.body.open": "Open...",
@@ -1136,11 +1137,12 @@
"ndv.output.run": "Run",
"ndv.output.runNodeHint": "Execute this node to view data",
"ndv.output.runNodeHintSubNode": "Output will appear here once the parent node is run",
- "ndv.output.waitNodeWaitingForWebhook": "Execution will continue when webhook is received on ",
"ndv.output.githubNodeWaitingForWebhook": "Execution will continue when the following webhook URL is called: ",
"ndv.output.sendAndWaitWaitingApproval": "Execution will continue after the user's response",
- "ndv.output.waitNodeWaitingForFormSubmission": "Execution will continue when form is submitted on ",
- "ndv.output.waitNodeWaiting": "Execution will continue when wait time is over",
+ "ndv.output.waitNodeWaiting.title": "Waiting for input",
+ "ndv.output.waitNodeWaiting.description.webhook": "Execution will continue when webhook is received on ",
+ "ndv.output.waitNodeWaiting.description.form": "Execution will continue when form is submitted on ",
+ "ndv.output.waitNodeWaiting.description.timer": "Execution will continue when wait time is over",
"ndv.output.insertTestData": "set mock data",
"ndv.output.staleDataWarning.regular": "Node parameters have changed.
Test node again to refresh output.",
"ndv.output.staleDataWarning.pinData": "Node parameter changes will not affect pinned output data.",
diff --git a/packages/frontend/editor-ui/src/utils/executionUtils.test.ts b/packages/frontend/editor-ui/src/utils/executionUtils.test.ts
index 1d9a704085..933bc3d430 100644
--- a/packages/frontend/editor-ui/src/utils/executionUtils.test.ts
+++ b/packages/frontend/editor-ui/src/utils/executionUtils.test.ts
@@ -33,9 +33,9 @@ vi.mock('@/plugins/i18n', () => ({
i18n: {
baseText: (key: string, options?: { interpolate?: { error?: string; details?: string } }) => {
const texts: { [key: string]: string } = {
- 'ndv.output.waitNodeWaiting': 'Waiting for execution to resume...',
- 'ndv.output.waitNodeWaitingForFormSubmission': 'Waiting for form submission: ',
- 'ndv.output.waitNodeWaitingForWebhook': 'Waiting for webhook call: ',
+ 'ndv.output.waitNodeWaiting.description.timer': 'Waiting for execution to resume...',
+ 'ndv.output.waitNodeWaiting.description.form': 'Waiting for form submission: ',
+ 'ndv.output.waitNodeWaiting.description.webhook': 'Waiting for webhook call: ',
'ndv.output.githubNodeWaitingForWebhook': 'Waiting for webhook call: ',
'ndv.output.sendAndWaitWaitingApproval': 'Waiting for approval...',
'pushConnection.executionError': `Execution error${options?.interpolate?.error}`,
diff --git a/packages/frontend/editor-ui/src/utils/executionUtils.ts b/packages/frontend/editor-ui/src/utils/executionUtils.ts
index 4bc81926e5..751daa81cb 100644
--- a/packages/frontend/editor-ui/src/utils/executionUtils.ts
+++ b/packages/frontend/editor-ui/src/utils/executionUtils.ts
@@ -157,7 +157,7 @@ export const waitingNodeTooltip = (node: INodeUi | null | undefined) => {
}
if (resume) {
if (!['webhook', 'form'].includes(resume as string)) {
- return i18n.baseText('ndv.output.waitNodeWaiting');
+ return i18n.baseText('ndv.output.waitNodeWaiting.description.timer');
}
const { webhookSuffix } = (node.parameters.options ?? {}) as { webhookSuffix: string };
@@ -168,12 +168,12 @@ export const waitingNodeTooltip = (node: INodeUi | null | undefined) => {
if (resume === 'form') {
resumeUrl = `${useRootStore().formWaitingUrl}/${useWorkflowsStore().activeExecutionId}${suffix}`;
- message = i18n.baseText('ndv.output.waitNodeWaitingForFormSubmission');
+ message = i18n.baseText('ndv.output.waitNodeWaiting.description.form');
}
if (resume === 'webhook') {
resumeUrl = `${useRootStore().webhookWaitingUrl}/${useWorkflowsStore().activeExecutionId}${suffix}`;
- message = i18n.baseText('ndv.output.waitNodeWaitingForWebhook');
+ message = i18n.baseText('ndv.output.waitNodeWaiting.description.webhook');
}
if (message && resumeUrl) {
@@ -182,7 +182,7 @@ export const waitingNodeTooltip = (node: INodeUi | null | undefined) => {
}
if (node?.type === FORM_NODE_TYPE) {
- const message = i18n.baseText('ndv.output.waitNodeWaitingForFormSubmission');
+ const message = i18n.baseText('ndv.output.waitNodeWaiting.description.form');
const resumeUrl = `${useRootStore().formWaitingUrl}/${useWorkflowsStore().activeExecutionId}`;
return `${message}${resumeUrl}`;
}