@flowuent-org/diagramming-core 1.3.9 → 1.3.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/apps/diagramming/src/AutomationDiagramData.ts +46 -0
- package/package.json +1 -1
- package/packages/diagrams/src/lib/components/automation/AutomationEmailNode.tsx +773 -0
- package/packages/diagrams/src/lib/components/automation/AutomationGoogleServicesNode.tsx +5 -5
- package/packages/diagrams/src/lib/components/automation/AutomationInteractionNode.tsx +5 -5
- package/packages/diagrams/src/lib/components/automation/AutomationMonitoringNode.tsx +6 -8
- package/packages/diagrams/src/lib/components/automation/AutomationSlackNode.tsx +15 -4
- package/packages/diagrams/src/lib/components/automation/AutomationTelegramNode.tsx +15 -4
- package/packages/diagrams/src/lib/components/automation/index.ts +8 -0
- package/packages/diagrams/src/lib/types/node-types.ts +3 -0
|
@@ -749,6 +749,52 @@ export const automationDefaultNodes = [
|
|
|
749
749
|
measured: { width: 300, height: 200 },
|
|
750
750
|
},
|
|
751
751
|
// =====================================
|
|
752
|
+
// Email Node
|
|
753
|
+
// =====================================
|
|
754
|
+
{
|
|
755
|
+
id: 'email-node',
|
|
756
|
+
type: 'AutomationEmailNode',
|
|
757
|
+
position: { x: 2200, y: 450 },
|
|
758
|
+
data: {
|
|
759
|
+
label: 'Send Email',
|
|
760
|
+
description: 'Send an email notification',
|
|
761
|
+
operationType: 'send-email',
|
|
762
|
+
status: 'Ready',
|
|
763
|
+
parameters: {
|
|
764
|
+
to: ['recipient@example.com'],
|
|
765
|
+
cc: [],
|
|
766
|
+
bcc: [],
|
|
767
|
+
subject: 'Workflow Notification',
|
|
768
|
+
body: 'Your workflow has completed successfully!',
|
|
769
|
+
htmlBody: '',
|
|
770
|
+
attachments: [],
|
|
771
|
+
emailAccessToken: '',
|
|
772
|
+
emailTokenExpiresAt: undefined,
|
|
773
|
+
emailProvider: 'smtp',
|
|
774
|
+
emailAddress: '',
|
|
775
|
+
},
|
|
776
|
+
emailAuth: {
|
|
777
|
+
provider: 'smtp',
|
|
778
|
+
isAuthenticated: false,
|
|
779
|
+
isLoading: false,
|
|
780
|
+
email: '',
|
|
781
|
+
},
|
|
782
|
+
formData: {
|
|
783
|
+
nodeId: 'email-node',
|
|
784
|
+
title: 'Send Email',
|
|
785
|
+
type: 'email',
|
|
786
|
+
operationType: 'send-email',
|
|
787
|
+
to: ['recipient@example.com'],
|
|
788
|
+
subject: 'Workflow Notification',
|
|
789
|
+
body: 'Your workflow has completed successfully!',
|
|
790
|
+
},
|
|
791
|
+
lastRun: 'Never',
|
|
792
|
+
},
|
|
793
|
+
width: 300,
|
|
794
|
+
height: 200,
|
|
795
|
+
measured: { width: 300, height: 200 },
|
|
796
|
+
},
|
|
797
|
+
// =====================================
|
|
752
798
|
// Interaction Node
|
|
753
799
|
// =====================================
|
|
754
800
|
{
|
package/package.json
CHANGED
|
@@ -0,0 +1,773 @@
|
|
|
1
|
+
import React, { useState, useEffect, useRef } from 'react';
|
|
2
|
+
import { createRoot } from 'react-dom/client';
|
|
3
|
+
import { Handle, Position, useNodeId } from '@xyflow/react';
|
|
4
|
+
import {
|
|
5
|
+
Box,
|
|
6
|
+
Typography,
|
|
7
|
+
Chip,
|
|
8
|
+
IconButton,
|
|
9
|
+
Card,
|
|
10
|
+
CardContent,
|
|
11
|
+
Button,
|
|
12
|
+
CircularProgress,
|
|
13
|
+
Tooltip,
|
|
14
|
+
LinearProgress,
|
|
15
|
+
} from '@mui/material';
|
|
16
|
+
import {
|
|
17
|
+
Email as EmailIcon,
|
|
18
|
+
Send as SendIcon,
|
|
19
|
+
AttachFile as AttachFileIcon,
|
|
20
|
+
AccessTime as AccessTimeIcon,
|
|
21
|
+
} from '@mui/icons-material';
|
|
22
|
+
import { RiCloseLine } from 'react-icons/ri';
|
|
23
|
+
import ReactJson from 'react-json-view';
|
|
24
|
+
import { useTranslation } from 'react-i18next';
|
|
25
|
+
import { useDiagram } from '../../contexts/DiagramProvider';
|
|
26
|
+
import { useSearch } from '../../contexts/SearchContext';
|
|
27
|
+
import { NodeActionButtons } from './NodeActionButtons';
|
|
28
|
+
import { showNodeAIAssistantPopup } from './NodeAIAssistantPopup';
|
|
29
|
+
import { getStatusColor } from './statusColors';
|
|
30
|
+
|
|
31
|
+
// ========================
|
|
32
|
+
// Types
|
|
33
|
+
// ========================
|
|
34
|
+
|
|
35
|
+
export type EmailOperationType =
|
|
36
|
+
| 'send-email'
|
|
37
|
+
| 'send-with-attachment'
|
|
38
|
+
| 'send-template'
|
|
39
|
+
| 'send-bulk'
|
|
40
|
+
| 'reply-to-email'
|
|
41
|
+
| 'forward-email';
|
|
42
|
+
|
|
43
|
+
export interface EmailTokenData {
|
|
44
|
+
accessToken: string;
|
|
45
|
+
expiresAt?: number;
|
|
46
|
+
email?: string;
|
|
47
|
+
provider?: 'smtp' | 'gmail' | 'outlook' | 'sendgrid' | 'mailgun';
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface AutomationEmailNodeData {
|
|
51
|
+
label: string;
|
|
52
|
+
description: string;
|
|
53
|
+
operationType?: EmailOperationType;
|
|
54
|
+
status: 'Ready' | 'Running' | 'Completed' | 'Failed' | 'Need to Config' | 'authenticated';
|
|
55
|
+
parameters?: {
|
|
56
|
+
// Email operations
|
|
57
|
+
to?: string[];
|
|
58
|
+
cc?: string[];
|
|
59
|
+
bcc?: string[];
|
|
60
|
+
subject?: string;
|
|
61
|
+
body?: string;
|
|
62
|
+
htmlBody?: string;
|
|
63
|
+
attachments?: Array<{
|
|
64
|
+
filename: string;
|
|
65
|
+
content: string;
|
|
66
|
+
contentType?: string;
|
|
67
|
+
}>;
|
|
68
|
+
|
|
69
|
+
// Template operations
|
|
70
|
+
templateId?: string;
|
|
71
|
+
templateVariables?: Record<string, any>;
|
|
72
|
+
|
|
73
|
+
// Bulk operations
|
|
74
|
+
recipients?: Array<{
|
|
75
|
+
email: string;
|
|
76
|
+
variables?: Record<string, any>;
|
|
77
|
+
}>;
|
|
78
|
+
|
|
79
|
+
// Reply/Forward operations
|
|
80
|
+
replyTo?: string;
|
|
81
|
+
inReplyTo?: string;
|
|
82
|
+
forwardTo?: string[];
|
|
83
|
+
|
|
84
|
+
// Token data
|
|
85
|
+
emailAccessToken?: string;
|
|
86
|
+
emailTokenExpiresAt?: number;
|
|
87
|
+
emailProvider?: string;
|
|
88
|
+
emailAddress?: string;
|
|
89
|
+
|
|
90
|
+
[key: string]: any;
|
|
91
|
+
};
|
|
92
|
+
emailAuth?: {
|
|
93
|
+
provider?: string;
|
|
94
|
+
isAuthenticated?: boolean;
|
|
95
|
+
isLoading?: boolean;
|
|
96
|
+
email?: string;
|
|
97
|
+
};
|
|
98
|
+
formData?: {
|
|
99
|
+
nodeId?: string;
|
|
100
|
+
title?: string;
|
|
101
|
+
type?: string;
|
|
102
|
+
operationType?: string;
|
|
103
|
+
[key: string]: any;
|
|
104
|
+
};
|
|
105
|
+
lastRun?: string;
|
|
106
|
+
duration?: string;
|
|
107
|
+
executionResult?: {
|
|
108
|
+
success: boolean;
|
|
109
|
+
data?: any;
|
|
110
|
+
error?: string;
|
|
111
|
+
};
|
|
112
|
+
onEmailLogin?: () => void;
|
|
113
|
+
onEmailDisconnect?: () => void;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export interface AutomationEmailNodeProps {
|
|
117
|
+
data: AutomationEmailNodeData;
|
|
118
|
+
selected?: boolean;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// ========================
|
|
122
|
+
// Operation Configuration
|
|
123
|
+
// ========================
|
|
124
|
+
|
|
125
|
+
const OPERATION_CONFIG: Record<
|
|
126
|
+
EmailOperationType,
|
|
127
|
+
{
|
|
128
|
+
icon: React.ElementType;
|
|
129
|
+
label: string;
|
|
130
|
+
color: string;
|
|
131
|
+
description: string;
|
|
132
|
+
}
|
|
133
|
+
> = {
|
|
134
|
+
'send-email': {
|
|
135
|
+
icon: SendIcon,
|
|
136
|
+
label: 'Send Email',
|
|
137
|
+
color: '#4285F4',
|
|
138
|
+
description: 'Send a simple email',
|
|
139
|
+
},
|
|
140
|
+
'send-with-attachment': {
|
|
141
|
+
icon: AttachFileIcon,
|
|
142
|
+
label: 'Send with Attachment',
|
|
143
|
+
color: '#34A853',
|
|
144
|
+
description: 'Send an email with file attachments',
|
|
145
|
+
},
|
|
146
|
+
'send-template': {
|
|
147
|
+
icon: EmailIcon,
|
|
148
|
+
label: 'Send Template',
|
|
149
|
+
color: '#FBBC04',
|
|
150
|
+
description: 'Send an email using a template',
|
|
151
|
+
},
|
|
152
|
+
'send-bulk': {
|
|
153
|
+
icon: EmailIcon,
|
|
154
|
+
label: 'Send Bulk',
|
|
155
|
+
color: '#EA4335',
|
|
156
|
+
description: 'Send bulk emails to multiple recipients',
|
|
157
|
+
},
|
|
158
|
+
'reply-to-email': {
|
|
159
|
+
icon: EmailIcon,
|
|
160
|
+
label: 'Reply to Email',
|
|
161
|
+
color: '#4285F4',
|
|
162
|
+
description: 'Reply to an existing email',
|
|
163
|
+
},
|
|
164
|
+
'forward-email': {
|
|
165
|
+
icon: EmailIcon,
|
|
166
|
+
label: 'Forward Email',
|
|
167
|
+
color: '#34A853',
|
|
168
|
+
description: 'Forward an email to recipients',
|
|
169
|
+
},
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
// ========================
|
|
173
|
+
// Helper Functions
|
|
174
|
+
// ========================
|
|
175
|
+
|
|
176
|
+
const isTokenExpired = (expiresAt?: number): boolean => {
|
|
177
|
+
if (!expiresAt) return false;
|
|
178
|
+
return Date.now() > expiresAt;
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
const getTimeUntilExpiry = (expiresAt?: number): string => {
|
|
182
|
+
if (!expiresAt) return 'No expiry';
|
|
183
|
+
const diff = expiresAt - Date.now();
|
|
184
|
+
if (diff <= 0) return 'Expired';
|
|
185
|
+
const minutes = Math.floor(diff / 60000);
|
|
186
|
+
if (minutes < 60) return `${minutes}m`;
|
|
187
|
+
const hours = Math.floor(minutes / 60);
|
|
188
|
+
return `${hours}h ${minutes % 60}m`;
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
// ========================
|
|
192
|
+
// Main Component
|
|
193
|
+
// ========================
|
|
194
|
+
|
|
195
|
+
export const AutomationEmailNode: React.FC<AutomationEmailNodeProps> = ({
|
|
196
|
+
data,
|
|
197
|
+
selected,
|
|
198
|
+
}) => {
|
|
199
|
+
const { t } = useTranslation();
|
|
200
|
+
const { highlightText } = useSearch();
|
|
201
|
+
const [isJsonOpen, setIsJsonOpen] = useState(false);
|
|
202
|
+
const rootRef = useRef<any>(null);
|
|
203
|
+
const portalRef = useRef<HTMLDivElement | null>(null);
|
|
204
|
+
const nodeRef = useRef<HTMLDivElement | null>(null);
|
|
205
|
+
const nodeId = useNodeId();
|
|
206
|
+
const setSelectedNode = useDiagram((state) => state.setSelectedNode);
|
|
207
|
+
const enableJson = useDiagram((state) => state.enableNodeJsonPopover ?? true);
|
|
208
|
+
const onNodesChange = useDiagram((state) => state.onNodesChange);
|
|
209
|
+
const nodes = useDiagram((state) => state.nodes);
|
|
210
|
+
const setNodes = useDiagram((state) => state.setNodes);
|
|
211
|
+
|
|
212
|
+
// Get operation configuration
|
|
213
|
+
const operationTypeRaw = data.operationType || data.formData?.operationType || 'send-email';
|
|
214
|
+
const operationType = (Object.keys(OPERATION_CONFIG).includes(operationTypeRaw) ? operationTypeRaw : 'send-email') as EmailOperationType;
|
|
215
|
+
const operationConfig = OPERATION_CONFIG[operationType];
|
|
216
|
+
const OperationIcon = operationConfig.icon;
|
|
217
|
+
|
|
218
|
+
// Auth state
|
|
219
|
+
const isAuthenticated = data.emailAuth?.isAuthenticated || !!data.parameters?.emailAccessToken;
|
|
220
|
+
const isAuthLoading = data.emailAuth?.isLoading || false;
|
|
221
|
+
const emailAddress = data.emailAuth?.email || data.parameters?.emailAddress;
|
|
222
|
+
const tokenExpired = isTokenExpired(data.parameters?.emailTokenExpiresAt);
|
|
223
|
+
|
|
224
|
+
// Execution state
|
|
225
|
+
const status = data.status || 'Ready';
|
|
226
|
+
const executionProgress = status === 'Running' ? 50 : 0;
|
|
227
|
+
|
|
228
|
+
// Status configuration - using centralized status colors
|
|
229
|
+
const statusConfig = getStatusColor(status, status === 'authenticated' ? 'authenticated' : 'ready');
|
|
230
|
+
|
|
231
|
+
// Handle JSON view
|
|
232
|
+
const handleJsonClick = () => {
|
|
233
|
+
if (nodeId) setSelectedNode(nodeId);
|
|
234
|
+
if (!enableJson) return;
|
|
235
|
+
setIsJsonOpen(!isJsonOpen);
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
const handleClose = () => {
|
|
239
|
+
setIsJsonOpen(false);
|
|
240
|
+
// Clean up portal
|
|
241
|
+
if (rootRef.current) {
|
|
242
|
+
rootRef.current.unmount();
|
|
243
|
+
rootRef.current = null;
|
|
244
|
+
}
|
|
245
|
+
if (portalRef.current) {
|
|
246
|
+
document.body.removeChild(portalRef.current);
|
|
247
|
+
portalRef.current = null;
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
// Render email auth button
|
|
252
|
+
const renderEmailAuthButton = () => {
|
|
253
|
+
if (isAuthenticated && !tokenExpired) {
|
|
254
|
+
return (
|
|
255
|
+
<Button
|
|
256
|
+
onClick={data.onEmailDisconnect}
|
|
257
|
+
disabled={isAuthLoading}
|
|
258
|
+
fullWidth
|
|
259
|
+
sx={{
|
|
260
|
+
backgroundColor: '#4285F4',
|
|
261
|
+
borderRadius: '8px',
|
|
262
|
+
padding: '8px 12px',
|
|
263
|
+
gap: '8px',
|
|
264
|
+
color: '#FFFFFF',
|
|
265
|
+
textTransform: 'none',
|
|
266
|
+
fontSize: '13px',
|
|
267
|
+
fontWeight: 500,
|
|
268
|
+
'&:hover': {
|
|
269
|
+
backgroundColor: '#357ae8',
|
|
270
|
+
},
|
|
271
|
+
}}
|
|
272
|
+
>
|
|
273
|
+
<EmailIcon sx={{ fontSize: 18 }} />
|
|
274
|
+
{emailAddress ? emailAddress : 'Connected'}
|
|
275
|
+
</Button>
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return (
|
|
280
|
+
<Button
|
|
281
|
+
onClick={data.onEmailLogin}
|
|
282
|
+
disabled={isAuthLoading}
|
|
283
|
+
fullWidth
|
|
284
|
+
sx={{
|
|
285
|
+
backgroundColor: '#171C29',
|
|
286
|
+
border: '1px solid #374151',
|
|
287
|
+
borderRadius: '8px',
|
|
288
|
+
padding: '8px 12px',
|
|
289
|
+
gap: '8px',
|
|
290
|
+
color: '#B2BCD8',
|
|
291
|
+
textTransform: 'none',
|
|
292
|
+
fontSize: '13px',
|
|
293
|
+
fontWeight: 500,
|
|
294
|
+
'&:hover': {
|
|
295
|
+
backgroundColor: '#1F2937',
|
|
296
|
+
borderColor: '#4285F4',
|
|
297
|
+
},
|
|
298
|
+
'&:disabled': {
|
|
299
|
+
opacity: 0.6,
|
|
300
|
+
},
|
|
301
|
+
}}
|
|
302
|
+
>
|
|
303
|
+
{isAuthLoading ? (
|
|
304
|
+
<>
|
|
305
|
+
<CircularProgress size={16} sx={{ color: '#B2BCD8' }} />
|
|
306
|
+
Connecting...
|
|
307
|
+
</>
|
|
308
|
+
) : (
|
|
309
|
+
<>
|
|
310
|
+
<EmailIcon sx={{ fontSize: 18 }} />
|
|
311
|
+
Connect Email
|
|
312
|
+
</>
|
|
313
|
+
)}
|
|
314
|
+
</Button>
|
|
315
|
+
);
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
// Render status badge
|
|
319
|
+
const renderStatusBadge = () => {
|
|
320
|
+
return (
|
|
321
|
+
<Chip
|
|
322
|
+
label={status}
|
|
323
|
+
size="small"
|
|
324
|
+
sx={{
|
|
325
|
+
backgroundColor: statusConfig.bgColor,
|
|
326
|
+
color: statusConfig.color,
|
|
327
|
+
fontWeight: 500,
|
|
328
|
+
fontSize: '11px',
|
|
329
|
+
height: '22px',
|
|
330
|
+
borderRadius: '11px',
|
|
331
|
+
}}
|
|
332
|
+
/>
|
|
333
|
+
);
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
useEffect(() => {
|
|
337
|
+
const handleClickOutside = (event: MouseEvent) => {
|
|
338
|
+
if (isJsonOpen && !(event.target as Element).closest('#automation-json-popover')) {
|
|
339
|
+
handleClose();
|
|
340
|
+
}
|
|
341
|
+
};
|
|
342
|
+
document.addEventListener('mousedown', handleClickOutside);
|
|
343
|
+
return () => {
|
|
344
|
+
document.removeEventListener('mousedown', handleClickOutside);
|
|
345
|
+
};
|
|
346
|
+
}, [isJsonOpen]);
|
|
347
|
+
|
|
348
|
+
useEffect(() => {
|
|
349
|
+
if (isJsonOpen) {
|
|
350
|
+
const portalRoot = document.createElement('div');
|
|
351
|
+
document.body.appendChild(portalRoot);
|
|
352
|
+
portalRef.current = portalRoot;
|
|
353
|
+
|
|
354
|
+
const root = createRoot(portalRoot);
|
|
355
|
+
rootRef.current = root;
|
|
356
|
+
|
|
357
|
+
root.render(
|
|
358
|
+
<Card
|
|
359
|
+
id="automation-json-popover"
|
|
360
|
+
sx={{
|
|
361
|
+
position: 'fixed',
|
|
362
|
+
top: 0,
|
|
363
|
+
right: 0,
|
|
364
|
+
zIndex: 9999,
|
|
365
|
+
width: '400px',
|
|
366
|
+
height: '100vh',
|
|
367
|
+
overflow: 'auto',
|
|
368
|
+
bgcolor: '#242424',
|
|
369
|
+
color: '#fff',
|
|
370
|
+
border: '1px solid #333',
|
|
371
|
+
'&::-webkit-scrollbar': {
|
|
372
|
+
width: '6px',
|
|
373
|
+
},
|
|
374
|
+
'&::-webkit-scrollbar-track': {
|
|
375
|
+
background: 'transparent',
|
|
376
|
+
},
|
|
377
|
+
'&::-webkit-scrollbar-thumb': {
|
|
378
|
+
background: '#444',
|
|
379
|
+
borderRadius: '3px',
|
|
380
|
+
'&:hover': {
|
|
381
|
+
background: '#666',
|
|
382
|
+
},
|
|
383
|
+
},
|
|
384
|
+
}}
|
|
385
|
+
>
|
|
386
|
+
<CardContent sx={{ bgcolor: '#242424', color: '#fff' }}>
|
|
387
|
+
<IconButton
|
|
388
|
+
aria-label="close"
|
|
389
|
+
onClick={handleClose}
|
|
390
|
+
sx={{
|
|
391
|
+
color: '#999',
|
|
392
|
+
'&:hover': {
|
|
393
|
+
color: '#fff',
|
|
394
|
+
bgcolor: 'rgba(255, 255, 255, 0.1)',
|
|
395
|
+
},
|
|
396
|
+
}}
|
|
397
|
+
>
|
|
398
|
+
<RiCloseLine />
|
|
399
|
+
</IconButton>
|
|
400
|
+
{/* Show execution result prominently if available */}
|
|
401
|
+
{data.formData?.executionResult && (
|
|
402
|
+
<Box sx={{ mb: 2 }}>
|
|
403
|
+
<Typography variant="h6" sx={{ color: '#fff', mb: 1 }}>
|
|
404
|
+
{t('automation.common.executionResult')}
|
|
405
|
+
</Typography>
|
|
406
|
+
<Box sx={{
|
|
407
|
+
bgcolor: data.formData.executionResult.success ? '#1e3a8a' : '#dc2626',
|
|
408
|
+
p: 1,
|
|
409
|
+
borderRadius: 1,
|
|
410
|
+
mb: 1
|
|
411
|
+
}}>
|
|
412
|
+
<Typography variant="body2" sx={{ color: '#fff' }}>
|
|
413
|
+
{t('automation.common.status')}: {data.formData.executionResult.success ? t('automation.common.success') : t('automation.common.failed')}
|
|
414
|
+
</Typography>
|
|
415
|
+
<Typography variant="body2" sx={{ color: '#fff' }}>
|
|
416
|
+
{t('automation.common.timestamp')}: {new Date(data.formData.executionResult.timestamp).toLocaleString()}
|
|
417
|
+
</Typography>
|
|
418
|
+
{data.formData.executionResult.error && (
|
|
419
|
+
<Typography variant="body2" sx={{ color: '#fff' }}>
|
|
420
|
+
{t('automation.common.error')}: {data.formData.executionResult.error}
|
|
421
|
+
</Typography>
|
|
422
|
+
)}
|
|
423
|
+
</Box>
|
|
424
|
+
<Typography variant="h6" sx={{ color: '#fff', mb: 1 }}>
|
|
425
|
+
{t('automation.emailNode.executionData')}
|
|
426
|
+
</Typography>
|
|
427
|
+
<ReactJson
|
|
428
|
+
theme={'monokai'}
|
|
429
|
+
src={data.formData.executionResult.data}
|
|
430
|
+
collapsed={false}
|
|
431
|
+
/>
|
|
432
|
+
</Box>
|
|
433
|
+
)}
|
|
434
|
+
|
|
435
|
+
{/* Show full node data */}
|
|
436
|
+
<Typography variant="h6" sx={{ color: '#fff', mb: 1 }}>
|
|
437
|
+
{t('automation.common.fullNodeData')}
|
|
438
|
+
</Typography>
|
|
439
|
+
<ReactJson theme={'monokai'} src={data.formData || data} collapsed={false} />
|
|
440
|
+
</CardContent>
|
|
441
|
+
</Card>
|
|
442
|
+
);
|
|
443
|
+
} else {
|
|
444
|
+
// Clean up when closing
|
|
445
|
+
if (rootRef.current) {
|
|
446
|
+
rootRef.current.unmount();
|
|
447
|
+
rootRef.current = null;
|
|
448
|
+
}
|
|
449
|
+
if (portalRef.current) {
|
|
450
|
+
document.body.removeChild(portalRef.current);
|
|
451
|
+
portalRef.current = null;
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
}, [isJsonOpen, data]);
|
|
455
|
+
|
|
456
|
+
// ========================
|
|
457
|
+
// Render
|
|
458
|
+
// ========================
|
|
459
|
+
return (
|
|
460
|
+
<Box
|
|
461
|
+
sx={{
|
|
462
|
+
position: 'relative',
|
|
463
|
+
width: '336px',
|
|
464
|
+
overflow: 'visible',
|
|
465
|
+
}}
|
|
466
|
+
>
|
|
467
|
+
<Box
|
|
468
|
+
ref={nodeRef}
|
|
469
|
+
sx={{
|
|
470
|
+
width: '336px',
|
|
471
|
+
minHeight: '150px',
|
|
472
|
+
backgroundColor: '#181C25',
|
|
473
|
+
border: selected ? '2px solid #3b82f6' : '1px solid #1e293b',
|
|
474
|
+
borderRadius: '12px',
|
|
475
|
+
color: '#ffffff',
|
|
476
|
+
position: 'relative',
|
|
477
|
+
boxShadow: selected ? '0 0 0 2px rgba(59, 130, 246, 0.5)' : '0 4px 8px rgba(0, 0, 0, 0.3)',
|
|
478
|
+
transition: 'all 0.2s ease',
|
|
479
|
+
cursor: 'pointer',
|
|
480
|
+
overflow: 'hidden',
|
|
481
|
+
...(status === 'Running' && {
|
|
482
|
+
animation: 'pulse-glow 2s ease-in-out infinite',
|
|
483
|
+
'@keyframes pulse-glow': {
|
|
484
|
+
'0%, 100%': {
|
|
485
|
+
boxShadow: '0 0 20px rgba(59, 130, 246, 0.4), 0 0 40px rgba(59, 130, 246, 0.2)',
|
|
486
|
+
borderColor: 'rgba(59, 130, 246, 0.6)',
|
|
487
|
+
},
|
|
488
|
+
'50%': {
|
|
489
|
+
boxShadow: '0 0 30px rgba(59, 130, 246, 0.6), 0 0 60px rgba(59, 130, 246, 0.3)',
|
|
490
|
+
borderColor: 'rgba(59, 130, 246, 0.9)',
|
|
491
|
+
},
|
|
492
|
+
},
|
|
493
|
+
}),
|
|
494
|
+
}}
|
|
495
|
+
onClick={handleJsonClick}
|
|
496
|
+
>
|
|
497
|
+
{/* Top Header Section */}
|
|
498
|
+
<Box sx={{
|
|
499
|
+
backgroundColor: "rgba(67, 93, 132, 0.1)",
|
|
500
|
+
padding: '8px 16px',
|
|
501
|
+
borderRadius: '12px 12px 0 0'
|
|
502
|
+
}}>
|
|
503
|
+
<Typography variant="body2" sx={{
|
|
504
|
+
color: '#ffffff',
|
|
505
|
+
fontSize: '12px',
|
|
506
|
+
fontWeight: 500
|
|
507
|
+
}}>
|
|
508
|
+
{highlightText(data.description || operationConfig.description)}
|
|
509
|
+
</Typography>
|
|
510
|
+
</Box>
|
|
511
|
+
|
|
512
|
+
{/* Main Content */}
|
|
513
|
+
<Box sx={{ padding: '16px' }}>
|
|
514
|
+
{/* Title Section */}
|
|
515
|
+
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 2 }}>
|
|
516
|
+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5 }}>
|
|
517
|
+
<Box
|
|
518
|
+
sx={{
|
|
519
|
+
width: '32px',
|
|
520
|
+
height: '32px',
|
|
521
|
+
backgroundColor: '#0ea5e9',
|
|
522
|
+
borderRadius: '50%',
|
|
523
|
+
display: 'flex',
|
|
524
|
+
alignItems: 'center',
|
|
525
|
+
justifyContent: 'center',
|
|
526
|
+
}}
|
|
527
|
+
>
|
|
528
|
+
<EmailIcon sx={{ fontSize: 18, color: '#FFFFFF' }} />
|
|
529
|
+
</Box>
|
|
530
|
+
<Typography variant="h6" sx={{ fontWeight: 600, fontSize: '16px' }}>
|
|
531
|
+
{highlightText(data.label || operationConfig.label)}
|
|
532
|
+
</Typography>
|
|
533
|
+
</Box>
|
|
534
|
+
<Chip
|
|
535
|
+
label={status}
|
|
536
|
+
size="small"
|
|
537
|
+
sx={{
|
|
538
|
+
backgroundColor: statusConfig.bgColor,
|
|
539
|
+
color: statusConfig.color,
|
|
540
|
+
fontWeight: 500,
|
|
541
|
+
fontSize: '12px',
|
|
542
|
+
height: '24px',
|
|
543
|
+
borderRadius: '12px',
|
|
544
|
+
}}
|
|
545
|
+
/>
|
|
546
|
+
</Box>
|
|
547
|
+
|
|
548
|
+
{/* Description Box */}
|
|
549
|
+
<Box sx={{
|
|
550
|
+
backgroundColor: '#1F2937',
|
|
551
|
+
borderRadius: '8px',
|
|
552
|
+
padding: '12px',
|
|
553
|
+
mb: 2,
|
|
554
|
+
border: '1px solid #374151'
|
|
555
|
+
}}>
|
|
556
|
+
{/* Inner text boundary box */}
|
|
557
|
+
<Box sx={{
|
|
558
|
+
backgroundColor: 'transparent',
|
|
559
|
+
borderRadius: '4px',
|
|
560
|
+
padding: '8px',
|
|
561
|
+
border: '1px solid #4B5563',
|
|
562
|
+
minHeight: '40px',
|
|
563
|
+
display: 'flex',
|
|
564
|
+
alignItems: 'center'
|
|
565
|
+
}}>
|
|
566
|
+
<Typography variant="body2" sx={{
|
|
567
|
+
color: '#9CA3AF',
|
|
568
|
+
fontSize: '12px',
|
|
569
|
+
lineHeight: 1.4,
|
|
570
|
+
margin: 0
|
|
571
|
+
}}>
|
|
572
|
+
{data.description || operationConfig.description}
|
|
573
|
+
</Typography>
|
|
574
|
+
</Box>
|
|
575
|
+
</Box>
|
|
576
|
+
|
|
577
|
+
{/* Email Auth Button */}
|
|
578
|
+
{renderEmailAuthButton()}
|
|
579
|
+
|
|
580
|
+
{/* Operation-specific Parameters Display */}
|
|
581
|
+
{(operationType === 'send-email' && (data.parameters?.to || data.parameters?.subject)) && (
|
|
582
|
+
<Box sx={{ mt: 2, mb: 1 }}>
|
|
583
|
+
{data.parameters?.to && (
|
|
584
|
+
<Typography variant="caption" sx={{ color: '#9CA3AF', fontSize: '11px', display: 'block' }}>
|
|
585
|
+
To: {data.parameters.to.join(', ')}
|
|
586
|
+
</Typography>
|
|
587
|
+
)}
|
|
588
|
+
{data.parameters?.subject && (
|
|
589
|
+
<Typography variant="caption" sx={{ color: '#9CA3AF', fontSize: '11px', display: 'block', mt: 0.5 }}>
|
|
590
|
+
Subject: {data.parameters.subject}
|
|
591
|
+
</Typography>
|
|
592
|
+
)}
|
|
593
|
+
</Box>
|
|
594
|
+
)}
|
|
595
|
+
|
|
596
|
+
{/* Last Run Info */}
|
|
597
|
+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5, mt: 1 }}>
|
|
598
|
+
<AccessTimeIcon sx={{ fontSize: '14px', color: '#9CA3AF' }} />
|
|
599
|
+
<Typography variant="body2" sx={{ color: '#9CA3AF', fontSize: '11px' }}>
|
|
600
|
+
{t('automation.common.lastRan')}: {data.lastRun || 'Never'}
|
|
601
|
+
</Typography>
|
|
602
|
+
</Box>
|
|
603
|
+
</Box>
|
|
604
|
+
|
|
605
|
+
{/* Connection Handles - Bidirectional (source + target at each position) */}
|
|
606
|
+
{/* Top - Source */}
|
|
607
|
+
<Handle
|
|
608
|
+
type="source"
|
|
609
|
+
position={Position.Top}
|
|
610
|
+
id="top-source"
|
|
611
|
+
className="connection-handle"
|
|
612
|
+
style={{
|
|
613
|
+
background: selected ? '#10B981' : '#1a1a2e',
|
|
614
|
+
width: '14px',
|
|
615
|
+
height: '14px',
|
|
616
|
+
border: '3px solid #10B981',
|
|
617
|
+
top: '-8px',
|
|
618
|
+
opacity: selected ? 1 : 0,
|
|
619
|
+
transition: 'all 0.2s ease-in-out',
|
|
620
|
+
cursor: 'crosshair',
|
|
621
|
+
zIndex: 10,
|
|
622
|
+
}}
|
|
623
|
+
/>
|
|
624
|
+
{/* Top - Target (hidden but functional) */}
|
|
625
|
+
<Handle
|
|
626
|
+
type="target"
|
|
627
|
+
position={Position.Top}
|
|
628
|
+
id="top-target"
|
|
629
|
+
style={{
|
|
630
|
+
background: 'transparent',
|
|
631
|
+
width: '14px',
|
|
632
|
+
height: '14px',
|
|
633
|
+
border: 'none',
|
|
634
|
+
top: '-8px',
|
|
635
|
+
opacity: 0,
|
|
636
|
+
pointerEvents: selected ? 'all' : 'none',
|
|
637
|
+
}}
|
|
638
|
+
/>
|
|
639
|
+
{/* Bottom - Source */}
|
|
640
|
+
<Handle
|
|
641
|
+
type="source"
|
|
642
|
+
position={Position.Bottom}
|
|
643
|
+
id="bottom-source"
|
|
644
|
+
className="connection-handle"
|
|
645
|
+
style={{
|
|
646
|
+
background: selected ? '#10B981' : '#1a1a2e',
|
|
647
|
+
width: '14px',
|
|
648
|
+
height: '14px',
|
|
649
|
+
border: '3px solid #10B981',
|
|
650
|
+
bottom: '-8px',
|
|
651
|
+
opacity: selected ? 1 : 0,
|
|
652
|
+
transition: 'all 0.2s ease-in-out',
|
|
653
|
+
cursor: 'crosshair',
|
|
654
|
+
zIndex: 10,
|
|
655
|
+
}}
|
|
656
|
+
/>
|
|
657
|
+
{/* Bottom - Target (hidden but functional) */}
|
|
658
|
+
<Handle
|
|
659
|
+
type="target"
|
|
660
|
+
position={Position.Bottom}
|
|
661
|
+
id="bottom-target"
|
|
662
|
+
style={{
|
|
663
|
+
background: 'transparent',
|
|
664
|
+
width: '14px',
|
|
665
|
+
height: '14px',
|
|
666
|
+
border: 'none',
|
|
667
|
+
bottom: '-8px',
|
|
668
|
+
opacity: 0,
|
|
669
|
+
pointerEvents: selected ? 'all' : 'none',
|
|
670
|
+
}}
|
|
671
|
+
/>
|
|
672
|
+
{/* Left - Source */}
|
|
673
|
+
<Handle
|
|
674
|
+
type="source"
|
|
675
|
+
position={Position.Left}
|
|
676
|
+
id="left-source"
|
|
677
|
+
className="connection-handle"
|
|
678
|
+
style={{
|
|
679
|
+
background: selected ? '#10B981' : '#1a1a2e',
|
|
680
|
+
width: '14px',
|
|
681
|
+
height: '14px',
|
|
682
|
+
border: '3px solid #10B981',
|
|
683
|
+
left: '-8px',
|
|
684
|
+
opacity: selected ? 1 : 0,
|
|
685
|
+
transition: 'all 0.2s ease-in-out',
|
|
686
|
+
cursor: 'crosshair',
|
|
687
|
+
zIndex: 10,
|
|
688
|
+
}}
|
|
689
|
+
/>
|
|
690
|
+
{/* Left - Target (hidden but functional) */}
|
|
691
|
+
<Handle
|
|
692
|
+
type="target"
|
|
693
|
+
position={Position.Left}
|
|
694
|
+
id="left-target"
|
|
695
|
+
style={{
|
|
696
|
+
background: 'transparent',
|
|
697
|
+
width: '14px',
|
|
698
|
+
height: '14px',
|
|
699
|
+
border: 'none',
|
|
700
|
+
left: '-8px',
|
|
701
|
+
opacity: 0,
|
|
702
|
+
pointerEvents: selected ? 'all' : 'none',
|
|
703
|
+
}}
|
|
704
|
+
/>
|
|
705
|
+
{/* Right - Source */}
|
|
706
|
+
<Handle
|
|
707
|
+
type="source"
|
|
708
|
+
position={Position.Right}
|
|
709
|
+
id="right-source"
|
|
710
|
+
className="connection-handle"
|
|
711
|
+
style={{
|
|
712
|
+
background: selected ? '#10B981' : '#1a1a2e',
|
|
713
|
+
width: '14px',
|
|
714
|
+
height: '14px',
|
|
715
|
+
border: '3px solid #10B981',
|
|
716
|
+
right: '-8px',
|
|
717
|
+
opacity: selected ? 1 : 0,
|
|
718
|
+
transition: 'all 0.2s ease-in-out',
|
|
719
|
+
cursor: 'crosshair',
|
|
720
|
+
zIndex: 10,
|
|
721
|
+
}}
|
|
722
|
+
/>
|
|
723
|
+
{/* Right - Target (hidden but functional) */}
|
|
724
|
+
<Handle
|
|
725
|
+
type="target"
|
|
726
|
+
position={Position.Right}
|
|
727
|
+
id="right-target"
|
|
728
|
+
style={{
|
|
729
|
+
background: 'transparent',
|
|
730
|
+
width: '14px',
|
|
731
|
+
height: '14px',
|
|
732
|
+
border: 'none',
|
|
733
|
+
right: '-8px',
|
|
734
|
+
opacity: 0,
|
|
735
|
+
pointerEvents: selected ? 'all' : 'none',
|
|
736
|
+
}}
|
|
737
|
+
/>
|
|
738
|
+
</Box>
|
|
739
|
+
|
|
740
|
+
{/* Node Action Buttons - Shows when selected */}
|
|
741
|
+
<NodeActionButtons
|
|
742
|
+
selected={selected}
|
|
743
|
+
onOpenAIAssistant={(buttonElement) => {
|
|
744
|
+
if (nodeId) {
|
|
745
|
+
showNodeAIAssistantPopup(nodeId, 'Email Node', buttonElement);
|
|
746
|
+
}
|
|
747
|
+
}}
|
|
748
|
+
onDelete={() => {
|
|
749
|
+
if (nodeId && onNodesChange) {
|
|
750
|
+
onNodesChange([{ id: nodeId, type: 'remove' }]);
|
|
751
|
+
}
|
|
752
|
+
}}
|
|
753
|
+
onDuplicate={() => {
|
|
754
|
+
if (nodeId) {
|
|
755
|
+
const currentNode = nodes.find(n => n.id === nodeId);
|
|
756
|
+
if (currentNode) {
|
|
757
|
+
const newNode = {
|
|
758
|
+
...currentNode,
|
|
759
|
+
id: `${currentNode.id}-copy-${Date.now()}`,
|
|
760
|
+
position: {
|
|
761
|
+
x: currentNode.position.x + 50,
|
|
762
|
+
y: currentNode.position.y + 50,
|
|
763
|
+
},
|
|
764
|
+
selected: false,
|
|
765
|
+
};
|
|
766
|
+
setNodes([...nodes, newNode]);
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
}}
|
|
770
|
+
/>
|
|
771
|
+
</Box>
|
|
772
|
+
);
|
|
773
|
+
};
|
|
@@ -463,7 +463,7 @@ export const AutomationGoogleServicesNode: React.FC<AutomationGoogleServicesNode
|
|
|
463
463
|
borderRadius: '12px',
|
|
464
464
|
color: '#ffffff',
|
|
465
465
|
position: 'relative',
|
|
466
|
-
boxShadow: selected ?
|
|
466
|
+
boxShadow: selected ? '0 0 0 2px rgba(59, 130, 246, 0.5)' : '0 4px 8px rgba(0, 0, 0, 0.3)',
|
|
467
467
|
transition: 'all 0.2s ease',
|
|
468
468
|
cursor: 'pointer',
|
|
469
469
|
overflow: 'hidden',
|
|
@@ -471,12 +471,12 @@ export const AutomationGoogleServicesNode: React.FC<AutomationGoogleServicesNode
|
|
|
471
471
|
animation: 'pulse-glow 2s ease-in-out infinite',
|
|
472
472
|
'@keyframes pulse-glow': {
|
|
473
473
|
'0%, 100%': {
|
|
474
|
-
boxShadow:
|
|
475
|
-
borderColor:
|
|
474
|
+
boxShadow: '0 0 20px rgba(59, 130, 246, 0.4), 0 0 40px rgba(59, 130, 246, 0.2)',
|
|
475
|
+
borderColor: 'rgba(59, 130, 246, 0.6)',
|
|
476
476
|
},
|
|
477
477
|
'50%': {
|
|
478
|
-
boxShadow:
|
|
479
|
-
borderColor:
|
|
478
|
+
boxShadow: '0 0 30px rgba(59, 130, 246, 0.6), 0 0 60px rgba(59, 130, 246, 0.3)',
|
|
479
|
+
borderColor: 'rgba(59, 130, 246, 0.9)',
|
|
480
480
|
},
|
|
481
481
|
},
|
|
482
482
|
}),
|
|
@@ -403,7 +403,7 @@ export const AutomationInteractionNode: React.FC<AutomationInteractionNodeProps>
|
|
|
403
403
|
borderRadius: '12px',
|
|
404
404
|
color: '#ffffff',
|
|
405
405
|
position: 'relative',
|
|
406
|
-
boxShadow: selected ?
|
|
406
|
+
boxShadow: selected ? '0 0 0 2px rgba(59, 130, 246, 0.5)' : '0 4px 8px rgba(0, 0, 0, 0.3)',
|
|
407
407
|
transition: 'all 0.2s ease',
|
|
408
408
|
cursor: 'pointer',
|
|
409
409
|
overflow: 'hidden',
|
|
@@ -411,12 +411,12 @@ export const AutomationInteractionNode: React.FC<AutomationInteractionNodeProps>
|
|
|
411
411
|
animation: 'pulse-glow 2s ease-in-out infinite',
|
|
412
412
|
'@keyframes pulse-glow': {
|
|
413
413
|
'0%, 100%': {
|
|
414
|
-
boxShadow:
|
|
415
|
-
borderColor:
|
|
414
|
+
boxShadow: '0 0 20px rgba(59, 130, 246, 0.4), 0 0 40px rgba(59, 130, 246, 0.2)',
|
|
415
|
+
borderColor: 'rgba(59, 130, 246, 0.6)',
|
|
416
416
|
},
|
|
417
417
|
'50%': {
|
|
418
|
-
boxShadow:
|
|
419
|
-
borderColor:
|
|
418
|
+
boxShadow: '0 0 30px rgba(59, 130, 246, 0.6), 0 0 60px rgba(59, 130, 246, 0.3)',
|
|
419
|
+
borderColor: 'rgba(59, 130, 246, 0.9)',
|
|
420
420
|
},
|
|
421
421
|
},
|
|
422
422
|
}),
|
|
@@ -190,24 +190,22 @@ export const AutomationMonitoringNode: React.FC<AutomationMonitoringNodeProps> =
|
|
|
190
190
|
sx={{
|
|
191
191
|
bgcolor: '#1E1E2E',
|
|
192
192
|
borderRadius: 2,
|
|
193
|
-
border: selected ?
|
|
193
|
+
border: selected ? '2px solid rgba(59, 130, 246, 0.5)' : '1px solid #374151',
|
|
194
194
|
minWidth: 280,
|
|
195
195
|
maxWidth: 320,
|
|
196
196
|
overflow: 'hidden',
|
|
197
|
-
boxShadow: selected
|
|
198
|
-
? `0 0 20px ${monitoringColor}30`
|
|
199
|
-
: '0 4px 12px rgba(0, 0, 0, 0.3)',
|
|
197
|
+
boxShadow: selected ? '0 0 0 2px rgba(59, 130, 246, 0.5)' : '0 4px 8px rgba(0, 0, 0, 0.3)',
|
|
200
198
|
transition: 'all 0.2s ease',
|
|
201
199
|
...(status === 'Running' && {
|
|
202
200
|
animation: 'pulse-glow 2s ease-in-out infinite',
|
|
203
201
|
'@keyframes pulse-glow': {
|
|
204
202
|
'0%, 100%': {
|
|
205
|
-
boxShadow:
|
|
206
|
-
borderColor:
|
|
203
|
+
boxShadow: '0 0 20px rgba(59, 130, 246, 0.4), 0 0 40px rgba(59, 130, 246, 0.2)',
|
|
204
|
+
borderColor: 'rgba(59, 130, 246, 0.6)',
|
|
207
205
|
},
|
|
208
206
|
'50%': {
|
|
209
|
-
boxShadow:
|
|
210
|
-
borderColor:
|
|
207
|
+
boxShadow: '0 0 30px rgba(59, 130, 246, 0.6), 0 0 60px rgba(59, 130, 246, 0.3)',
|
|
208
|
+
borderColor: 'rgba(59, 130, 246, 0.9)',
|
|
211
209
|
},
|
|
212
210
|
},
|
|
213
211
|
}),
|
|
@@ -434,14 +434,25 @@ export const AutomationSlackNode: React.FC<AutomationSlackNodeProps> = ({
|
|
|
434
434
|
sx={{
|
|
435
435
|
bgcolor: '#1E1E2E',
|
|
436
436
|
borderRadius: 2,
|
|
437
|
-
border: selected ?
|
|
437
|
+
border: selected ? '2px solid rgba(59, 130, 246, 0.5)' : '1px solid #374151',
|
|
438
438
|
minWidth: 280,
|
|
439
439
|
maxWidth: 320,
|
|
440
440
|
overflow: 'hidden',
|
|
441
|
-
boxShadow: selected
|
|
442
|
-
? `0 0 20px ${operationConfig.color}30`
|
|
443
|
-
: '0 4px 12px rgba(0, 0, 0, 0.3)',
|
|
441
|
+
boxShadow: selected ? '0 0 0 2px rgba(59, 130, 246, 0.5)' : '0 4px 8px rgba(0, 0, 0, 0.3)',
|
|
444
442
|
transition: 'all 0.2s ease',
|
|
443
|
+
...(status === 'Running' && {
|
|
444
|
+
animation: 'pulse-glow 2s ease-in-out infinite',
|
|
445
|
+
'@keyframes pulse-glow': {
|
|
446
|
+
'0%, 100%': {
|
|
447
|
+
boxShadow: '0 0 20px rgba(59, 130, 246, 0.4), 0 0 40px rgba(59, 130, 246, 0.2)',
|
|
448
|
+
borderColor: 'rgba(59, 130, 246, 0.6)',
|
|
449
|
+
},
|
|
450
|
+
'50%': {
|
|
451
|
+
boxShadow: '0 0 30px rgba(59, 130, 246, 0.6), 0 0 60px rgba(59, 130, 246, 0.3)',
|
|
452
|
+
borderColor: 'rgba(59, 130, 246, 0.9)',
|
|
453
|
+
},
|
|
454
|
+
},
|
|
455
|
+
}),
|
|
445
456
|
}}
|
|
446
457
|
onClick={() => setSelectedNode(nodeId || '')}
|
|
447
458
|
>
|
|
@@ -427,14 +427,25 @@ export const AutomationTelegramNode: React.FC<AutomationTelegramNodeProps> = ({
|
|
|
427
427
|
sx={{
|
|
428
428
|
bgcolor: '#1E1E2E',
|
|
429
429
|
borderRadius: 2,
|
|
430
|
-
border: selected ?
|
|
430
|
+
border: selected ? '2px solid rgba(59, 130, 246, 0.5)' : '1px solid #374151',
|
|
431
431
|
minWidth: 280,
|
|
432
432
|
maxWidth: 320,
|
|
433
433
|
overflow: 'hidden',
|
|
434
|
-
boxShadow: selected
|
|
435
|
-
? `0 0 20px ${operationConfig.color}30`
|
|
436
|
-
: '0 4px 12px rgba(0, 0, 0, 0.3)',
|
|
434
|
+
boxShadow: selected ? '0 0 0 2px rgba(59, 130, 246, 0.5)' : '0 4px 8px rgba(0, 0, 0, 0.3)',
|
|
437
435
|
transition: 'all 0.2s ease',
|
|
436
|
+
...(status === 'Running' && {
|
|
437
|
+
animation: 'pulse-glow 2s ease-in-out infinite',
|
|
438
|
+
'@keyframes pulse-glow': {
|
|
439
|
+
'0%, 100%': {
|
|
440
|
+
boxShadow: '0 0 20px rgba(59, 130, 246, 0.4), 0 0 40px rgba(59, 130, 246, 0.2)',
|
|
441
|
+
borderColor: 'rgba(59, 130, 246, 0.6)',
|
|
442
|
+
},
|
|
443
|
+
'50%': {
|
|
444
|
+
boxShadow: '0 0 30px rgba(59, 130, 246, 0.6), 0 0 60px rgba(59, 130, 246, 0.3)',
|
|
445
|
+
borderColor: 'rgba(59, 130, 246, 0.9)',
|
|
446
|
+
},
|
|
447
|
+
},
|
|
448
|
+
}),
|
|
438
449
|
}}
|
|
439
450
|
onClick={() => setSelectedNode(nodeId || '')}
|
|
440
451
|
>
|
|
@@ -43,6 +43,14 @@ export type {
|
|
|
43
43
|
AutomationInteractionNodeData,
|
|
44
44
|
AutomationInteractionNodeProps,
|
|
45
45
|
} from './AutomationInteractionNode';
|
|
46
|
+
// Email Node
|
|
47
|
+
export { AutomationEmailNode } from './AutomationEmailNode';
|
|
48
|
+
export type {
|
|
49
|
+
EmailOperationType,
|
|
50
|
+
EmailTokenData,
|
|
51
|
+
AutomationEmailNodeData,
|
|
52
|
+
AutomationEmailNodeProps,
|
|
53
|
+
} from './AutomationEmailNode';
|
|
46
54
|
// Monitoring Node
|
|
47
55
|
export { AutomationMonitoringNode } from './AutomationMonitoringNode';
|
|
48
56
|
export type {
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
AutomationSlackNode,
|
|
17
17
|
AutomationTelegramNode,
|
|
18
18
|
AutomationInteractionNode,
|
|
19
|
+
AutomationEmailNode,
|
|
19
20
|
AutomationMonitoringNode,
|
|
20
21
|
} from '../components/automation';
|
|
21
22
|
|
|
@@ -46,6 +47,8 @@ export default {
|
|
|
46
47
|
AutomationTelegramNode,
|
|
47
48
|
// Interaction Node
|
|
48
49
|
AutomationInteractionNode,
|
|
50
|
+
// Email Node
|
|
51
|
+
AutomationEmailNode,
|
|
49
52
|
// Monitoring Node
|
|
50
53
|
AutomationMonitoringNode,
|
|
51
54
|
};
|