@cat-factory/app 0.6.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 (189) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +88 -0
  3. package/app/app.config.ts +8 -0
  4. package/app/app.vue +11 -0
  5. package/app/assets/css/main.css +100 -0
  6. package/app/components/auth/AuthGate.vue +24 -0
  7. package/app/components/auth/LoginScreen.vue +143 -0
  8. package/app/components/auth/UserMenu.vue +39 -0
  9. package/app/components/board/AddTaskModal.vue +444 -0
  10. package/app/components/board/AgentFailureCard.vue +97 -0
  11. package/app/components/board/AgentStopButton.vue +61 -0
  12. package/app/components/board/BoardCanvas.vue +183 -0
  13. package/app/components/board/ContextPicker.vue +367 -0
  14. package/app/components/board/RecurringPipelineModal.vue +219 -0
  15. package/app/components/board/TaskDependencyEdges.vue +132 -0
  16. package/app/components/board/nodes/AgentChip.vue +59 -0
  17. package/app/components/board/nodes/BlockNode.vue +433 -0
  18. package/app/components/board/nodes/DecisionBadge.vue +27 -0
  19. package/app/components/board/nodes/DraggableTask.vue +48 -0
  20. package/app/components/board/nodes/ModuleFrame.vue +97 -0
  21. package/app/components/board/nodes/TaskCard.vue +359 -0
  22. package/app/components/board/nodes/TaskPipelineMini.vue +159 -0
  23. package/app/components/bootstrap/BootstrapModal.vue +665 -0
  24. package/app/components/clarity/ClarityReviewWindow.vue +611 -0
  25. package/app/components/consensus/ConsensusSessionWindow.vue +210 -0
  26. package/app/components/documents/DocumentImportModal.vue +161 -0
  27. package/app/components/documents/DocumentSourceConnectModal.vue +127 -0
  28. package/app/components/documents/SpawnPreviewModal.vue +161 -0
  29. package/app/components/documents/TaskContextDocs.vue +83 -0
  30. package/app/components/focus/BlockFocusView.vue +171 -0
  31. package/app/components/fragments/FragmentLibraryPanel.vue +340 -0
  32. package/app/components/gates/GateResultView.vue +282 -0
  33. package/app/components/github/AddServiceFromRepoModal.vue +354 -0
  34. package/app/components/github/GitHubConnect.vue +183 -0
  35. package/app/components/github/GitHubOnboarding.vue +45 -0
  36. package/app/components/github/GitHubPanel.vue +584 -0
  37. package/app/components/github/RepoTreeBrowser.vue +171 -0
  38. package/app/components/layout/AccountTeamSettings.vue +237 -0
  39. package/app/components/layout/BoardSwitcher.vue +280 -0
  40. package/app/components/layout/BoardToolbar.vue +156 -0
  41. package/app/components/layout/CommandBar.vue +336 -0
  42. package/app/components/layout/GitHubPatBanner.vue +73 -0
  43. package/app/components/layout/NotificationsInbox.vue +175 -0
  44. package/app/components/layout/SideBar.vue +314 -0
  45. package/app/components/layout/SpendWarningBanner.vue +107 -0
  46. package/app/components/observability/StepMetricsBar.vue +102 -0
  47. package/app/components/palettes/AgentPalette.vue +86 -0
  48. package/app/components/panels/AgentStepDetail.vue +737 -0
  49. package/app/components/panels/DecisionModal.vue +71 -0
  50. package/app/components/panels/InspectorPanel.vue +465 -0
  51. package/app/components/panels/ObservabilityPanel.vue +351 -0
  52. package/app/components/panels/StepMetadataCard.vue +253 -0
  53. package/app/components/panels/StepRestartControl.vue +90 -0
  54. package/app/components/panels/StepResultViewHost.vue +40 -0
  55. package/app/components/panels/StepTestReport.vue +84 -0
  56. package/app/components/panels/inspector/ContainerSummary.vue +74 -0
  57. package/app/components/panels/inspector/RecurringScheduleSettings.vue +178 -0
  58. package/app/components/panels/inspector/ServiceFragments.vue +82 -0
  59. package/app/components/panels/inspector/ServiceTestConfig.vue +198 -0
  60. package/app/components/panels/inspector/TaskAgentConfig.vue +81 -0
  61. package/app/components/panels/inspector/TaskDependencies.vue +70 -0
  62. package/app/components/panels/inspector/TaskEstimateBadge.vue +56 -0
  63. package/app/components/panels/inspector/TaskExecution.vue +364 -0
  64. package/app/components/panels/inspector/TaskRunSettings.vue +187 -0
  65. package/app/components/panels/inspector/TaskStructure.vue +96 -0
  66. package/app/components/pipeline/AgentKindIcon.vue +30 -0
  67. package/app/components/pipeline/IterationCapPrompt.vue +70 -0
  68. package/app/components/pipeline/PipelineBuilder.vue +817 -0
  69. package/app/components/pipeline/PipelineProgress.vue +484 -0
  70. package/app/components/providers/ApiKeysSection.vue +273 -0
  71. package/app/components/providers/PersonalCredentialModal.vue +128 -0
  72. package/app/components/providers/PersonalSubscriptionSection.vue +225 -0
  73. package/app/components/providers/VendorCredentialsModal.vue +197 -0
  74. package/app/components/recurring/RecurrenceEditor.vue +124 -0
  75. package/app/components/requirements/RequirementsReviewWindow.vue +620 -0
  76. package/app/components/settings/DatadogPanel.vue +213 -0
  77. package/app/components/settings/LocalModelEndpointsPanel.vue +286 -0
  78. package/app/components/settings/MergeThresholdsPanel.vue +378 -0
  79. package/app/components/settings/ModelDefaultsPanel.vue +250 -0
  80. package/app/components/settings/ServiceFragmentDefaultsPanel.vue +124 -0
  81. package/app/components/settings/WorkspaceSettingsPanel.vue +142 -0
  82. package/app/components/slack/SlackPanel.vue +299 -0
  83. package/app/components/tasks/TaskContextIssues.vue +88 -0
  84. package/app/components/tasks/TaskImportModal.vue +207 -0
  85. package/app/components/tasks/TaskSourceConnectModal.vue +133 -0
  86. package/app/components/testing/TestReportWindow.vue +404 -0
  87. package/app/composables/api/accounts.ts +81 -0
  88. package/app/composables/api/auth.ts +45 -0
  89. package/app/composables/api/board.ts +101 -0
  90. package/app/composables/api/bootstrap.ts +62 -0
  91. package/app/composables/api/context.ts +25 -0
  92. package/app/composables/api/documents.ts +74 -0
  93. package/app/composables/api/execution.ts +127 -0
  94. package/app/composables/api/fragments.ts +71 -0
  95. package/app/composables/api/github.ts +131 -0
  96. package/app/composables/api/models.ts +127 -0
  97. package/app/composables/api/notifications.ts +23 -0
  98. package/app/composables/api/presets.ts +29 -0
  99. package/app/composables/api/recurring.ts +68 -0
  100. package/app/composables/api/releaseHealth.ts +43 -0
  101. package/app/composables/api/reviews.ts +146 -0
  102. package/app/composables/api/slack.ts +54 -0
  103. package/app/composables/api/tasks.ts +72 -0
  104. package/app/composables/api/workspaces.ts +36 -0
  105. package/app/composables/useApi.ts +89 -0
  106. package/app/composables/useBlockDrag.ts +90 -0
  107. package/app/composables/useBlockQueries.ts +154 -0
  108. package/app/composables/useBoardFlow.ts +11 -0
  109. package/app/composables/useContextLinking.ts +65 -0
  110. package/app/composables/useDepLabels.ts +26 -0
  111. package/app/composables/useFrameResize.ts +54 -0
  112. package/app/composables/useResultView.ts +48 -0
  113. package/app/composables/useReviewStage.ts +40 -0
  114. package/app/composables/useSemanticZoom.ts +31 -0
  115. package/app/composables/useStepApproval.ts +233 -0
  116. package/app/composables/useStepProse.ts +78 -0
  117. package/app/composables/useStepTimer.ts +63 -0
  118. package/app/composables/useTaskExpansion.ts +92 -0
  119. package/app/composables/useWorkspaceStream.ts +155 -0
  120. package/app/docs/architecture.md +31 -0
  121. package/app/pages/index.vue +141 -0
  122. package/app/stores/accounts.ts +152 -0
  123. package/app/stores/agentConfig.ts +35 -0
  124. package/app/stores/agentRuns.ts +122 -0
  125. package/app/stores/agents.ts +40 -0
  126. package/app/stores/apiKeys.ts +108 -0
  127. package/app/stores/auth.ts +166 -0
  128. package/app/stores/board.spec.ts +205 -0
  129. package/app/stores/board.ts +286 -0
  130. package/app/stores/bootstrap.ts +97 -0
  131. package/app/stores/clarity.ts +196 -0
  132. package/app/stores/consensus.ts +60 -0
  133. package/app/stores/documents.ts +176 -0
  134. package/app/stores/execution.ts +273 -0
  135. package/app/stores/fragmentLibrary.ts +147 -0
  136. package/app/stores/fragments.ts +40 -0
  137. package/app/stores/github.ts +305 -0
  138. package/app/stores/localModels.ts +51 -0
  139. package/app/stores/mergePresets.ts +58 -0
  140. package/app/stores/modelDefaults.ts +76 -0
  141. package/app/stores/models.ts +134 -0
  142. package/app/stores/notifications.ts +70 -0
  143. package/app/stores/observability.ts +144 -0
  144. package/app/stores/personalSubscriptions.ts +215 -0
  145. package/app/stores/pipelines.ts +327 -0
  146. package/app/stores/recurringPipelines.ts +112 -0
  147. package/app/stores/releaseHealth.ts +75 -0
  148. package/app/stores/requirements.spec.ts +94 -0
  149. package/app/stores/requirements.ts +208 -0
  150. package/app/stores/serviceFragmentDefaults.ts +29 -0
  151. package/app/stores/services.ts +87 -0
  152. package/app/stores/slack.ts +142 -0
  153. package/app/stores/taskExpansion.ts +36 -0
  154. package/app/stores/tasks.spec.ts +71 -0
  155. package/app/stores/tasks.ts +176 -0
  156. package/app/stores/tracker.ts +27 -0
  157. package/app/stores/ui.ts +434 -0
  158. package/app/stores/vendorCredentials.ts +54 -0
  159. package/app/stores/workspace.ts +215 -0
  160. package/app/stores/workspaceSettings.ts +36 -0
  161. package/app/types/accounts.ts +77 -0
  162. package/app/types/bootstrap.ts +83 -0
  163. package/app/types/clarity.ts +59 -0
  164. package/app/types/consensus.ts +91 -0
  165. package/app/types/documents.ts +104 -0
  166. package/app/types/domain.ts +495 -0
  167. package/app/types/execution.ts +383 -0
  168. package/app/types/fragments.ts +72 -0
  169. package/app/types/github.ts +173 -0
  170. package/app/types/localModels.ts +73 -0
  171. package/app/types/merge.ts +71 -0
  172. package/app/types/models.ts +157 -0
  173. package/app/types/notifications.ts +74 -0
  174. package/app/types/recurring.ts +69 -0
  175. package/app/types/releaseHealth.ts +31 -0
  176. package/app/types/requirements.ts +61 -0
  177. package/app/types/services.ts +27 -0
  178. package/app/types/slack.ts +57 -0
  179. package/app/types/tasks.ts +82 -0
  180. package/app/types/tracker.ts +18 -0
  181. package/app/utils/agentOutput.spec.ts +128 -0
  182. package/app/utils/agentOutput.ts +173 -0
  183. package/app/utils/catalog.spec.ts +112 -0
  184. package/app/utils/catalog.ts +455 -0
  185. package/app/utils/dnd.ts +29 -0
  186. package/app/utils/observability.ts +52 -0
  187. package/app/utils/pipelineRender.ts +151 -0
  188. package/nuxt.config.ts +55 -0
  189. package/package.json +45 -0
@@ -0,0 +1,207 @@
1
+ <script setup lang="ts">
2
+ // Import an issue from a connected task source (by key or URL) and review the
3
+ // issues already imported into the workspace. An imported issue can be attached
4
+ // to an existing task for context from the inspector (see TaskContextIssues.vue),
5
+ // or turned directly into a new board task here — pick a container (service frame
6
+ // or module) and "Create task", which seeds a leaf block from the issue and links
7
+ // the issue to it for context.
8
+ import type { Block, TaskSourceKind } from '~/types/domain'
9
+
10
+ const ui = useUiStore()
11
+ const tasks = useTasksStore()
12
+ const board = useBoardStore()
13
+ const toast = useToast()
14
+
15
+ const open = computed({
16
+ get: () => ui.taskImport !== null,
17
+ set: (v: boolean) => {
18
+ if (!v) ui.closeTaskImport()
19
+ },
20
+ })
21
+
22
+ const source = ref<TaskSourceKind | undefined>(undefined)
23
+ const ref_ = ref('')
24
+ const importing = ref(false)
25
+
26
+ const sourceItems = computed(() =>
27
+ tasks.connectedSources.map((s) => ({ label: s.label, value: s.source })),
28
+ )
29
+ const descriptor = computed(() => (source.value ? tasks.descriptorFor(source.value) : undefined))
30
+
31
+ const sourceTasks = computed(() =>
32
+ source.value ? tasks.tasks.filter((t) => t.source === source.value) : [],
33
+ )
34
+
35
+ // Containers a new task can be created in: every service frame and module on the
36
+ // board. Modules are labelled with their parent frame so the choice is unambiguous.
37
+ const containerId = ref<string | undefined>(undefined)
38
+ const containerItems = computed(() =>
39
+ board.blocks
40
+ .filter((b) => b.level === 'frame' || b.level === 'module')
41
+ .map((b) => ({
42
+ label:
43
+ b.level === 'module'
44
+ ? `${board.getBlock(b.parentId ?? '')?.title ?? '?'} › ${b.title}`
45
+ : b.title,
46
+ value: b.id,
47
+ })),
48
+ )
49
+ // The issue currently being turned into a task (its row shows a spinner).
50
+ const creatingId = ref<string | null>(null)
51
+
52
+ watch(open, (isOpen) => {
53
+ if (isOpen) {
54
+ ref_.value = ''
55
+ source.value = ui.taskImport?.source ?? tasks.connectedSources[0]?.source ?? undefined
56
+ containerId.value = containerItems.value[0]?.value
57
+ creatingId.value = null
58
+ tasks.loadTasks().catch(() => {})
59
+ }
60
+ })
61
+
62
+ async function createTask(externalId: string) {
63
+ if (!source.value || !containerId.value) return
64
+ creatingId.value = externalId
65
+ try {
66
+ const { block } = await tasks.createTaskFromIssue(source.value, externalId, containerId.value)
67
+ board.upsert(block as Block)
68
+ toast.add({ title: `Created task "${block.title}"`, icon: 'i-lucide-square-check' })
69
+ } catch (e) {
70
+ toast.add({
71
+ title: 'Could not create task',
72
+ description: e instanceof Error ? e.message : String(e),
73
+ icon: 'i-lucide-triangle-alert',
74
+ color: 'error',
75
+ })
76
+ } finally {
77
+ creatingId.value = null
78
+ }
79
+ }
80
+
81
+ async function doImport() {
82
+ const value = ref_.value.trim()
83
+ if (!value || !source.value) return
84
+ importing.value = true
85
+ try {
86
+ const task = await tasks.importTask(source.value, value)
87
+ ref_.value = ''
88
+ toast.add({ title: `Imported "${task.title}"`, icon: 'i-lucide-file-down' })
89
+ } catch (e) {
90
+ toast.add({
91
+ title: 'Import failed',
92
+ description: e instanceof Error ? e.message : String(e),
93
+ icon: 'i-lucide-triangle-alert',
94
+ color: 'error',
95
+ })
96
+ } finally {
97
+ importing.value = false
98
+ }
99
+ }
100
+ </script>
101
+
102
+ <template>
103
+ <UModal v-model:open="open" title="Import from a task source">
104
+ <template #body>
105
+ <!-- Empty state: no connections -->
106
+ <div v-if="!tasks.anyConnected" class="space-y-3 text-center">
107
+ <UIcon name="i-lucide-plug" class="mx-auto h-8 w-8 text-slate-500" />
108
+ <p class="text-sm text-slate-400">Connect a task source first.</p>
109
+ <div class="flex justify-center gap-2">
110
+ <UButton
111
+ v-for="s in tasks.sources"
112
+ :key="s.source"
113
+ color="primary"
114
+ variant="soft"
115
+ :icon="s.icon"
116
+ @click="ui.openTaskConnect(s.source)"
117
+ >
118
+ Connect {{ s.label }}
119
+ </UButton>
120
+ </div>
121
+ </div>
122
+
123
+ <!-- Main form -->
124
+ <div v-else class="space-y-4">
125
+ <UFormField v-if="sourceItems.length > 1" label="Source">
126
+ <USelect v-model="source" :items="sourceItems" class="w-full" />
127
+ </UFormField>
128
+
129
+ <div class="flex items-end gap-2">
130
+ <UFormField :label="descriptor?.refLabel ?? 'Issue key or URL'" class="flex-1">
131
+ <UInput
132
+ v-model="ref_"
133
+ :placeholder="descriptor?.refPlaceholder"
134
+ class="w-full"
135
+ @keydown.enter="doImport"
136
+ />
137
+ </UFormField>
138
+ <UButton
139
+ color="primary"
140
+ icon="i-lucide-file-down"
141
+ :loading="importing"
142
+ :disabled="!ref_.trim()"
143
+ @click="doImport"
144
+ >
145
+ Import
146
+ </UButton>
147
+ </div>
148
+
149
+ <!-- List of already-imported issues -->
150
+ <div v-if="sourceTasks.length" class="space-y-2">
151
+ <div class="flex items-end justify-between gap-3">
152
+ <h3 class="text-[11px] font-semibold uppercase tracking-wide text-slate-400">
153
+ Imported issues
154
+ </h3>
155
+ <UFormField v-if="containerItems.length" label="Create tasks in" size="xs" class="w-56">
156
+ <USelect
157
+ v-model="containerId"
158
+ :items="containerItems"
159
+ placeholder="Pick a frame or module"
160
+ class="w-full"
161
+ />
162
+ </UFormField>
163
+ </div>
164
+ <div
165
+ v-for="task in sourceTasks"
166
+ :key="`${task.source}:${task.externalId}`"
167
+ class="rounded-lg border border-slate-800 bg-slate-900/60 p-3"
168
+ >
169
+ <div class="flex items-start justify-between gap-2">
170
+ <div class="min-w-0">
171
+ <a
172
+ :href="task.url"
173
+ target="_blank"
174
+ rel="noopener"
175
+ class="truncate text-sm font-medium text-white hover:underline"
176
+ >
177
+ {{ task.externalId }} · {{ task.title }}
178
+ </a>
179
+ <p class="mt-0.5 line-clamp-2 text-xs text-slate-500">{{ task.excerpt }}</p>
180
+ </div>
181
+ <div class="flex shrink-0 items-center gap-2">
182
+ <UBadge color="neutral" variant="soft" size="xs">
183
+ {{ task.status }}
184
+ </UBadge>
185
+ <UButton
186
+ color="primary"
187
+ variant="soft"
188
+ size="xs"
189
+ icon="i-lucide-square-check"
190
+ :loading="creatingId === task.externalId"
191
+ :disabled="!containerId || creatingId !== null"
192
+ @click="createTask(task.externalId)"
193
+ >
194
+ Create task
195
+ </UButton>
196
+ </div>
197
+ </div>
198
+ </div>
199
+ <p v-if="!containerItems.length" class="text-[11px] text-slate-500">
200
+ Add a service frame to the board first to create tasks from issues.
201
+ </p>
202
+ </div>
203
+ <p v-else class="text-center text-xs text-slate-500">No issues imported yet.</p>
204
+ </div>
205
+ </template>
206
+ </UModal>
207
+ </template>
@@ -0,0 +1,133 @@
1
+ <script setup lang="ts">
2
+ // Connect (or disconnect) the workspace to a task source. The form is rendered
3
+ // generically from the source's descriptor (credential fields), so the same
4
+ // modal serves Jira and any future tracker. Secret credentials are write-only —
5
+ // the backend never returns them, so on reload we show "Connected" with empty
6
+ // fields.
7
+ const ui = useUiStore()
8
+ const tasks = useTasksStore()
9
+ const toast = useToast()
10
+
11
+ const source = computed(() => ui.taskConnect?.source ?? null)
12
+ const descriptor = computed(() => (source.value ? tasks.descriptorFor(source.value) : undefined))
13
+ const connection = computed(() => (source.value ? tasks.connectionFor(source.value) : undefined))
14
+ const connected = computed(() => connection.value !== undefined)
15
+
16
+ const open = computed({
17
+ get: () => ui.taskConnect !== null,
18
+ set: (v: boolean) => {
19
+ if (!v) ui.closeTaskConnect()
20
+ },
21
+ })
22
+
23
+ /** One value per credential field, reset whenever the modal (re)opens. */
24
+ const values = ref<Record<string, string>>({})
25
+ const saving = ref(false)
26
+
27
+ watch(open, (isOpen) => {
28
+ if (isOpen) values.value = {}
29
+ })
30
+
31
+ // A source with no credential fields (e.g. GitHub, which reuses the workspace's
32
+ // installed GitHub App) connects with an empty bag — there is nothing to fill in,
33
+ // so the button is enabled as long as it isn't already connected.
34
+ const credentialless = computed(() => (descriptor.value?.credentialFields.length ?? 0) === 0)
35
+
36
+ const canSubmit = computed(() => {
37
+ const fields = descriptor.value?.credentialFields ?? []
38
+ if (credentialless.value) return !connected.value
39
+ return fields.every((f) => (values.value[f.key] ?? '').trim())
40
+ })
41
+
42
+ async function submit() {
43
+ if (!canSubmit.value || !source.value) return
44
+ const credentials: Record<string, string> = {}
45
+ for (const f of descriptor.value!.credentialFields) {
46
+ credentials[f.key] = values.value[f.key]!.trim()
47
+ }
48
+ saving.value = true
49
+ try {
50
+ await tasks.connect(source.value, credentials)
51
+ toast.add({
52
+ title: `${descriptor.value!.label} connected`,
53
+ icon: 'i-lucide-check',
54
+ color: 'success',
55
+ })
56
+ ui.closeTaskConnect()
57
+ } catch (e) {
58
+ toast.add({
59
+ title: 'Could not connect',
60
+ description: e instanceof Error ? e.message : String(e),
61
+ icon: 'i-lucide-triangle-alert',
62
+ color: 'error',
63
+ })
64
+ } finally {
65
+ saving.value = false
66
+ }
67
+ }
68
+
69
+ async function disconnect() {
70
+ if (!source.value) return
71
+ await tasks.disconnect(source.value)
72
+ toast.add({
73
+ title: `${descriptor.value?.label ?? 'Source'} disconnected`,
74
+ icon: 'i-lucide-unplug',
75
+ })
76
+ ui.closeTaskConnect()
77
+ }
78
+ </script>
79
+
80
+ <template>
81
+ <UModal v-model:open="open" :title="descriptor?.label ?? 'Connect source'">
82
+ <template #body>
83
+ <div v-if="descriptor" class="space-y-4">
84
+ <p class="text-sm text-slate-400">
85
+ Connect {{ descriptor.label }} to import issues and attach them to tasks as agent context.
86
+ </p>
87
+
88
+ <p v-if="credentialless" class="text-[11px] text-slate-500">
89
+ This source uses the GitHub App already installed on your workspace — there are no
90
+ credentials to enter. Connecting just enables linking its issues to tasks.
91
+ </p>
92
+
93
+ <div v-else class="space-y-3">
94
+ <UFormField
95
+ v-for="field in descriptor.credentialFields"
96
+ :key="field.key"
97
+ :label="field.label"
98
+ :help="field.help"
99
+ >
100
+ <UInput
101
+ v-model="values[field.key]"
102
+ :type="field.secret ? 'password' : 'text'"
103
+ :placeholder="field.placeholder"
104
+ class="w-full"
105
+ />
106
+ </UFormField>
107
+ </div>
108
+
109
+ <div class="flex items-center justify-between gap-2 pt-1">
110
+ <UButton
111
+ v-if="connected"
112
+ color="error"
113
+ variant="ghost"
114
+ icon="i-lucide-unplug"
115
+ @click="disconnect"
116
+ >
117
+ Disconnect
118
+ </UButton>
119
+ <div v-else />
120
+ <UButton
121
+ color="primary"
122
+ icon="i-lucide-plug"
123
+ :loading="saving"
124
+ :disabled="!canSubmit"
125
+ @click="submit"
126
+ >
127
+ {{ connected ? 'Update connection' : 'Connect' }}
128
+ </UButton>
129
+ </div>
130
+ </div>
131
+ </template>
132
+ </UModal>
133
+ </template>