@revenexx/studio-integrations 0.1.4 → 0.2.0

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.
Files changed (179) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/runtime/components/Integrations/CredentialCreateDialog.d.vue.ts +13 -0
  3. package/dist/runtime/components/Integrations/CredentialCreateDialog.vue +279 -0
  4. package/dist/runtime/components/Integrations/CredentialCreateDialog.vue.d.ts +13 -0
  5. package/dist/runtime/components/Integrations/CredentialDetailDialog.d.vue.ts +20 -0
  6. package/dist/runtime/components/Integrations/CredentialDetailDialog.vue +342 -0
  7. package/dist/runtime/components/Integrations/CredentialDetailDialog.vue.d.ts +20 -0
  8. package/dist/runtime/components/Integrations/CredentialLogo.d.vue.ts +14 -0
  9. package/dist/runtime/components/Integrations/CredentialLogo.vue +37 -0
  10. package/dist/runtime/components/Integrations/CredentialLogo.vue.d.ts +14 -0
  11. package/dist/runtime/components/Integrations/CredentialRevenexxSource.d.vue.ts +12 -0
  12. package/dist/runtime/components/Integrations/CredentialRevenexxSource.vue +364 -0
  13. package/dist/runtime/components/Integrations/CredentialRevenexxSource.vue.d.ts +12 -0
  14. package/dist/runtime/components/Integrations/CredentialTypePicker.d.vue.ts +15 -0
  15. package/dist/runtime/components/Integrations/CredentialTypePicker.vue +71 -0
  16. package/dist/runtime/components/Integrations/CredentialTypePicker.vue.d.ts +15 -0
  17. package/dist/runtime/components/Integrations/DeadLettersPanel.d.vue.ts +5 -0
  18. package/dist/runtime/components/Integrations/DeadLettersPanel.vue +151 -0
  19. package/dist/runtime/components/Integrations/DeadLettersPanel.vue.d.ts +5 -0
  20. package/dist/runtime/components/Integrations/EditorTip.d.vue.ts +17 -0
  21. package/dist/runtime/components/Integrations/EditorTip.vue +46 -0
  22. package/dist/runtime/components/Integrations/EditorTip.vue.d.ts +17 -0
  23. package/dist/runtime/components/Integrations/EventTypeCombobox.d.vue.ts +9 -0
  24. package/dist/runtime/components/Integrations/EventTypeCombobox.vue +64 -0
  25. package/dist/runtime/components/Integrations/EventTypeCombobox.vue.d.ts +9 -0
  26. package/dist/runtime/components/Integrations/ExpressionInput.d.vue.ts +19 -0
  27. package/dist/runtime/components/Integrations/ExpressionInput.vue +236 -0
  28. package/dist/runtime/components/Integrations/ExpressionInput.vue.d.ts +19 -0
  29. package/dist/runtime/components/Integrations/GroupFrameView.d.vue.ts +28 -0
  30. package/dist/runtime/components/Integrations/GroupFrameView.vue +222 -0
  31. package/dist/runtime/components/Integrations/GroupFrameView.vue.d.ts +28 -0
  32. package/dist/runtime/components/Integrations/NodeConfigDynamicField.d.vue.ts +14 -0
  33. package/dist/runtime/components/Integrations/NodeConfigDynamicField.vue +184 -0
  34. package/dist/runtime/components/Integrations/NodeConfigDynamicField.vue.d.ts +14 -0
  35. package/dist/runtime/components/Integrations/NodeConfigDynamicSchema.d.vue.ts +16 -0
  36. package/dist/runtime/components/Integrations/NodeConfigDynamicSchema.vue +102 -0
  37. package/dist/runtime/components/Integrations/NodeConfigDynamicSchema.vue.d.ts +16 -0
  38. package/dist/runtime/components/Integrations/NodeConfigField.d.vue.ts +23 -0
  39. package/dist/runtime/components/Integrations/NodeConfigField.vue +587 -0
  40. package/dist/runtime/components/Integrations/NodeConfigField.vue.d.ts +23 -0
  41. package/dist/runtime/components/Integrations/NodeDynamicOutputsResolver.d.vue.ts +14 -0
  42. package/dist/runtime/components/Integrations/NodeDynamicOutputsResolver.vue +80 -0
  43. package/dist/runtime/components/Integrations/NodeDynamicOutputsResolver.vue.d.ts +14 -0
  44. package/dist/runtime/components/Integrations/NodeInspector.d.vue.ts +22 -3
  45. package/dist/runtime/components/Integrations/NodeInspector.vue +1074 -294
  46. package/dist/runtime/components/Integrations/NodeInspector.vue.d.ts +22 -3
  47. package/dist/runtime/components/Integrations/NodeLogo.d.vue.ts +9 -0
  48. package/dist/runtime/components/Integrations/NodeLogo.vue +38 -0
  49. package/dist/runtime/components/Integrations/NodeLogo.vue.d.ts +9 -0
  50. package/dist/runtime/components/Integrations/NodePalette.d.vue.ts +22 -1
  51. package/dist/runtime/components/Integrations/NodePalette.vue +411 -90
  52. package/dist/runtime/components/Integrations/NodePalette.vue.d.ts +22 -1
  53. package/dist/runtime/components/Integrations/QueryBuilder.d.vue.ts +9 -0
  54. package/dist/runtime/components/Integrations/QueryBuilder.vue +239 -0
  55. package/dist/runtime/components/Integrations/QueryBuilder.vue.d.ts +9 -0
  56. package/dist/runtime/components/Integrations/{RunDetailDialog.d.vue.ts → RunActionsMenu.d.vue.ts} +3 -4
  57. package/dist/runtime/components/Integrations/RunActionsMenu.vue +86 -0
  58. package/dist/runtime/components/Integrations/{RunDetailDialog.vue.d.ts → RunActionsMenu.vue.d.ts} +3 -4
  59. package/dist/runtime/components/Integrations/RunDetailPanel.d.vue.ts +14 -0
  60. package/dist/runtime/components/Integrations/RunDetailPanel.vue +777 -0
  61. package/dist/runtime/components/Integrations/RunDetailPanel.vue.d.ts +14 -0
  62. package/dist/runtime/components/Integrations/RunPayloadInput.d.vue.ts +13 -0
  63. package/dist/runtime/components/Integrations/RunPayloadInput.vue +175 -0
  64. package/dist/runtime/components/Integrations/RunPayloadInput.vue.d.ts +13 -0
  65. package/dist/runtime/components/Integrations/RunResponseModal.d.vue.ts +27 -0
  66. package/dist/runtime/components/Integrations/RunResponseModal.vue +84 -0
  67. package/dist/runtime/components/Integrations/RunResponseModal.vue.d.ts +27 -0
  68. package/dist/runtime/components/Integrations/RunsTable.d.vue.ts +21 -0
  69. package/dist/runtime/components/Integrations/RunsTable.vue +114 -0
  70. package/dist/runtime/components/Integrations/RunsTable.vue.d.ts +21 -0
  71. package/dist/runtime/components/Integrations/SftpAccountImportDialog.d.vue.ts +11 -0
  72. package/dist/runtime/components/Integrations/SftpAccountImportDialog.vue +91 -0
  73. package/dist/runtime/components/Integrations/SftpAccountImportDialog.vue.d.ts +11 -0
  74. package/dist/runtime/components/Integrations/StickyNoteView.d.vue.ts +28 -0
  75. package/dist/runtime/components/Integrations/StickyNoteView.vue +208 -0
  76. package/dist/runtime/components/Integrations/StickyNoteView.vue.d.ts +28 -0
  77. package/dist/runtime/components/Integrations/TemplateFlowPreview.d.vue.ts +29 -0
  78. package/dist/runtime/components/Integrations/TemplateFlowPreview.vue +201 -0
  79. package/dist/runtime/components/Integrations/TemplateFlowPreview.vue.d.ts +29 -0
  80. package/dist/runtime/components/Integrations/WorkflowContextMenu.d.vue.ts +32 -0
  81. package/dist/runtime/components/Integrations/WorkflowContextMenu.vue +138 -0
  82. package/dist/runtime/components/Integrations/WorkflowContextMenu.vue.d.ts +32 -0
  83. package/dist/runtime/components/Integrations/WorkflowCreateDialog.d.vue.ts +9 -0
  84. package/dist/runtime/components/Integrations/WorkflowCreateDialog.vue +322 -0
  85. package/dist/runtime/components/Integrations/WorkflowCreateDialog.vue.d.ts +9 -0
  86. package/dist/runtime/components/Integrations/WorkflowDuplicateDialog.d.vue.ts +18 -0
  87. package/dist/runtime/components/Integrations/WorkflowDuplicateDialog.vue +63 -0
  88. package/dist/runtime/components/Integrations/WorkflowDuplicateDialog.vue.d.ts +18 -0
  89. package/dist/runtime/components/Integrations/WorkflowEdgeView.d.vue.ts +12 -1
  90. package/dist/runtime/components/Integrations/WorkflowEdgeView.vue +68 -6
  91. package/dist/runtime/components/Integrations/WorkflowEdgeView.vue.d.ts +12 -1
  92. package/dist/runtime/components/Integrations/WorkflowEditor.d.vue.ts +8 -1
  93. package/dist/runtime/components/Integrations/WorkflowEditor.vue +1424 -144
  94. package/dist/runtime/components/Integrations/WorkflowEditor.vue.d.ts +8 -1
  95. package/dist/runtime/components/Integrations/WorkflowHistoryPanel.d.vue.ts +12 -0
  96. package/dist/runtime/components/Integrations/WorkflowHistoryPanel.vue +417 -0
  97. package/dist/runtime/components/Integrations/WorkflowHistoryPanel.vue.d.ts +12 -0
  98. package/dist/runtime/components/Integrations/WorkflowIconPicker.d.vue.ts +10 -0
  99. package/dist/runtime/components/Integrations/WorkflowIconPicker.vue +89 -0
  100. package/dist/runtime/components/Integrations/WorkflowIconPicker.vue.d.ts +10 -0
  101. package/dist/runtime/components/Integrations/WorkflowIconView.d.vue.ts +10 -0
  102. package/dist/runtime/components/Integrations/WorkflowIconView.vue +35 -0
  103. package/dist/runtime/components/Integrations/WorkflowIconView.vue.d.ts +10 -0
  104. package/dist/runtime/components/Integrations/WorkflowNodeView.d.vue.ts +5 -1
  105. package/dist/runtime/components/Integrations/WorkflowNodeView.vue +114 -84
  106. package/dist/runtime/components/Integrations/WorkflowNodeView.vue.d.ts +5 -1
  107. package/dist/runtime/components/Integrations/WorkflowRunReplayPanel.d.vue.ts +12 -0
  108. package/dist/runtime/components/Integrations/WorkflowRunReplayPanel.vue +654 -0
  109. package/dist/runtime/components/Integrations/WorkflowRunReplayPanel.vue.d.ts +12 -0
  110. package/dist/runtime/components/Integrations/WorkflowSettingsDialog.d.vue.ts +36 -0
  111. package/dist/runtime/components/Integrations/WorkflowSettingsDialog.vue +136 -0
  112. package/dist/runtime/components/Integrations/WorkflowSettingsDialog.vue.d.ts +36 -0
  113. package/dist/runtime/components/Integrations/WorkflowTagsInput.d.vue.ts +14 -0
  114. package/dist/runtime/components/Integrations/WorkflowTagsInput.vue +63 -0
  115. package/dist/runtime/components/Integrations/WorkflowTagsInput.vue.d.ts +14 -0
  116. package/dist/runtime/components/Integrations/WorkflowTriggerView.vue +35 -15
  117. package/dist/runtime/components/Integrations/WorkflowVariablesDialog.d.vue.ts +13 -0
  118. package/dist/runtime/components/Integrations/WorkflowVariablesDialog.vue +265 -0
  119. package/dist/runtime/components/Integrations/WorkflowVariablesDialog.vue.d.ts +13 -0
  120. package/dist/runtime/components/Integrations/WorkflowsTable.d.vue.ts +4 -1
  121. package/dist/runtime/components/Integrations/WorkflowsTable.vue +122 -39
  122. package/dist/runtime/components/Integrations/WorkflowsTable.vue.d.ts +4 -1
  123. package/dist/runtime/components/Integrations/workflowLayout.d.ts +5 -0
  124. package/dist/runtime/components/Integrations/workflowLayout.js +5 -3
  125. package/dist/runtime/composables/integrationsApi.d.ts +6 -2
  126. package/dist/runtime/composables/integrationsApi.js +5 -4
  127. package/dist/runtime/composables/useCanvasShortcuts.d.ts +37 -0
  128. package/dist/runtime/composables/useCanvasShortcuts.js +62 -0
  129. package/dist/runtime/composables/useIntegrationsDeadLetters.d.ts +22 -0
  130. package/dist/runtime/composables/useIntegrationsDeadLetters.js +50 -0
  131. package/dist/runtime/composables/useIntegrationsNodes.d.ts +4 -0
  132. package/dist/runtime/composables/useIntegrationsNodes.js +6 -3
  133. package/dist/runtime/composables/useIntegrationsRevisions.d.ts +40 -0
  134. package/dist/runtime/composables/useIntegrationsRevisions.js +51 -0
  135. package/dist/runtime/composables/useIntegrationsRuns.d.ts +21 -1
  136. package/dist/runtime/composables/useIntegrationsRuns.js +92 -4
  137. package/dist/runtime/composables/useIntegrationsTemplates.d.ts +1 -0
  138. package/dist/runtime/composables/useIntegrationsTemplates.js +32 -4
  139. package/dist/runtime/composables/useIntegrationsWorkflows.d.ts +1 -0
  140. package/dist/runtime/composables/useIntegrationsWorkflows.js +57 -1
  141. package/dist/runtime/composables/useMiniMapLayout.d.ts +34 -0
  142. package/dist/runtime/composables/useMiniMapLayout.js +119 -0
  143. package/dist/runtime/composables/useNodeConfigResolve.d.ts +43 -0
  144. package/dist/runtime/composables/useNodeConfigResolve.js +123 -0
  145. package/dist/runtime/composables/useNodeDrag.d.ts +22 -0
  146. package/dist/runtime/composables/useNodeDrag.js +10 -0
  147. package/dist/runtime/composables/usePaletteLayout.d.ts +41 -0
  148. package/dist/runtime/composables/usePaletteLayout.js +113 -0
  149. package/dist/runtime/data/expressionEditor.d.ts +22 -0
  150. package/dist/runtime/data/expressionEditor.js +40 -0
  151. package/dist/runtime/data/integrationNodeCatalog.js +8 -8
  152. package/dist/runtime/data/nodeInputData.d.ts +37 -0
  153. package/dist/runtime/data/nodeInputData.js +62 -0
  154. package/dist/runtime/data/nodeRunData.d.ts +56 -0
  155. package/dist/runtime/data/nodeRunData.js +91 -0
  156. package/dist/runtime/data/payloadForm.d.ts +44 -0
  157. package/dist/runtime/data/payloadForm.js +126 -0
  158. package/dist/runtime/data/workflowIconCatalog.d.ts +34 -0
  159. package/dist/runtime/data/workflowIconCatalog.js +116 -0
  160. package/dist/runtime/pages/integrations/audit.vue +25 -35
  161. package/dist/runtime/pages/integrations/credentials.vue +117 -122
  162. package/dist/runtime/pages/integrations/index.vue +133 -36
  163. package/dist/runtime/pages/integrations/runs.vue +125 -75
  164. package/dist/runtime/pages/integrations/schedules.vue +5 -16
  165. package/dist/runtime/pages/integrations/secrets.vue +1 -3
  166. package/dist/runtime/pages/integrations/workflows/[id].vue +599 -194
  167. package/dist/runtime/pages/integrations/workflows/index.vue +10 -8
  168. package/dist/runtime/types/integrations.d.ts +190 -4
  169. package/dist/runtime/utils/credentialState.d.ts +28 -0
  170. package/dist/runtime/utils/credentialState.js +29 -0
  171. package/dist/runtime/utils/localize.d.ts +3 -1
  172. package/dist/runtime/utils/localize.js +1 -2
  173. package/dist/runtime/utils/statusStyle.d.ts +6 -0
  174. package/dist/runtime/utils/statusStyle.js +15 -0
  175. package/package.json +11 -1
  176. package/dist/runtime/components/Integrations/RunDetailDialog.vue +0 -169
  177. package/dist/runtime/pages/integrations/workflows/new.d.vue.ts +0 -2
  178. package/dist/runtime/pages/integrations/workflows/new.vue +0 -153
  179. package/dist/runtime/pages/integrations/workflows/new.vue.d.ts +0 -2
@@ -1,19 +1,40 @@
1
1
  <script setup>
2
- import { computed } from "vue";
3
- import { Pulse as Activity, DangerTriangle as AlertTriangle, ArrowRight, ClockCircle as Clock, Layers, AddCircle as Plus, MagicStick as Sparkles, GraphUp as TrendingUp, Programming as Workflow } from "#solar-icons";
2
+ import { computed, ref, watch } from "vue";
3
+ import TemplateFlowPreview from "../../components/Integrations/TemplateFlowPreview.vue";
4
+ import WorkflowCreateDialog from "../../components/Integrations/WorkflowCreateDialog.vue";
5
+ import { Pulse as Activity, DangerTriangle as AlertTriangle, ArrowRight, Box as Boxes, ClockCircle as Clock, Database, Layers, AddCircle as Plus, Radio, MagicStick as Sparkles, LinkRound as Webhook, Programming as Workflow } from "#solar-icons";
4
6
  import { Badge, Button } from "@revenexx/studio";
5
7
  useHead({ title: "Dashboard \xB7 Integration Studio" });
6
- const { workflows, activeCount: activeWorkflows, load: loadWorkflows } = useIntegrationsWorkflows();
7
- const { runs, runningCount, failedCount, load: loadRuns } = useIntegrationsRuns();
8
- const { popular, load: loadTemplates } = useIntegrationsTemplates();
8
+ const { workflows, activeCount: activeWorkflows, loading: workflowsLoading, load: loadWorkflows } = useIntegrationsWorkflows();
9
+ const { runs, runningCount, failedCount, loading: runsLoading, load: loadRuns } = useIntegrationsRuns();
10
+ const { popular, loading: templatesLoading, load: loadTemplates, ensureDetails } = useIntegrationsTemplates();
11
+ watch(() => popular.value.map((t) => t.slug).join(","), () => {
12
+ if (popular.value.length) void ensureDetails(popular.value.map((t) => t.slug));
13
+ }, { immediate: true });
9
14
  const { activeCount: schedulesActive, load: loadSchedules } = useIntegrationsSchedules();
15
+ const { pendingCount: missedRuns, globalLoading: missedLoading, loadGlobal: loadMissedRuns } = useIntegrationsDeadLetters();
16
+ const kpisLoading = computed(() => workflowsLoading.value && !workflows.value.length || runsLoading.value && !runs.value.length);
17
+ const latestLoading = computed(() => runsLoading.value && !runs.value.length);
18
+ const createOpen = ref(false);
19
+ const NuxtLinkComp = resolveComponent("NuxtLink");
10
20
  onMounted(() => {
11
21
  void loadWorkflows();
12
22
  void loadRuns();
13
23
  void loadTemplates();
14
24
  void loadSchedules();
25
+ void loadMissedRuns();
26
+ });
27
+ const latestPerWorkflow = computed(() => {
28
+ const seen = /* @__PURE__ */ new Set();
29
+ const out = [];
30
+ for (const r of runs.value) {
31
+ if (seen.has(r.workflowId)) continue;
32
+ seen.add(r.workflowId);
33
+ out.push(r);
34
+ if (out.length >= 10) break;
35
+ }
36
+ return out;
15
37
  });
16
- const recentRuns = computed(() => runs.value.slice(0, 6));
17
38
  const failedRecent = computed(() => runs.value.filter((r) => r.status === "failed").slice(0, 3));
18
39
  function formatRelative(iso) {
19
40
  if (!iso) return "\u2014";
@@ -58,8 +79,22 @@ const kpis = computed(() => [
58
79
  sub: "cron triggers active",
59
80
  icon: Clock,
60
81
  tile: "bg-violet-500 text-white"
82
+ },
83
+ {
84
+ label: "Missed runs",
85
+ value: missedRuns.value,
86
+ sub: "trigger firings without a run",
87
+ icon: AlertTriangle,
88
+ tile: missedRuns.value ? "bg-amber-500 text-white" : "bg-slate-300 text-white",
89
+ to: "/integrations/runs?tab=missed"
61
90
  }
62
91
  ]);
92
+ const surfaces = [
93
+ { label: "Import & Export", to: "/integrations/io", icon: Database, hint: "Profiles that move catalogue and entity data in and out." },
94
+ { label: "SFTP Server", to: "/integrations/sftp", icon: Boxes, hint: "Managed SFTP endpoints for file-based exchange." },
95
+ { label: "Event Stream", to: "/integrations/events", icon: Radio, hint: "Live platform events that workflows subscribe to." },
96
+ { label: "Webhooks", to: "/integrations/webhooks", icon: Webhook, hint: "Inbound HTTP endpoints that trigger workflows." }
97
+ ];
63
98
  </script>
64
99
 
65
100
  <template>
@@ -75,25 +110,31 @@ const kpis = computed(() => [
75
110
  Browse templates
76
111
  </Button>
77
112
  </NuxtLink>
78
- <NuxtLink to="/integrations/workflows/new">
79
- <Button class="h-7 text-sm">
80
- <Plus :size="14" />
81
- New workflow
82
- </Button>
83
- </NuxtLink>
113
+ <Button class="h-7 text-sm" @click="createOpen = true">
114
+ <Plus :size="14" />
115
+ New workflow
116
+ </Button>
84
117
  </template>
85
118
  </PageHeader>
86
119
 
87
120
  <div class="p-4 space-y-4">
88
121
  <!-- KPI strip -->
89
122
  <div class="grid gap-2 sm:grid-cols-2 lg:grid-cols-4">
90
- <div v-for="k in kpis" :key="k.label" class="rounded-lg border border-black/[0.06] bg-white p-3">
123
+ <component
124
+ :is="k.to ? NuxtLinkComp : 'div'"
125
+ v-for="k in kpis"
126
+ :key="k.label"
127
+ :to="k.to"
128
+ class="block rounded-lg border border-black/[0.06] bg-white p-3"
129
+ :class="k.to ? 'transition hover:border-black/20' : ''"
130
+ >
91
131
  <div class="flex items-start justify-between gap-2">
92
132
  <div class="min-w-0">
93
133
  <div class="text-mono text-muted-foreground truncate">
94
134
  {{ k.label }}
95
135
  </div>
96
- <div class="text-2xl font-semibold tabular-nums text-[#0B0522]">
136
+ <div v-if="kpisLoading" class="my-1.5 h-6 w-12 rounded bg-black/[0.07] animate-pulse" />
137
+ <div v-else class="text-2xl font-semibold tabular-nums text-[#0B0522]">
97
138
  {{ k.value }}
98
139
  </div>
99
140
  <div class="text-sm text-muted-foreground">
@@ -104,7 +145,7 @@ const kpis = computed(() => [
104
145
  <component :is="k.icon" :size="16" />
105
146
  </span>
106
147
  </div>
107
- </div>
148
+ </component>
108
149
  </div>
109
150
 
110
151
  <!-- Two-column: recent runs + needs attention + popular templates -->
@@ -116,7 +157,7 @@ const kpis = computed(() => [
116
157
  <span class="flex size-6 items-center justify-center rounded-md bg-slate-100 text-slate-500">
117
158
  <Activity :size="12" />
118
159
  </span>
119
- <h3 class="text-sm font-semibold">Recent runs</h3>
160
+ <h3 class="text-sm font-semibold">Latest executions</h3>
120
161
  </div>
121
162
  <NuxtLink to="/integrations/runs" class="text-sm text-muted-foreground hover:text-foreground inline-flex items-center gap-1">
122
163
  View all <ArrowRight :size="11" />
@@ -124,17 +165,17 @@ const kpis = computed(() => [
124
165
  </div>
125
166
  <div>
126
167
  <div
127
- v-for="r in recentRuns"
168
+ v-for="r in latestPerWorkflow"
128
169
  :key="r.id"
129
- class="grid grid-cols-[80px_minmax(0,1fr)_100px_70px] items-center gap-2 border-b border-black/[0.04] px-3 py-2 text-sm last:border-b-0 cursor-pointer hover:bg-muted/30"
130
- @click="$router.push(`/integrations/workflows/${r.workflowId}`)"
170
+ class="grid grid-cols-[90px_minmax(0,1fr)_100px_70px] items-center gap-2 border-b border-black/[0.04] px-3 py-2 text-sm last:border-b-0 cursor-pointer hover:bg-muted/30"
171
+ @click="$router.push({ path: `/integrations/workflows/${r.workflowId}`, query: { run: r.id } })"
131
172
  >
132
- <Badge variant="outline" :class="['text-sm border', statusClass(r.status)]">
173
+ <Badge variant="outline" :class="['border text-xs font-medium uppercase', statusClass(r.status)]">
133
174
  {{ r.status }}
134
175
  </Badge>
135
176
  <div class="truncate">
136
177
  <div class="font-medium text-foreground truncate">{{ r.workflowName }}</div>
137
- <div class="font-mono text-sm text-muted-foreground truncate">{{ r.id }}</div>
178
+ <div class="font-mono text-xs text-muted-foreground truncate">{{ r.temporalRunId ?? "\u2014" }}</div>
138
179
  </div>
139
180
  <div class="text-sm text-muted-foreground tabular-nums truncate">
140
181
  {{ formatRelative(r.startedAt) }}
@@ -143,7 +184,22 @@ const kpis = computed(() => [
143
184
  {{ r.durationMs == null ? "\u2026" : `${(r.durationMs / 1e3).toFixed(2)}s` }}
144
185
  </div>
145
186
  </div>
146
- <div v-if="!recentRuns.length" class="px-3 py-8 text-center text-sm text-muted-foreground">
187
+ <template v-if="latestLoading && !latestPerWorkflow.length">
188
+ <div
189
+ v-for="n in 6"
190
+ :key="`sk-run-${n}`"
191
+ class="grid grid-cols-[90px_minmax(0,1fr)_100px_70px] items-center gap-2 border-b border-black/[0.04] px-3 py-2.5 last:border-b-0"
192
+ >
193
+ <div class="h-5 w-16 rounded bg-black/[0.07] animate-pulse" />
194
+ <div class="space-y-1.5">
195
+ <div class="h-3 w-1/2 rounded bg-black/[0.07] animate-pulse" />
196
+ <div class="h-3 w-2/3 rounded bg-black/[0.05] animate-pulse" />
197
+ </div>
198
+ <div class="h-3 w-14 rounded bg-black/[0.05] animate-pulse" />
199
+ <div class="h-3 w-10 justify-self-end rounded bg-black/[0.05] animate-pulse" />
200
+ </div>
201
+ </template>
202
+ <div v-else-if="!latestPerWorkflow.length" class="px-3 py-8 text-center text-sm text-muted-foreground">
147
203
  No runs yet.
148
204
  </div>
149
205
  </div>
@@ -159,7 +215,21 @@ const kpis = computed(() => [
159
215
  <h3 class="text-sm font-semibold">Needs attention</h3>
160
216
  </div>
161
217
  </div>
162
- <div v-if="failedRecent.length">
218
+ <div v-if="(missedLoading || latestLoading) && !missedRuns && !failedRecent.length" class="space-y-2 p-3">
219
+ <div v-for="n in 3" :key="`sk-att-${n}`" class="h-3 rounded bg-black/[0.07] animate-pulse" :class="n === 1 ? 'w-2/3' : n === 2 ? 'w-1/2' : 'w-3/5'" />
220
+ </div>
221
+ <div v-else-if="missedRuns || failedRecent.length">
222
+ <!-- Missed runs first — trigger firings that never became a run. -->
223
+ <NuxtLink
224
+ v-if="missedRuns"
225
+ to="/integrations/runs?tab=missed"
226
+ class="block space-y-1 border-b border-black/[0.04] px-3 py-2 text-sm hover:bg-muted/30"
227
+ >
228
+ <div class="font-medium">{{ missedRuns }} missed run{{ missedRuns === 1 ? "" : "s" }}</div>
229
+ <div class="text-sm text-amber-700">
230
+ Triggers fired while their workflow couldn't run — review &amp; replay.
231
+ </div>
232
+ </NuxtLink>
163
233
  <div
164
234
  v-for="r in failedRecent"
165
235
  :key="r.id"
@@ -181,6 +251,27 @@ const kpis = computed(() => [
181
251
  </div>
182
252
  </div>
183
253
 
254
+ <!-- Data exchange & events — the studio surfaces beyond workflows. -->
255
+ <div class="grid gap-2 sm:grid-cols-2 lg:grid-cols-4">
256
+ <NuxtLink
257
+ v-for="sfc in surfaces"
258
+ :key="sfc.to"
259
+ :to="sfc.to"
260
+ class="group rounded-lg border border-black/[0.06] bg-white p-3 transition hover:border-black/20"
261
+ >
262
+ <div class="flex items-center gap-2">
263
+ <span class="flex size-6 items-center justify-center rounded-md bg-slate-100 text-slate-500">
264
+ <component :is="sfc.icon" :size="12" />
265
+ </span>
266
+ <span class="min-w-0 flex-1 truncate text-sm font-semibold">{{ sfc.label }}</span>
267
+ <ArrowRight :size="11" class="text-muted-foreground transition-transform group-hover:translate-x-0.5" />
268
+ </div>
269
+ <p class="mt-2 text-sm text-muted-foreground">
270
+ {{ sfc.hint }}
271
+ </p>
272
+ </NuxtLink>
273
+ </div>
274
+
184
275
  <!-- Popular templates -->
185
276
  <div class="rounded-lg border border-black/[0.06] bg-white">
186
277
  <div class="flex items-center justify-between border-b border-black/[0.06] px-3 py-2">
@@ -194,7 +285,14 @@ const kpis = computed(() => [
194
285
  See all <ArrowRight :size="11" />
195
286
  </NuxtLink>
196
287
  </div>
197
- <div v-if="!popular.length" class="px-3 py-6 text-center text-sm text-muted-foreground">
288
+ <div v-if="templatesLoading && !popular.length" class="grid gap-2 p-3 sm:grid-cols-2 lg:grid-cols-4">
289
+ <div v-for="n in 4" :key="`sk-tpl-${n}`" class="rounded-md border border-black/[0.06] bg-white p-3 space-y-2">
290
+ <div class="h-5 w-20 rounded bg-black/[0.07] animate-pulse" />
291
+ <div class="h-3 w-3/4 rounded bg-black/[0.07] animate-pulse" />
292
+ <div class="h-3 w-1/2 rounded bg-black/[0.05] animate-pulse" />
293
+ </div>
294
+ </div>
295
+ <div v-else-if="!popular.length" class="px-3 py-6 text-center text-sm text-muted-foreground">
198
296
  No templates registered yet.
199
297
  </div>
200
298
  <div v-else class="grid gap-2 p-3 sm:grid-cols-2 lg:grid-cols-4">
@@ -202,25 +300,24 @@ const kpis = computed(() => [
202
300
  v-for="tpl in popular"
203
301
  :key="tpl.slug"
204
302
  :to="`/integrations/templates/${tpl.slug}`"
205
- class="block rounded-md border border-black/[0.06] bg-white p-3 transition hover:border-black/20 hover:shadow-sm"
303
+ class="flex flex-col overflow-hidden rounded-md border border-black/[0.06] bg-white transition hover:border-black/20 hover:shadow-sm"
206
304
  >
207
- <div class="flex items-center gap-2 mb-2">
208
- <Badge variant="outline" class="text-sm">
305
+ <div class="relative h-20 shrink-0 border-b border-black/[0.06] bg-slate-50/70 [background-image:radial-gradient(circle,rgba(15,23,42,0.07)_1px,transparent_1px)] [background-size:12px_12px]">
306
+ <TemplateFlowPreview v-if="tpl.detailLoaded" :blob="tpl.blob" :height="80" class="px-1" />
307
+ <span class="absolute left-1.5 top-1.5 inline-flex items-center whitespace-nowrap rounded-full bg-slate-100 px-1.5 py-px text-[11px] font-medium uppercase text-slate-700">
209
308
  {{ tpl.category }}
210
- </Badge>
211
- <span class="flex-1" />
212
- <span class="text-sm text-muted-foreground tabular-nums inline-flex items-center gap-0.5">
213
- <TrendingUp :size="10" />
214
- {{ tpl.installs }}
215
309
  </span>
216
310
  </div>
217
- <div class="text-sm font-medium truncate">{{ tpl.name }}</div>
218
- <p class="mt-1 text-sm text-muted-foreground line-clamp-2">
219
- {{ tpl.description }}
220
- </p>
311
+ <div class="flex min-h-0 flex-1 flex-col p-3">
312
+ <div class="text-sm font-medium truncate">{{ tpl.name }}</div>
313
+ <p class="mt-1 text-sm text-muted-foreground line-clamp-2">
314
+ {{ tpl.description }}
315
+ </p>
316
+ </div>
221
317
  </NuxtLink>
222
318
  </div>
223
319
  </div>
224
320
  </div>
321
+ <WorkflowCreateDialog v-model:open="createOpen" />
225
322
  </IntegrationsLayout>
226
323
  </template>
@@ -1,44 +1,76 @@
1
1
  <script setup>
2
- import { ClockCircle as Clock, Play, LinkRound as Webhook } from "#solar-icons";
3
- import { Badge } from "@revenexx/studio";
4
- import RunDetailDialog from "../../components/Integrations/RunDetailDialog.vue";
2
+ import { RefreshCircle as Loader2, Refresh as ReplayIcon, TrashBinMinimalistic as Trash2 } from "#solar-icons";
3
+ import { Badge, Button } from "@revenexx/studio";
4
+ import { toast } from "vue-sonner";
5
+ import RunsTable from "../../components/Integrations/RunsTable.vue";
5
6
  import { statusClass } from "../../utils/statusStyle";
6
7
  useHead({ title: "Runs \xB7 Integration Studio" });
7
- const { runs, load } = useIntegrationsRuns();
8
+ const route = useRoute();
9
+ const { runs, loading, load } = useIntegrationsRuns();
10
+ const {
11
+ globalItems: missed,
12
+ globalLoading: missedLoading,
13
+ pendingCount,
14
+ loadGlobal,
15
+ replay,
16
+ discard
17
+ } = useIntegrationsDeadLetters();
18
+ const tab = ref(route.query.tab === "missed" ? "missed" : "runs");
19
+ const tabs = computed(() => [
20
+ { key: "runs", label: "Runs" },
21
+ // "Missed runs": trigger firings that never became a run (dead letters).
22
+ { key: "missed", label: "Missed runs", badge: pendingCount.value || null }
23
+ ]);
24
+ const hasActiveRuns = computed(() => runs.value.some((r) => r.status === "running" || r.status === "queued"));
25
+ let pollTimer = null;
8
26
  onMounted(() => {
9
- void load();
27
+ void load(true);
28
+ void loadGlobal(true);
29
+ pollTimer = setInterval(() => {
30
+ if (hasActiveRuns.value) void load(true);
31
+ }, 3e3);
10
32
  });
11
- const detailOpen = ref(false);
12
- const selectedRun = ref(null);
13
- function openRun(r) {
14
- selectedRun.value = r;
15
- detailOpen.value = true;
33
+ onUnmounted(() => {
34
+ if (pollTimer) {
35
+ clearInterval(pollTimer);
36
+ pollTimer = null;
37
+ }
38
+ });
39
+ const busyId = ref(null);
40
+ async function onReplay(dl) {
41
+ busyId.value = dl.id;
42
+ try {
43
+ const res = await replay(dl.id);
44
+ const newId = res && typeof res === "object" && "id" in res ? String(res.id) : null;
45
+ toast.success(newId ? `Replayed \u2014 started run #${newId}` : "Replayed");
46
+ await loadGlobal(true);
47
+ void load(true);
48
+ } catch (e) {
49
+ toast.error(e?.message ?? "Could not replay.");
50
+ } finally {
51
+ busyId.value = null;
52
+ }
16
53
  }
17
- function triggerIcon(type) {
18
- return type === "schedule" ? Clock : type === "webhook" ? Webhook : Play;
54
+ async function onDiscard(dl) {
55
+ busyId.value = dl.id;
56
+ try {
57
+ await discard(dl.id);
58
+ toast.success("Discarded");
59
+ await loadGlobal(true);
60
+ } catch (e) {
61
+ toast.error(e?.message ?? "Could not discard.");
62
+ } finally {
63
+ busyId.value = null;
64
+ }
19
65
  }
20
- function formatRelative(iso) {
66
+ function fmt(iso) {
21
67
  if (!iso) return "\u2014";
22
- const ms = Date.now() - new Date(iso).getTime();
23
- if (ms < 6e4) return `${Math.max(1, Math.floor(ms / 1e3))}s ago`;
24
- if (ms < 36e5) return `${Math.floor(ms / 6e4)}m ago`;
25
- if (ms < 864e5) return `${Math.floor(ms / 36e5)}h ago`;
26
- return `${Math.floor(ms / 864e5)}d ago`;
27
- }
28
- function formatDuration(ms) {
29
- if (ms == null) return "\u2014";
30
- if (ms < 1e3) return `${ms}ms`;
31
- if (ms < 6e4) return `${(ms / 1e3).toFixed(2)}s`;
32
- return `${(ms / 6e4).toFixed(1)}m`;
68
+ try {
69
+ return new Date(iso).toLocaleString();
70
+ } catch {
71
+ return iso;
72
+ }
33
73
  }
34
- const columns = [
35
- { key: "status", label: "Status", width: "100px", sortable: true, sortGetter: (r) => r.status },
36
- { key: "workflow", label: "Workflow", sortable: true, sortGetter: (r) => r.workflowName.toLowerCase(), searchGetter: (r) => `${r.workflowName} ${r.id}` },
37
- { key: "trigger", label: "Trigger", width: "120px", sortable: true, sortGetter: (r) => r.triggerType },
38
- { key: "started", label: "Started", width: "120px", sortable: true, sortGetter: (r) => r.startedAt },
39
- { key: "duration", label: "Duration", width: "100px", sortable: true, sortGetter: (r) => r.durationMs ?? -1 },
40
- { key: "steps", label: "Steps", width: "80px", sortable: true, sortGetter: (r) => r.steps.length }
41
- ];
42
74
  </script>
43
75
 
44
76
  <template>
@@ -48,53 +80,71 @@ const columns = [
48
80
  description="Execution history across every workflow — sourced from Temporal."
49
81
  />
50
82
 
51
- <div class="p-4">
52
- <DataTable
53
- :data="runs"
54
- :columns="columns"
55
- :row-key="(r) => r.id"
56
- :default-sort="{ column: 'started', direction: 'desc' }"
57
- :search="{ placeholder: 'Search runs\u2026' }"
58
- :filter="true"
59
- empty-text="No runs yet — kick off a workflow to populate this view."
60
- @row-click="(r) => openRun(r)"
61
- >
62
- <template #cell-status="{ row: run }">
63
- <Badge variant="outline" :class="['text-sm border', statusClass(run.status)]">
64
- {{ run.status }}
65
- </Badge>
66
- </template>
67
-
68
- <template #cell-workflow="{ row: run }">
69
- <div class="min-w-0">
70
- <div class="font-medium text-sm truncate">{{ run.workflowName }}</div>
71
- <div class="font-mono text-sm text-muted-foreground truncate">{{ run.id }}</div>
72
- </div>
73
- </template>
83
+ <TabBar v-model="tab" :tabs="tabs" class="px-4" />
74
84
 
75
- <template #cell-trigger="{ row: run }">
76
- <div class="flex items-center gap-1 text-sm text-muted-foreground">
77
- <component :is="triggerIcon(run.triggerType)" :size="11" />
78
- {{ run.triggerType }}
79
- </div>
80
- </template>
85
+ <div class="p-4 space-y-4">
86
+ <RunsTable v-if="tab === 'runs'" :runs="runs" :loading="loading" @changed="() => load(true)" />
81
87
 
82
- <template #cell-started="{ row: run }">
83
- <span class="text-sm text-muted-foreground tabular-nums">{{ formatRelative(run.startedAt) }}</span>
84
- </template>
88
+ <!-- Missed runs — trigger firings that never produced a run (the
89
+ workflow wasn't runnable when its schedule/event fired). -->
90
+ <template v-else>
91
+ <div class="flex items-center justify-between">
92
+ <p class="text-sm text-muted-foreground">
93
+ Triggers that fired while their workflow couldn't run (bundle building or failed). Replay starts a fresh run from the captured payload.
94
+ </p>
95
+ <Button variant="outline" size="sm" class="h-7 text-sm" :disabled="missedLoading" @click="() => loadGlobal(true)">
96
+ <Loader2 v-if="missedLoading" :size="12" class="animate-spin" />
97
+ Refresh
98
+ </Button>
99
+ </div>
85
100
 
86
- <template #cell-duration="{ row: run }">
87
- <span class="text-sm tabular-nums">{{ formatDuration(run.durationMs) }}</span>
88
- </template>
101
+ <div v-if="missedLoading && !missed.length"><div class="rounded-lg border border-black/[0.06] bg-white divide-y divide-black/[0.04]">
102
+ <div v-for="n in 4" :key="`sk-${n}`" class="flex items-start gap-3 px-3 py-3">
103
+ <div class="h-5 w-16 shrink-0 rounded bg-black/[0.07] animate-pulse" />
104
+ <div class="min-w-0 flex-1 space-y-2">
105
+ <div class="h-3 w-1/3 rounded bg-black/[0.07] animate-pulse" />
106
+ <div class="h-3 w-2/3 rounded bg-black/[0.05] animate-pulse" />
107
+ </div>
108
+ </div>
109
+ </div>
110
+ </div>
111
+ <div v-else-if="!missed.length" class="rounded-lg border border-black/[0.06] bg-white p-6 text-center text-sm text-muted-foreground">
112
+ No missed runs. Every trigger firing made it into a run.
113
+ </div>
89
114
 
90
- <template #cell-steps="{ row: run }">
91
- <span class="text-sm text-muted-foreground tabular-nums">
92
- {{ run.steps.filter((s) => s.status === "succeeded").length }} / {{ run.steps.length }}
93
- </span>
94
- </template>
95
- </DataTable>
115
+ <div v-else class="rounded-lg border border-black/[0.06] bg-white divide-y divide-black/[0.04]">
116
+ <div
117
+ v-for="dl in missed"
118
+ :key="dl.id"
119
+ class="flex items-start gap-3 px-3 py-2.5"
120
+ >
121
+ <Badge variant="outline" :class="['border text-xs font-medium uppercase shrink-0 mt-0.5', statusClass(dl.status)]">{{ dl.status }}</Badge>
122
+ <div class="min-w-0 flex-1">
123
+ <NuxtLink :to="`/integrations/workflows/${dl.workflow_id}`" class="text-sm font-medium truncate transition-colors hover:text-primary">
124
+ {{ dl.workflowName }}
125
+ </NuxtLink>
126
+ <div class="text-sm break-words">{{ dl.reason || "No reason recorded." }}</div>
127
+ <div class="mt-0.5 flex flex-wrap items-center gap-x-3 gap-y-0.5 text-xs text-muted-foreground font-mono">
128
+ <span>#{{ dl.id }}</span>
129
+ <span v-if="dl.source">{{ dl.source }}</span>
130
+ <span v-if="dl.trigger_handle">↳ {{ dl.trigger_handle }}</span>
131
+ <span>{{ fmt(dl.created_at) }}</span>
132
+ </div>
133
+ </div>
134
+ <div v-if="dl.status === 'pending'" class="flex shrink-0 items-center gap-1">
135
+ <Button variant="outline" size="sm" class="h-7 text-sm" :disabled="busyId === dl.id" @click="onReplay(dl)">
136
+ <Loader2 v-if="busyId === dl.id" :size="12" class="animate-spin" />
137
+ <ReplayIcon v-else :size="12" />
138
+ Replay
139
+ </Button>
140
+ <Button variant="ghost" size="sm" class="h-7 text-sm text-rose-600 hover:bg-rose-500/10" :disabled="busyId === dl.id" @click="onDiscard(dl)">
141
+ <Trash2 :size="12" />
142
+ Discard
143
+ </Button>
144
+ </div>
145
+ </div>
146
+ </div>
147
+ </template>
96
148
  </div>
97
-
98
- <RunDetailDialog v-model:open="detailOpen" :run="selectedRun" />
99
149
  </IntegrationsLayout>
100
150
  </template>
@@ -3,21 +3,10 @@ import { ClockCircle as Clock, Globus as Globe } from "#solar-icons";
3
3
  import { Badge } from "@revenexx/studio";
4
4
  useHead({ title: "Schedules \xB7 Integration Studio" });
5
5
  const router = useRouter();
6
- const { entries, activeCount, load } = useIntegrationsSchedules();
6
+ const { entries, activeCount, loading, load } = useIntegrationsSchedules();
7
7
  onMounted(() => {
8
- void load();
8
+ void load(true);
9
9
  });
10
- function describeCron(cron) {
11
- const parts = cron.split(/\s+/);
12
- if (parts.length !== 5) return cron;
13
- const [m, h, dom, mon, dow] = parts;
14
- if (cron === "0 * * * *") return "every hour";
15
- if (m === "0" && /^\d+$/.test(h ?? "") && dom === "*" && mon === "*" && dow === "*") return `daily at ${h?.padStart(2, "0")}:00`;
16
- if (m === "0" && h === "0" && dom === "*" && mon === "*" && dow === "*") return "daily at midnight";
17
- if (dom !== "*" && mon === "*") return `on day ${dom} of each month`;
18
- if (dow !== "*" && dow !== "?") return `weekly on ${dow}`;
19
- return cron;
20
- }
21
10
  function formatRelative(iso) {
22
11
  if (!iso) return "\u2014";
23
12
  const ms = new Date(iso).getTime() - Date.now();
@@ -57,6 +46,7 @@ const columns = [
57
46
 
58
47
  <div class="p-4">
59
48
  <DataTable
49
+ :loading="loading"
60
50
  :data="entries"
61
51
  :columns="columns"
62
52
  :row-key="(e2) => e2.id"
@@ -73,7 +63,6 @@ const columns = [
73
63
  </div>
74
64
  <div class="min-w-0">
75
65
  <div class="font-medium text-sm truncate">{{ e.workflowName }}</div>
76
- <div class="text-sm text-muted-foreground truncate">{{ describeCron(e.cron) }}</div>
77
66
  </div>
78
67
  </div>
79
68
  </template>
@@ -98,10 +87,10 @@ const columns = [
98
87
  </template>
99
88
 
100
89
  <template #cell-status="{ row: e }">
101
- <Badge v-if="e.active" variant="outline" class="bg-emerald-500/10 border-emerald-500/20 text-emerald-700 text-sm">
90
+ <Badge v-if="e.active" variant="outline" class="bg-emerald-500/10 border-emerald-500/20 text-emerald-700 text-xs font-medium uppercase">
102
91
  live
103
92
  </Badge>
104
- <Badge v-else variant="outline" class="text-sm">
93
+ <Badge v-else variant="outline" class="text-xs font-medium uppercase">
105
94
  paused
106
95
  </Badge>
107
96
  </template>
@@ -166,10 +166,8 @@ const columns = [
166
166
  <div v-if="error" class="mb-3 rounded-lg border border-rose-500/20 bg-rose-500/5 px-3 py-2 text-sm text-rose-700">
167
167
  {{ error }}
168
168
  </div>
169
- <div v-if="loading && !secrets.length" class="flex items-center gap-2 px-1 py-3 text-sm text-muted-foreground">
170
- <Loader2 :size="14" class="animate-spin" /> Loading secrets…
171
- </div>
172
169
  <DataTable
170
+ :loading="loading"
173
171
  :data="secrets"
174
172
  :columns="columns"
175
173
  :row-key="(s2) => s2.key"