@dilipod/ui 0.4.8 → 0.4.9
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/dist/components/workflow-viewer.d.ts +53 -1
- package/dist/components/workflow-viewer.d.ts.map +1 -1
- package/dist/index.js +268 -20
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +269 -21
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/components/workflow-viewer.tsx +335 -19
package/package.json
CHANGED
|
@@ -21,7 +21,11 @@ import {
|
|
|
21
21
|
Copy,
|
|
22
22
|
PencilSimple,
|
|
23
23
|
Eye,
|
|
24
|
-
TreeStructure
|
|
24
|
+
TreeStructure,
|
|
25
|
+
DownloadSimple,
|
|
26
|
+
ArrowSquareOut,
|
|
27
|
+
ClockCounterClockwise,
|
|
28
|
+
ArrowsClockwise,
|
|
25
29
|
} from '@phosphor-icons/react'
|
|
26
30
|
|
|
27
31
|
// Lazy load the flow visualization to avoid SSR issues
|
|
@@ -117,11 +121,25 @@ export interface WorkflowViewerProps {
|
|
|
117
121
|
pushToN8n?: (workerId: string) => Promise<{ success: boolean; error?: string }>
|
|
118
122
|
/** Pull workflow from n8n */
|
|
119
123
|
pullFromN8n?: (workflowDefId: string) => Promise<{ success: boolean; error?: string; descriptionSync?: { needsUpdate: boolean; reason?: string } }>
|
|
124
|
+
/** Export workflow from Sim Studio (backup) */
|
|
125
|
+
exportFromSim?: (workflowDefId: string) => Promise<{ success: boolean; error?: string; backup?: { id: string; version: number; exportedAt: string }; workflow?: { blocksCount: number; edgesCount: number } }>
|
|
126
|
+
/** Get backup history for Sim workflow */
|
|
127
|
+
getSimBackups?: (workflowDefId: string) => Promise<{ success: boolean; error?: string; backups?: Array<{ id: string; version: number; versionLabel?: string; workflowName: string; isDeployed: boolean; exportedAt: string }> }>
|
|
128
|
+
/** Push workflow to Sim Studio */
|
|
129
|
+
pushToSim?: (workflowDefId: string) => Promise<{ success: boolean; error?: string; workflowId?: string }>
|
|
130
|
+
/** Pull workflow from Sim Studio */
|
|
131
|
+
pullFromSim?: (workflowDefId: string) => Promise<{ success: boolean; error?: string; descriptionSync?: { needsUpdate: boolean; reason?: string } }>
|
|
120
132
|
/** Create new workflow */
|
|
121
133
|
createWorkflow?: (data: { agent_id: string; name: string; platform: 'n8n' | 'sim'; n8n_workflow?: N8nWorkflow | null; sim_workflow?: SimWorkflow | null; is_active: boolean; is_global: boolean }) => Promise<{ success: boolean; error?: string; workflow?: { id: string } }>
|
|
122
134
|
/** Load workflow template */
|
|
123
135
|
loadTemplate?: (type: string, workerId: string) => Promise<{ success: boolean; error?: string; workflow?: N8nWorkflow | SimWorkflow }>
|
|
136
|
+
/** Switch workflow platform (e.g., n8n to Sim) */
|
|
137
|
+
switchPlatform?: (workflowDefId: string, targetPlatform: 'n8n' | 'sim') => Promise<{ success: boolean; error?: string; simWorkflowId?: string }>
|
|
124
138
|
}
|
|
139
|
+
/** Sim workflow ID (for Sim Studio integration) */
|
|
140
|
+
simWorkflowId?: string | null
|
|
141
|
+
/** Sim Studio base URL for external links */
|
|
142
|
+
simStudioUrl?: string
|
|
125
143
|
/** Custom loading component */
|
|
126
144
|
loadingComponent?: ReactNode
|
|
127
145
|
/** Format distance function for timestamps */
|
|
@@ -430,8 +448,10 @@ export function WorkflowViewer({
|
|
|
430
448
|
apiHandlers,
|
|
431
449
|
loadingComponent,
|
|
432
450
|
formatDistance = defaultFormatDistance,
|
|
451
|
+
simWorkflowId,
|
|
452
|
+
simStudioUrl,
|
|
433
453
|
}: WorkflowViewerProps) {
|
|
434
|
-
const [viewMode, setViewMode] = useState<'summary' | 'flow' | 'json' | 'edit' | 'create'>('summary')
|
|
454
|
+
const [viewMode, setViewMode] = useState<'summary' | 'flow' | 'json' | 'edit' | 'create' | 'backups'>('summary')
|
|
435
455
|
const [editedJson, setEditedJson] = useState('')
|
|
436
456
|
const [jsonError, setJsonError] = useState<string | null>(null)
|
|
437
457
|
const [message, setMessage] = useState<{ type: 'success' | 'error'; text: string } | null>(null)
|
|
@@ -440,6 +460,12 @@ export function WorkflowViewer({
|
|
|
440
460
|
const [pulling, setPulling] = useState(false)
|
|
441
461
|
const [creating, setCreating] = useState(false)
|
|
442
462
|
const [selectedTemplate, setSelectedTemplate] = useState<WorkflowTemplate>('blank')
|
|
463
|
+
const [exporting, setExporting] = useState(false)
|
|
464
|
+
const [backups, setBackups] = useState<Array<{ id: string; version: number; versionLabel?: string; workflowName: string; isDeployed: boolean; exportedAt: string }>>([])
|
|
465
|
+
const [loadingBackups, setLoadingBackups] = useState(false)
|
|
466
|
+
const [pushingToSim, setPushingToSim] = useState(false)
|
|
467
|
+
const [pullingFromSim, setPullingFromSim] = useState(false)
|
|
468
|
+
const [switchingPlatform, setSwitchingPlatform] = useState(false)
|
|
443
469
|
|
|
444
470
|
const [localPlatform, setLocalPlatform] = useState<'n8n' | 'sim'>(platform)
|
|
445
471
|
const [localIsActive, setLocalIsActive] = useState(isActive ?? true)
|
|
@@ -624,6 +650,133 @@ export function WorkflowViewer({
|
|
|
624
650
|
}
|
|
625
651
|
}
|
|
626
652
|
|
|
653
|
+
async function exportFromSim() {
|
|
654
|
+
if (!workflowDefinitionId || !simWorkflowId || !apiHandlers?.exportFromSim) {
|
|
655
|
+
setMessage({ type: 'error', text: 'Cannot export - no Sim workflow ID or API handler' })
|
|
656
|
+
return
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
setExporting(true)
|
|
660
|
+
setMessage(null)
|
|
661
|
+
|
|
662
|
+
try {
|
|
663
|
+
const result = await apiHandlers.exportFromSim(workflowDefinitionId)
|
|
664
|
+
|
|
665
|
+
if (result.success) {
|
|
666
|
+
const blocksCount = result.workflow?.blocksCount || 0
|
|
667
|
+
const edgesCount = result.workflow?.edgesCount || 0
|
|
668
|
+
setMessage({
|
|
669
|
+
type: 'success',
|
|
670
|
+
text: `Exported from Sim Studio (v${result.backup?.version || 1}, ${blocksCount} blocks, ${edgesCount} edges)`
|
|
671
|
+
})
|
|
672
|
+
// Refresh backups list if viewing
|
|
673
|
+
if (viewMode === 'backups') {
|
|
674
|
+
loadBackups()
|
|
675
|
+
}
|
|
676
|
+
} else {
|
|
677
|
+
setMessage({ type: 'error', text: result.error || 'Failed to export from Sim Studio' })
|
|
678
|
+
}
|
|
679
|
+
} catch {
|
|
680
|
+
setMessage({ type: 'error', text: 'Failed to export from Sim Studio' })
|
|
681
|
+
} finally {
|
|
682
|
+
setExporting(false)
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
async function loadBackups() {
|
|
687
|
+
if (!workflowDefinitionId || !apiHandlers?.getSimBackups) {
|
|
688
|
+
return
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
setLoadingBackups(true)
|
|
692
|
+
|
|
693
|
+
try {
|
|
694
|
+
const result = await apiHandlers.getSimBackups(workflowDefinitionId)
|
|
695
|
+
if (result.success && result.backups) {
|
|
696
|
+
setBackups(result.backups)
|
|
697
|
+
}
|
|
698
|
+
} catch {
|
|
699
|
+
console.error('Failed to load backups')
|
|
700
|
+
} finally {
|
|
701
|
+
setLoadingBackups(false)
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
function showBackups() {
|
|
706
|
+
setViewMode('backups')
|
|
707
|
+
loadBackups()
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
function openInSimStudio() {
|
|
711
|
+
if (simStudioUrl && simWorkflowId) {
|
|
712
|
+
window.open(`${simStudioUrl}/w/${simWorkflowId}`, '_blank')
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
async function pushToSim() {
|
|
717
|
+
if (!workflowDefinitionId || !apiHandlers?.pushToSim) {
|
|
718
|
+
setMessage({ type: 'error', text: 'Cannot push - no workflow definition ID or API handler' })
|
|
719
|
+
return
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
setPushingToSim(true)
|
|
723
|
+
setMessage(null)
|
|
724
|
+
|
|
725
|
+
try {
|
|
726
|
+
const result = await apiHandlers.pushToSim(workflowDefinitionId)
|
|
727
|
+
|
|
728
|
+
if (result.success) {
|
|
729
|
+
setMessage({
|
|
730
|
+
type: 'success',
|
|
731
|
+
text: result.workflowId
|
|
732
|
+
? `Pushed to Sim Studio (workflow: ${result.workflowId})`
|
|
733
|
+
: 'Pushed to Sim Studio successfully'
|
|
734
|
+
})
|
|
735
|
+
// Reload to show updated state
|
|
736
|
+
setTimeout(() => window.location.reload(), 1500)
|
|
737
|
+
} else {
|
|
738
|
+
setMessage({ type: 'error', text: result.error || 'Failed to push to Sim Studio' })
|
|
739
|
+
}
|
|
740
|
+
} catch {
|
|
741
|
+
setMessage({ type: 'error', text: 'Failed to push to Sim Studio' })
|
|
742
|
+
} finally {
|
|
743
|
+
setPushingToSim(false)
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
async function pullFromSim() {
|
|
748
|
+
if (!workflowDefinitionId || !simWorkflowId || !apiHandlers?.pullFromSim) {
|
|
749
|
+
setMessage({ type: 'error', text: 'Cannot pull - no Sim workflow ID or API handler' })
|
|
750
|
+
return
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
setPullingFromSim(true)
|
|
754
|
+
setMessage(null)
|
|
755
|
+
|
|
756
|
+
try {
|
|
757
|
+
const result = await apiHandlers.pullFromSim(workflowDefinitionId)
|
|
758
|
+
|
|
759
|
+
if (result.success) {
|
|
760
|
+
if (result.descriptionSync?.needsUpdate) {
|
|
761
|
+
setMessage({
|
|
762
|
+
type: 'success',
|
|
763
|
+
text: `Pulled from Sim Studio. Note: ${result.descriptionSync.reason || 'Worker description may need updating.'}`
|
|
764
|
+
})
|
|
765
|
+
} else {
|
|
766
|
+
setMessage({ type: 'success', text: 'Pulled from Sim Studio successfully' })
|
|
767
|
+
}
|
|
768
|
+
// Reload to show updated state
|
|
769
|
+
setTimeout(() => window.location.reload(), 1500)
|
|
770
|
+
} else {
|
|
771
|
+
setMessage({ type: 'error', text: result.error || 'Failed to pull from Sim Studio' })
|
|
772
|
+
}
|
|
773
|
+
} catch {
|
|
774
|
+
setMessage({ type: 'error', text: 'Failed to pull from Sim Studio' })
|
|
775
|
+
} finally {
|
|
776
|
+
setPullingFromSim(false)
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
|
|
627
780
|
function startCreating() {
|
|
628
781
|
if (internalWorkerType) {
|
|
629
782
|
setSelectedTemplate(internalWorkerType as WorkflowTemplate)
|
|
@@ -677,6 +830,37 @@ export function WorkflowViewer({
|
|
|
677
830
|
}
|
|
678
831
|
}
|
|
679
832
|
|
|
833
|
+
async function switchPlatform(targetPlatform: 'n8n' | 'sim') {
|
|
834
|
+
if (!workflowDefinitionId || !apiHandlers?.switchPlatform) {
|
|
835
|
+
setMessage({ type: 'error', text: 'Cannot switch platform - no workflow definition ID or API handler' })
|
|
836
|
+
return
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
setSwitchingPlatform(true)
|
|
840
|
+
setMessage(null)
|
|
841
|
+
|
|
842
|
+
try {
|
|
843
|
+
const result = await apiHandlers.switchPlatform(workflowDefinitionId, targetPlatform)
|
|
844
|
+
|
|
845
|
+
if (result.success) {
|
|
846
|
+
setMessage({
|
|
847
|
+
type: 'success',
|
|
848
|
+
text: targetPlatform === 'sim'
|
|
849
|
+
? `Switched to Sim Studio${result.simWorkflowId ? ` (workflow: ${result.simWorkflowId})` : ''}`
|
|
850
|
+
: 'Switched to n8n'
|
|
851
|
+
})
|
|
852
|
+
// Reload to show updated state
|
|
853
|
+
setTimeout(() => window.location.reload(), 1500)
|
|
854
|
+
} else {
|
|
855
|
+
setMessage({ type: 'error', text: result.error || 'Failed to switch platform' })
|
|
856
|
+
}
|
|
857
|
+
} catch {
|
|
858
|
+
setMessage({ type: 'error', text: 'Failed to switch platform' })
|
|
859
|
+
} finally {
|
|
860
|
+
setSwitchingPlatform(false)
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
|
|
680
864
|
async function loadTemplate() {
|
|
681
865
|
if (!workerId || !workerName || !apiHandlers?.loadTemplate) {
|
|
682
866
|
setJsonError('Worker information or API handler required to generate template.')
|
|
@@ -904,16 +1088,22 @@ export function WorkflowViewer({
|
|
|
904
1088
|
{lastSynced && (
|
|
905
1089
|
<span>Synced {formatDistance(new Date(lastSynced), { addSuffix: true })}</span>
|
|
906
1090
|
)}
|
|
907
|
-
|
|
1091
|
+
</div>
|
|
1092
|
+
|
|
1093
|
+
{/* Webhook URL display */}
|
|
1094
|
+
{webhookUrl && (
|
|
1095
|
+
<div className="flex items-center gap-2 p-2 bg-muted/50 rounded border border-border">
|
|
1096
|
+
<WebhooksLogo size={14} className="text-muted-foreground flex-shrink-0" />
|
|
1097
|
+
<code className="text-xs font-mono text-foreground truncate flex-1">{webhookUrl}</code>
|
|
908
1098
|
<button
|
|
909
1099
|
onClick={() => copyToClipboard(webhookUrl, 'Webhook URL copied')}
|
|
910
|
-
className="flex items-center gap-1 hover:text-foreground"
|
|
1100
|
+
className="flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground flex-shrink-0"
|
|
911
1101
|
>
|
|
912
1102
|
<Copy size={12} />
|
|
913
|
-
Copy
|
|
1103
|
+
Copy
|
|
914
1104
|
</button>
|
|
915
|
-
|
|
916
|
-
|
|
1105
|
+
</div>
|
|
1106
|
+
)}
|
|
917
1107
|
|
|
918
1108
|
{(allowPlatformChange || allowStatusChange) && workflowDefinitionId && hasUnsavedChanges && apiHandlers?.saveSettings && (
|
|
919
1109
|
<div className="p-3 bg-amber-50 border border-amber-200 rounded-lg space-y-3">
|
|
@@ -966,8 +1156,9 @@ export function WorkflowViewer({
|
|
|
966
1156
|
</Alert>
|
|
967
1157
|
)}
|
|
968
1158
|
|
|
1159
|
+
{/* n8n sync buttons */}
|
|
969
1160
|
{editable && workerId && platform === 'n8n' && viewMode !== 'edit' && apiHandlers?.pushToN8n && (
|
|
970
|
-
<div className="flex gap-2">
|
|
1161
|
+
<div className="flex flex-wrap gap-2">
|
|
971
1162
|
<Button onClick={pushToN8n} disabled={syncing} variant="primary" size="sm" icon={<CloudArrowUp size={16} />}>
|
|
972
1163
|
{syncing ? 'Pushing...' : 'Push to n8n'}
|
|
973
1164
|
</Button>
|
|
@@ -976,6 +1167,80 @@ export function WorkflowViewer({
|
|
|
976
1167
|
{pulling ? 'Pulling...' : 'Pull from n8n'}
|
|
977
1168
|
</Button>
|
|
978
1169
|
)}
|
|
1170
|
+
{/* Switch to Sim Studio button */}
|
|
1171
|
+
{workflowDefinitionId && apiHandlers?.switchPlatform && (
|
|
1172
|
+
<Button
|
|
1173
|
+
onClick={() => switchPlatform('sim')}
|
|
1174
|
+
disabled={switchingPlatform}
|
|
1175
|
+
variant="outline"
|
|
1176
|
+
size="sm"
|
|
1177
|
+
icon={<ArrowsClockwise size={16} />}
|
|
1178
|
+
className="border-purple-300 text-purple-700 hover:bg-purple-50 hover:border-purple-400"
|
|
1179
|
+
>
|
|
1180
|
+
{switchingPlatform ? 'Switching...' : 'Switch to Sim Studio'}
|
|
1181
|
+
</Button>
|
|
1182
|
+
)}
|
|
1183
|
+
</div>
|
|
1184
|
+
)}
|
|
1185
|
+
|
|
1186
|
+
{/* Sim Studio sync buttons */}
|
|
1187
|
+
{platform === 'sim' && viewMode !== 'edit' && (
|
|
1188
|
+
<div className="flex flex-wrap gap-2">
|
|
1189
|
+
{/* Push/Pull buttons (require workflow definition) */}
|
|
1190
|
+
{editable && workflowDefinitionId && apiHandlers?.pushToSim && (
|
|
1191
|
+
<Button
|
|
1192
|
+
onClick={pushToSim}
|
|
1193
|
+
disabled={pushingToSim}
|
|
1194
|
+
variant="primary"
|
|
1195
|
+
size="sm"
|
|
1196
|
+
icon={<CloudArrowUp size={16} />}
|
|
1197
|
+
>
|
|
1198
|
+
{pushingToSim ? 'Pushing...' : 'Push to Sim'}
|
|
1199
|
+
</Button>
|
|
1200
|
+
)}
|
|
1201
|
+
{simWorkflowId && apiHandlers?.pullFromSim && (
|
|
1202
|
+
<Button
|
|
1203
|
+
onClick={pullFromSim}
|
|
1204
|
+
disabled={pullingFromSim}
|
|
1205
|
+
variant="outline"
|
|
1206
|
+
size="sm"
|
|
1207
|
+
icon={<CloudArrowDown size={16} />}
|
|
1208
|
+
>
|
|
1209
|
+
{pullingFromSim ? 'Pulling...' : 'Pull from Sim'}
|
|
1210
|
+
</Button>
|
|
1211
|
+
)}
|
|
1212
|
+
{/* Export/Backup buttons (require sim workflow id) */}
|
|
1213
|
+
{simWorkflowId && apiHandlers?.exportFromSim && (
|
|
1214
|
+
<Button
|
|
1215
|
+
onClick={exportFromSim}
|
|
1216
|
+
disabled={exporting}
|
|
1217
|
+
variant="outline"
|
|
1218
|
+
size="sm"
|
|
1219
|
+
icon={<DownloadSimple size={16} />}
|
|
1220
|
+
>
|
|
1221
|
+
{exporting ? 'Exporting...' : 'Export Backup'}
|
|
1222
|
+
</Button>
|
|
1223
|
+
)}
|
|
1224
|
+
{simWorkflowId && apiHandlers?.getSimBackups && (
|
|
1225
|
+
<Button
|
|
1226
|
+
onClick={showBackups}
|
|
1227
|
+
variant="outline"
|
|
1228
|
+
size="sm"
|
|
1229
|
+
icon={<ClockCounterClockwise size={16} />}
|
|
1230
|
+
>
|
|
1231
|
+
History
|
|
1232
|
+
</Button>
|
|
1233
|
+
)}
|
|
1234
|
+
{simStudioUrl && simWorkflowId && (
|
|
1235
|
+
<Button
|
|
1236
|
+
onClick={openInSimStudio}
|
|
1237
|
+
variant="outline"
|
|
1238
|
+
size="sm"
|
|
1239
|
+
icon={<ArrowSquareOut size={16} />}
|
|
1240
|
+
>
|
|
1241
|
+
Open in Sim
|
|
1242
|
+
</Button>
|
|
1243
|
+
)}
|
|
979
1244
|
</div>
|
|
980
1245
|
)}
|
|
981
1246
|
|
|
@@ -998,17 +1263,20 @@ export function WorkflowViewer({
|
|
|
998
1263
|
</p>
|
|
999
1264
|
</div>
|
|
1000
1265
|
) : viewMode === 'json' ? (
|
|
1001
|
-
<div className="relative">
|
|
1002
|
-
<
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1266
|
+
<div className="relative rounded-sm overflow-hidden border border-border">
|
|
1267
|
+
<div className="flex items-center justify-between px-4 py-2 bg-[var(--black)] border-b border-gray-800">
|
|
1268
|
+
<span className="text-xs font-medium text-gray-400">JSON</span>
|
|
1269
|
+
<Button
|
|
1270
|
+
onClick={() => copyToClipboard(JSON.stringify(workflow, null, 2), 'JSON copied')}
|
|
1271
|
+
variant="outline"
|
|
1272
|
+
size="sm"
|
|
1273
|
+
className="h-7 bg-transparent border-gray-600 text-gray-300 hover:bg-gray-800 hover:text-white hover:border-gray-500"
|
|
1274
|
+
icon={<Copy size={12} />}
|
|
1275
|
+
>
|
|
1276
|
+
Copy
|
|
1277
|
+
</Button>
|
|
1278
|
+
</div>
|
|
1279
|
+
<pre className="p-4 bg-[var(--black)] text-gray-100 text-xs overflow-auto max-h-[500px] font-mono">
|
|
1012
1280
|
{JSON.stringify(workflow, null, 2)}
|
|
1013
1281
|
</pre>
|
|
1014
1282
|
</div>
|
|
@@ -1018,6 +1286,54 @@ export function WorkflowViewer({
|
|
|
1018
1286
|
<WorkflowFlow workflow={workflow as any} height={380} />
|
|
1019
1287
|
</Suspense>
|
|
1020
1288
|
)
|
|
1289
|
+
) : viewMode === 'backups' ? (
|
|
1290
|
+
<div className="space-y-3">
|
|
1291
|
+
<div className="flex items-center justify-between">
|
|
1292
|
+
<h4 className="text-sm font-medium">Backup History</h4>
|
|
1293
|
+
<Button
|
|
1294
|
+
onClick={() => setViewMode('summary')}
|
|
1295
|
+
variant="outline"
|
|
1296
|
+
size="sm"
|
|
1297
|
+
>
|
|
1298
|
+
Back to Summary
|
|
1299
|
+
</Button>
|
|
1300
|
+
</div>
|
|
1301
|
+
{loadingBackups ? (
|
|
1302
|
+
<div className="py-8 text-center text-muted-foreground">Loading backups...</div>
|
|
1303
|
+
) : backups.length === 0 ? (
|
|
1304
|
+
<div className="py-8 text-center text-muted-foreground">
|
|
1305
|
+
<ClockCounterClockwise size={32} className="mx-auto mb-2 opacity-50" />
|
|
1306
|
+
<p>No backups yet</p>
|
|
1307
|
+
<p className="text-xs mt-1">Click "Export from Sim" to create a backup</p>
|
|
1308
|
+
</div>
|
|
1309
|
+
) : (
|
|
1310
|
+
<div className="space-y-2">
|
|
1311
|
+
{backups.map((backup) => (
|
|
1312
|
+
<div
|
|
1313
|
+
key={backup.id}
|
|
1314
|
+
className="flex items-center justify-between p-3 bg-muted/50 rounded border border-border"
|
|
1315
|
+
>
|
|
1316
|
+
<div className="flex items-center gap-3">
|
|
1317
|
+
<div className="flex items-center justify-center w-8 h-8 rounded bg-primary/10 text-primary text-sm font-semibold">
|
|
1318
|
+
v{backup.version}
|
|
1319
|
+
</div>
|
|
1320
|
+
<div>
|
|
1321
|
+
<p className="text-sm font-medium">
|
|
1322
|
+
{backup.versionLabel || backup.workflowName}
|
|
1323
|
+
</p>
|
|
1324
|
+
<p className="text-xs text-muted-foreground">
|
|
1325
|
+
{formatDistance(new Date(backup.exportedAt), { addSuffix: true })}
|
|
1326
|
+
{backup.isDeployed && (
|
|
1327
|
+
<Badge variant="success" size="sm" className="ml-2">Deployed</Badge>
|
|
1328
|
+
)}
|
|
1329
|
+
</p>
|
|
1330
|
+
</div>
|
|
1331
|
+
</div>
|
|
1332
|
+
</div>
|
|
1333
|
+
))}
|
|
1334
|
+
</div>
|
|
1335
|
+
)}
|
|
1336
|
+
</div>
|
|
1021
1337
|
) : (
|
|
1022
1338
|
platform === 'n8n'
|
|
1023
1339
|
? <N8nWorkflowSummary workflow={workflow as N8nWorkflow} showFlow={false} />
|