@cat-factory/app 0.261.3 → 0.261.5
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/README.md +40 -0
- package/app/components/board/AddTaskModal.vue +10 -6
- package/app/components/board/AgentStopButton.vue +2 -5
- package/app/components/board/CreateInitiativeModal.vue +2 -7
- package/app/components/board/RecurringPipelineModal.vue +10 -6
- package/app/components/bootstrap/BootstrapModal.vue +5 -24
- package/app/components/brainstorm/BrainstormWindow.vue +7 -15
- package/app/components/clarity/ClarityReviewWindow.vue +7 -15
- package/app/components/documents/DocumentImportModal.vue +2 -6
- package/app/components/documents/DocumentSourceConnectModal.vue +3 -12
- package/app/components/documents/DocumentSyncState.vue +2 -7
- package/app/components/documents/DocumentTemplatesModal.vue +4 -19
- package/app/components/documents/SpawnPreviewModal.vue +3 -12
- package/app/components/foundational/FoundationalServiceCatalogList.vue +3 -11
- package/app/components/foundational/FoundationalServiceRegistry.vue +3 -11
- package/app/components/foundational/FoundationalServiceSources.vue +5 -13
- package/app/components/foundational/FoundationalSuppressions.vue +2 -6
- package/app/components/fragments/FragmentLibraryManager.vue +12 -20
- package/app/components/github/AddServiceFromRepoModal.vue +3 -12
- package/app/components/github/GitHubConnect.vue +4 -12
- package/app/components/github/GitHubPanel.vue +9 -17
- package/app/components/github/RepoTreeBrowser.vue +2 -7
- package/app/components/humanTest/HumanTestWindow.vue +2 -7
- package/app/components/initiative/InitiativeTrackerWindow.vue +2 -3
- package/app/components/layout/AccountAuditLog.vue +2 -7
- package/app/components/layout/AccountDeploymentSettings.vue +11 -51
- package/app/components/layout/AccountModelPolicySettings.vue +2 -5
- package/app/components/layout/AccountPlatformAlertSettings.vue +2 -5
- package/app/components/layout/AccountRunCredentialSettings.vue +2 -5
- package/app/components/layout/AccountTeamSettings.vue +9 -18
- package/app/components/layout/BoardSwitcher.vue +6 -14
- package/app/components/layout/BoardToolbar.vue +3 -10
- package/app/components/layout/NotificationsInbox.vue +3 -13
- package/app/components/layout/WorkspaceMembersSettings.vue +6 -15
- package/app/components/notifications/NotificationSettingsPanel.vue +3 -11
- package/app/components/panels/DecisionModal.vue +2 -7
- package/app/components/panels/inspector/RecurringScheduleSettings.vue +4 -20
- package/app/components/panels/inspector/ServiceReleaseHealthConfig.vue +3 -11
- package/app/components/panels/inspector/ServiceTestSecrets.vue +3 -11
- package/app/components/panels/inspector/ServiceValidationConfig.vue +4 -12
- package/app/components/pipeline/PipelineBuilder.vue +4 -7
- package/app/components/providers/ApiKeysSection.vue +4 -15
- package/app/components/providers/PersonalCredentialModal.vue +2 -5
- package/app/components/providers/PersonalSubscriptionSection.vue +3 -10
- package/app/components/providers/VendorCredentialsModal.vue +5 -20
- package/app/components/requirements/RequirementsReviewWindow.vue +11 -19
- package/app/components/sandbox/SandboxPanel.vue +4 -18
- package/app/components/settings/ApiTokensPanel.vue +4 -12
- package/app/components/settings/BudgetSettings.vue +2 -5
- package/app/components/settings/CloudflareHandlerSection.vue +3 -12
- package/app/components/settings/ConsensusGroupsSection.vue +3 -12
- package/app/components/settings/CustomManifestTypeEditor.vue +3 -13
- package/app/components/settings/DefaultProvisionTypeSection.vue +2 -5
- package/app/components/settings/InfraHandlersConfigurator.vue +7 -14
- package/app/components/settings/InfrastructureBackendPicker.vue +2 -7
- package/app/components/settings/IssueTrackerPanel.vue +4 -18
- package/app/components/settings/KubernetesEngineForm.vue +71 -26
- package/app/components/settings/KubernetesEnvironmentForm.vue +35 -8
- package/app/components/settings/LocalContainerPoolSettings.vue +2 -6
- package/app/components/settings/LocalModelEndpointsPanel.vue +3 -11
- package/app/components/settings/ModelConfigurationPanel.vue +13 -16
- package/app/components/settings/ModelPresetHealthModal.vue +3 -13
- package/app/components/settings/ObservabilityConnectionPanel.vue +6 -14
- package/app/components/settings/OpenRouterCatalogPanel.vue +3 -12
- package/app/components/settings/PackageRegistriesPanel.vue +4 -12
- package/app/components/settings/ProviderConnectionTab.vue +5 -13
- package/app/components/settings/RiskPolicyHealthModal.vue +2 -7
- package/app/components/settings/RiskPolicyPanel.vue +5 -13
- package/app/components/settings/ServiceFragmentDefaultsPanel.vue +2 -7
- package/app/components/settings/SharedStacksPanel.vue +10 -20
- package/app/components/settings/TaskTypeSuppressionsPanel.vue +3 -12
- package/app/components/settings/UserSecretsSection.vue +3 -11
- package/app/components/settings/WorkspaceMetadataSettings.vue +2 -6
- package/app/components/settings/WorkspaceSettingsPanel.vue +2 -6
- package/app/components/skills/SkillLibraryManager.vue +5 -13
- package/app/components/slack/SlackPanel.vue +7 -15
- package/app/components/tasks/BugHuntModal.vue +2 -6
- package/app/components/tasks/TaskImportModal.vue +2 -6
- package/app/components/tasks/TaskSourceConnectModal.vue +4 -18
- package/app/composables/useBlockDeletion.ts +2 -6
- package/app/composables/useCopyToClipboard.ts +7 -1
- package/app/composables/usePipelineErrorToast.spec.ts +76 -16
- package/app/composables/usePipelineErrorToast.ts +109 -50
- package/app/stores/board/context.ts +9 -2
- package/app/stores/board/dependencies.ts +3 -13
- package/app/stores/board/mutations.ts +2 -7
- package/app/stores/board/placement.ts +12 -25
- package/app/stores/board/removal.ts +2 -7
- package/app/stores/board.spec.ts +15 -8
- package/app/stores/board.ts +11 -1
- package/app/stores/recurringPipelines.ts +5 -11
- package/app/stores/ui/k3sDeepLink.spec.ts +43 -1
- package/app/stores/ui/modals.ts +24 -0
- package/i18n/locales/de.json +2 -0
- package/i18n/locales/en.json +2 -0
- package/i18n/locales/es.json +2 -0
- package/i18n/locales/fr.json +2 -0
- package/i18n/locales/he.json +2 -0
- package/i18n/locales/it.json +2 -0
- package/i18n/locales/ja.json +2 -0
- package/i18n/locales/pl.json +2 -0
- package/i18n/locales/tr.json +2 -0
- package/i18n/locales/uk.json +2 -0
- package/package.json +2 -2
|
@@ -65,6 +65,9 @@ const form = reactive({
|
|
|
65
65
|
imageTemplate: '',
|
|
66
66
|
urlSource: 'ingressTemplate' as UrlSource,
|
|
67
67
|
hostTemplate: '',
|
|
68
|
+
// The ingress-template port, separate from `servicePort`: each url field belongs to exactly ONE
|
|
69
|
+
// variant, so a value entered for one source can never populate a config built for another.
|
|
70
|
+
ingressPort: '',
|
|
68
71
|
ingressName: '',
|
|
69
72
|
serviceName: '',
|
|
70
73
|
servicePort: '',
|
|
@@ -121,28 +124,34 @@ watch(
|
|
|
121
124
|
form.insecureSkipTlsVerify = k.insecureSkipTlsVerify === true
|
|
122
125
|
form.namespaceTemplate = k.namespaceTemplate ?? ''
|
|
123
126
|
form.imageTemplate = k.imageTemplate ?? ''
|
|
124
|
-
|
|
125
|
-
// different `source` cannot silently populate the form.
|
|
126
|
-
//
|
|
127
|
-
// Typed as present with an on-union `source`, read as neither: both were true when the
|
|
128
|
-
// connect form admitted this config, and the value has been through storage since — which is
|
|
129
|
-
// exactly why the backend re-parses a stored `providerConfig` rather than asserting it, and
|
|
130
|
-
// this form is where an operator REPAIRS one that drifted. An unrecognised source falls back
|
|
131
|
-
// to the form's default, because `buildUrl` has no branch to build a config out of one.
|
|
132
|
-
const url: KubeUrlSource | undefined = k.url
|
|
133
|
-
const source = url?.source
|
|
134
|
-
form.urlSource = isKubernetesUrlSource(source) ? source : 'ingressTemplate'
|
|
135
|
-
form.hostTemplate = url?.source === 'ingressTemplate' ? url.hostTemplate : ''
|
|
136
|
-
form.ingressName = url?.source === 'ingressStatus' ? (url.ingressName ?? '') : ''
|
|
137
|
-
form.serviceName = url?.source === 'serviceStatus' ? url.serviceName : ''
|
|
138
|
-
form.servicePort = url?.source === 'serviceStatus' && url.port != null ? String(url.port) : ''
|
|
139
|
-
form.gatewayName = url?.source === 'gatewayStatus' ? (url.gatewayName ?? '') : ''
|
|
140
|
-
form.httpRouteName = url?.source === 'httpRouteStatus' ? (url.httpRouteName ?? '') : ''
|
|
141
|
-
form.urlScheme = url?.scheme ?? 'default'
|
|
127
|
+
applyStoredUrlSource(k.url)
|
|
142
128
|
},
|
|
143
129
|
{ immediate: true },
|
|
144
130
|
)
|
|
145
131
|
|
|
132
|
+
/**
|
|
133
|
+
* Prefill the URL-derivation fields from a stored config. Each field is read off the ONE variant
|
|
134
|
+
* that carries it, so a field belonging to a different `source` cannot silently populate the form.
|
|
135
|
+
*
|
|
136
|
+
* Typed as present with an on-union `source`, read as neither: both were true when the connect form
|
|
137
|
+
* admitted this config, and the value has been through storage since, which is exactly why the
|
|
138
|
+
* backend re-parses a stored `providerConfig` rather than asserting it, and this form is where an
|
|
139
|
+
* operator REPAIRS one that drifted. An unrecognised source falls back to the form's default,
|
|
140
|
+
* because `buildUrl` has no branch to build a config out of one.
|
|
141
|
+
*/
|
|
142
|
+
function applyStoredUrlSource(url: KubeUrlSource | undefined): void {
|
|
143
|
+
const source = url?.source
|
|
144
|
+
form.urlSource = isKubernetesUrlSource(source) ? source : 'ingressTemplate'
|
|
145
|
+
form.hostTemplate = url?.source === 'ingressTemplate' ? url.hostTemplate : ''
|
|
146
|
+
form.ingressPort = url?.source === 'ingressTemplate' && url.port != null ? String(url.port) : ''
|
|
147
|
+
form.ingressName = url?.source === 'ingressStatus' ? (url.ingressName ?? '') : ''
|
|
148
|
+
form.serviceName = url?.source === 'serviceStatus' ? url.serviceName : ''
|
|
149
|
+
form.servicePort = url?.source === 'serviceStatus' && url.port != null ? String(url.port) : ''
|
|
150
|
+
form.gatewayName = url?.source === 'gatewayStatus' ? (url.gatewayName ?? '') : ''
|
|
151
|
+
form.httpRouteName = url?.source === 'httpRouteStatus' ? (url.httpRouteName ?? '') : ''
|
|
152
|
+
form.urlScheme = url?.scheme ?? 'default'
|
|
153
|
+
}
|
|
154
|
+
|
|
146
155
|
// The local-cluster apiserver address every loopback distro (k3s / k3d / kind / minikube)
|
|
147
156
|
// exposes by default — see `seedForEngine`.
|
|
148
157
|
const LOCAL_K3S_API_SERVER = 'https://127.0.0.1:6443'
|
|
@@ -185,22 +194,33 @@ watch(
|
|
|
185
194
|
if (prefill.insecureSkipTlsVerify !== undefined)
|
|
186
195
|
form.insecureSkipTlsVerify = prefill.insecureSkipTlsVerify
|
|
187
196
|
if (prefill.namespaceTemplate.trim()) form.namespaceTemplate = prefill.namespaceTemplate.trim()
|
|
197
|
+
// An EMPTY host template is meaningful, not a gap the link forgot to fill: the CLI withholds
|
|
198
|
+
// it when it could not establish that the cluster serves an ingress-derived URL, and leaving
|
|
199
|
+
// the required field blank is what stops a URL nothing answers being saved.
|
|
188
200
|
if (prefill.hostTemplate.trim()) {
|
|
189
201
|
form.urlSource = 'ingressTemplate'
|
|
190
202
|
form.hostTemplate = prefill.hostTemplate.trim()
|
|
203
|
+
// Carried alongside the template, never inside it: a local cluster publishing its controller
|
|
204
|
+
// on 18080 needs the port in the URL and NOT in the Ingress host the manifests declare.
|
|
205
|
+
form.ingressPort = prefill.ingressPort.trim()
|
|
191
206
|
}
|
|
207
|
+
if (prefill.urlScheme) form.urlScheme = prefill.urlScheme
|
|
192
208
|
},
|
|
193
209
|
{ immediate: true },
|
|
194
210
|
)
|
|
195
211
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
212
|
+
/** A blank port is valid (the scheme's default); anything typed has to be a real port number. */
|
|
213
|
+
function portValid(raw: string): boolean {
|
|
214
|
+
const trimmed = raw.trim()
|
|
215
|
+
if (!trimmed) return true
|
|
216
|
+
const port = Number(trimmed)
|
|
200
217
|
return Number.isInteger(port) && port >= 1 && port <= 65535
|
|
201
|
-
}
|
|
218
|
+
}
|
|
219
|
+
const servicePortValid = computed(() => portValid(form.servicePort))
|
|
220
|
+
const ingressPortValid = computed(() => portValid(form.ingressPort))
|
|
202
221
|
const urlValid = computed(() => {
|
|
203
|
-
if (form.urlSource === 'ingressTemplate')
|
|
222
|
+
if (form.urlSource === 'ingressTemplate')
|
|
223
|
+
return !!form.hostTemplate.trim() && ingressPortValid.value
|
|
204
224
|
if (form.urlSource === 'serviceStatus') return !!form.serviceName.trim() && servicePortValid.value
|
|
205
225
|
return true // ingressStatus / gatewayStatus / httpRouteStatus have no required field
|
|
206
226
|
})
|
|
@@ -257,8 +277,15 @@ const connectBlockedReason = computed(() => {
|
|
|
257
277
|
function buildUrl(): KubeUrlSource {
|
|
258
278
|
const scheme = form.urlScheme === 'default' ? {} : { scheme: form.urlScheme }
|
|
259
279
|
switch (form.urlSource) {
|
|
260
|
-
case 'ingressTemplate':
|
|
261
|
-
|
|
280
|
+
case 'ingressTemplate': {
|
|
281
|
+
const port = Number(form.ingressPort)
|
|
282
|
+
return {
|
|
283
|
+
source: 'ingressTemplate',
|
|
284
|
+
hostTemplate: form.hostTemplate.trim(),
|
|
285
|
+
...(form.ingressPort.trim() && Number.isInteger(port) ? { port } : {}),
|
|
286
|
+
...scheme,
|
|
287
|
+
}
|
|
288
|
+
}
|
|
262
289
|
case 'ingressStatus': {
|
|
263
290
|
const ingressName = form.ingressName.trim()
|
|
264
291
|
return { source: 'ingressStatus', ...(ingressName ? { ingressName } : {}), ...scheme }
|
|
@@ -456,6 +483,24 @@ async function copyAutoSetupCommand() {
|
|
|
456
483
|
/>
|
|
457
484
|
</UFormField>
|
|
458
485
|
|
|
486
|
+
<!-- The host port the controller answers on, when it is not the scheme's default. Separate from
|
|
487
|
+
the template on purpose: the rendered template is also the Ingress `host` the manifests
|
|
488
|
+
declare, and Kubernetes rejects a `host` with a port in it. -->
|
|
489
|
+
<UFormField
|
|
490
|
+
v-if="form.urlSource === 'ingressTemplate'"
|
|
491
|
+
:label="optional(t('settings.infrastructure.kubernetesEngine.port'))"
|
|
492
|
+
:help="t('settings.infrastructure.kubernetesEngine.ingressPortHelp')"
|
|
493
|
+
>
|
|
494
|
+
<UInput
|
|
495
|
+
v-model="form.ingressPort"
|
|
496
|
+
type="number"
|
|
497
|
+
:min="1"
|
|
498
|
+
:max="65535"
|
|
499
|
+
class="font-mono"
|
|
500
|
+
placeholder="80"
|
|
501
|
+
/>
|
|
502
|
+
</UFormField>
|
|
503
|
+
|
|
459
504
|
<UFormField
|
|
460
505
|
v-if="form.urlSource === 'ingressStatus'"
|
|
461
506
|
:label="optional(t('settings.infrastructure.kubernetesEngine.ingressName'))"
|
|
@@ -46,6 +46,9 @@ const form = reactive({
|
|
|
46
46
|
// url derivation
|
|
47
47
|
urlSource: 'ingressTemplate' as 'ingressTemplate' | 'ingressStatus' | 'serviceStatus',
|
|
48
48
|
hostTemplate: '',
|
|
49
|
+
// The ingress-template port, its own field because the rendered template is also the Ingress
|
|
50
|
+
// `host` a service's manifests declare, and Kubernetes rejects a `host` carrying a port.
|
|
51
|
+
ingressPort: '',
|
|
49
52
|
ingressName: '',
|
|
50
53
|
serviceName: '',
|
|
51
54
|
servicePort: '',
|
|
@@ -111,6 +114,7 @@ function applyUrl(k: Record<string, unknown>): void {
|
|
|
111
114
|
if (url?.source === 'ingressTemplate') {
|
|
112
115
|
form.urlSource = 'ingressTemplate'
|
|
113
116
|
form.hostTemplate = readString(url.hostTemplate)
|
|
117
|
+
form.ingressPort = typeof url.port === 'number' ? String(url.port) : ''
|
|
114
118
|
} else if (url?.source === 'ingressStatus') {
|
|
115
119
|
form.urlSource = 'ingressStatus'
|
|
116
120
|
form.ingressName = readString(url.ingressName)
|
|
@@ -154,16 +158,20 @@ const manifestSourceValid = computed(() =>
|
|
|
154
158
|
? repoShapeValid.value && !!form.manifestPath.trim()
|
|
155
159
|
: !!form.manifestPath.trim(),
|
|
156
160
|
)
|
|
157
|
-
// serviceStatus.port
|
|
158
|
-
//
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
161
|
+
// Both `ingressTemplate.port` and `serviceStatus.port` are optional integers 1..65535
|
|
162
|
+
// (kubernetesUrlSourceSchema). Validate here so a decimal isn't silently dropped and an
|
|
163
|
+
// out-of-range value isn't sent then 422'd.
|
|
164
|
+
function portValid(raw: string): boolean {
|
|
165
|
+
const trimmed = raw.trim()
|
|
166
|
+
if (!trimmed) return true
|
|
167
|
+
const port = Number(trimmed)
|
|
163
168
|
return Number.isInteger(port) && port >= 1 && port <= 65535
|
|
164
|
-
}
|
|
169
|
+
}
|
|
170
|
+
const servicePortValid = computed(() => portValid(form.servicePort))
|
|
171
|
+
const ingressPortValid = computed(() => portValid(form.ingressPort))
|
|
165
172
|
const urlValid = computed(() => {
|
|
166
|
-
if (form.urlSource === 'ingressTemplate')
|
|
173
|
+
if (form.urlSource === 'ingressTemplate')
|
|
174
|
+
return !!form.hostTemplate.trim() && ingressPortValid.value
|
|
167
175
|
if (form.urlSource === 'serviceStatus') return !!form.serviceName.trim() && servicePortValid.value
|
|
168
176
|
return true // ingressStatus has no required field
|
|
169
177
|
})
|
|
@@ -219,6 +227,8 @@ function buildUrl(): Record<string, unknown> {
|
|
|
219
227
|
const url: Record<string, unknown> = { source: form.urlSource }
|
|
220
228
|
if (form.urlSource === 'ingressTemplate') {
|
|
221
229
|
url.hostTemplate = form.hostTemplate.trim()
|
|
230
|
+
const port = Number(form.ingressPort)
|
|
231
|
+
if (form.ingressPort.trim() && Number.isInteger(port)) url.port = port
|
|
222
232
|
} else if (form.urlSource === 'ingressStatus') {
|
|
223
233
|
if (form.ingressName.trim()) url.ingressName = form.ingressName.trim()
|
|
224
234
|
} else {
|
|
@@ -338,6 +348,23 @@ function optional(label: string): string {
|
|
|
338
348
|
/>
|
|
339
349
|
</UFormField>
|
|
340
350
|
|
|
351
|
+
<!-- The host port the controller answers on, when it is not the scheme's default. Kept out of
|
|
352
|
+
the template because that value is also the Ingress `host` the manifests declare. -->
|
|
353
|
+
<UFormField
|
|
354
|
+
v-if="form.urlSource === 'ingressTemplate'"
|
|
355
|
+
:label="optional(t('settings.providerConnection.kubernetesEnv.port'))"
|
|
356
|
+
:help="t('settings.providerConnection.kubernetesEnv.ingressPortHelp')"
|
|
357
|
+
>
|
|
358
|
+
<UInput
|
|
359
|
+
v-model="form.ingressPort"
|
|
360
|
+
type="number"
|
|
361
|
+
:min="1"
|
|
362
|
+
:max="65535"
|
|
363
|
+
class="font-mono"
|
|
364
|
+
placeholder="80"
|
|
365
|
+
/>
|
|
366
|
+
</UFormField>
|
|
367
|
+
|
|
341
368
|
<UFormField
|
|
342
369
|
v-if="form.urlSource === 'ingressStatus'"
|
|
343
370
|
:label="optional(t('settings.providerConnection.kubernetesEnv.ingressName'))"
|
|
@@ -14,6 +14,7 @@ import { reactive, ref, watch } from 'vue'
|
|
|
14
14
|
const { t } = useI18n()
|
|
15
15
|
const store = useLocalSettingsStore()
|
|
16
16
|
const toast = useToast()
|
|
17
|
+
const { present } = usePipelineErrorToast()
|
|
17
18
|
|
|
18
19
|
const saving = ref(false)
|
|
19
20
|
|
|
@@ -65,12 +66,7 @@ async function save() {
|
|
|
65
66
|
color: 'success',
|
|
66
67
|
})
|
|
67
68
|
} catch (e) {
|
|
68
|
-
toast.
|
|
69
|
-
title: t('settings.localMode.toast.saveFailed'),
|
|
70
|
-
description: e instanceof Error ? e.message : String(e),
|
|
71
|
-
icon: 'i-lucide-triangle-alert',
|
|
72
|
-
color: 'error',
|
|
73
|
-
})
|
|
69
|
+
present(e, 'settings.localMode.toast.saveFailed')
|
|
74
70
|
} finally {
|
|
75
71
|
saving.value = false
|
|
76
72
|
}
|
|
@@ -19,6 +19,7 @@ const { t } = useI18n()
|
|
|
19
19
|
const ui = useUiStore()
|
|
20
20
|
const store = useLocalModelsStore()
|
|
21
21
|
const toast = useToast()
|
|
22
|
+
const { present } = usePipelineErrorToast()
|
|
22
23
|
const { confirm } = useConfirm()
|
|
23
24
|
|
|
24
25
|
const open = computed({
|
|
@@ -96,15 +97,6 @@ watch(provider, (p) => {
|
|
|
96
97
|
tested.value = false
|
|
97
98
|
})
|
|
98
99
|
|
|
99
|
-
function notifyError(title: string, e: unknown) {
|
|
100
|
-
toast.add({
|
|
101
|
-
title,
|
|
102
|
-
description: e instanceof Error ? e.message : String(e),
|
|
103
|
-
icon: 'i-lucide-triangle-alert',
|
|
104
|
-
color: 'error',
|
|
105
|
-
})
|
|
106
|
-
}
|
|
107
|
-
|
|
108
100
|
async function test() {
|
|
109
101
|
if (!baseUrl.value.trim()) return
|
|
110
102
|
testing.value = true
|
|
@@ -166,7 +158,7 @@ async function save() {
|
|
|
166
158
|
color: 'success',
|
|
167
159
|
})
|
|
168
160
|
} catch (e) {
|
|
169
|
-
|
|
161
|
+
present(e, 'settings.localModelEndpoints.toast.saveFailed')
|
|
170
162
|
} finally {
|
|
171
163
|
busy.value = false
|
|
172
164
|
}
|
|
@@ -195,7 +187,7 @@ async function remove(p: LocalRunner) {
|
|
|
195
187
|
}
|
|
196
188
|
toast.add({ title: t('settings.localModelEndpoints.toast.removed'), icon: 'i-lucide-check' })
|
|
197
189
|
} catch (e) {
|
|
198
|
-
|
|
190
|
+
present(e, 'settings.localModelEndpoints.toast.removeFailed')
|
|
199
191
|
} finally {
|
|
200
192
|
busy.value = false
|
|
201
193
|
}
|
|
@@ -32,6 +32,7 @@ const agents = useAgentsStore()
|
|
|
32
32
|
const agentTier = useAgentTierStore()
|
|
33
33
|
const creds = useVendorCredentialsStore()
|
|
34
34
|
const workspace = useWorkspaceStore()
|
|
35
|
+
const { present } = usePipelineErrorToast()
|
|
35
36
|
const toast = useToast()
|
|
36
37
|
const { confirm } = useConfirm()
|
|
37
38
|
|
|
@@ -180,7 +181,7 @@ async function setDefault(p: ModelPreset) {
|
|
|
180
181
|
try {
|
|
181
182
|
await presets.update(p.id, { isDefault: true })
|
|
182
183
|
} catch (e) {
|
|
183
|
-
|
|
184
|
+
present(e, 'settings.modelConfiguration.toast.setDefaultFailed')
|
|
184
185
|
} finally {
|
|
185
186
|
busy.value = false
|
|
186
187
|
}
|
|
@@ -199,7 +200,7 @@ async function remove(p: ModelPreset) {
|
|
|
199
200
|
try {
|
|
200
201
|
await presets.remove(p.id)
|
|
201
202
|
} catch (e) {
|
|
202
|
-
|
|
203
|
+
present(e, 'settings.modelConfiguration.toast.deleteFailed')
|
|
203
204
|
} finally {
|
|
204
205
|
busy.value = false
|
|
205
206
|
}
|
|
@@ -251,10 +252,15 @@ async function save() {
|
|
|
251
252
|
const e = editor.value
|
|
252
253
|
if (!e) return
|
|
253
254
|
if (!e.name.trim()) {
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
255
|
+
// NOT through `present`: that funnel classifies a BACKEND failure, and a synthesized local
|
|
256
|
+
// `Error` carries no envelope and no status, so it lands on the network-fault description and
|
|
257
|
+
// tells the user the server could not be reached about a check that never left the browser.
|
|
258
|
+
toast.add({
|
|
259
|
+
title: t('settings.modelConfiguration.toast.nameRequiredTitle'),
|
|
260
|
+
description: t('settings.modelConfiguration.toast.nameRequiredBody'),
|
|
261
|
+
color: 'warning',
|
|
262
|
+
icon: 'i-lucide-triangle-alert',
|
|
263
|
+
})
|
|
258
264
|
return
|
|
259
265
|
}
|
|
260
266
|
busy.value = true
|
|
@@ -280,20 +286,11 @@ async function save() {
|
|
|
280
286
|
}
|
|
281
287
|
editor.value = null
|
|
282
288
|
} catch (err) {
|
|
283
|
-
|
|
289
|
+
present(err, 'settings.modelConfiguration.toast.saveFailed')
|
|
284
290
|
} finally {
|
|
285
291
|
busy.value = false
|
|
286
292
|
}
|
|
287
293
|
}
|
|
288
|
-
|
|
289
|
-
function fail(title: string, e: unknown) {
|
|
290
|
-
toast.add({
|
|
291
|
-
title,
|
|
292
|
-
description: e instanceof Error ? e.message : String(e),
|
|
293
|
-
icon: 'i-lucide-triangle-alert',
|
|
294
|
-
color: 'error',
|
|
295
|
-
})
|
|
296
|
-
}
|
|
297
294
|
</script>
|
|
298
295
|
|
|
299
296
|
<template>
|
|
@@ -9,7 +9,7 @@ const { t } = useI18n()
|
|
|
9
9
|
const ui = useUiStore()
|
|
10
10
|
const presets = useModelPresetsStore()
|
|
11
11
|
const { newPresets, outdated, hasIssues } = useModelPresetHealth()
|
|
12
|
-
const
|
|
12
|
+
const { present } = usePipelineErrorToast()
|
|
13
13
|
|
|
14
14
|
const open = computed({
|
|
15
15
|
get: () => ui.modelPresetHealthOpen,
|
|
@@ -28,12 +28,7 @@ async function reseed(id: string) {
|
|
|
28
28
|
try {
|
|
29
29
|
await presets.reseed(id)
|
|
30
30
|
} catch (e) {
|
|
31
|
-
toast.
|
|
32
|
-
title: t('modelPreset.health.toast.reseedFailed'),
|
|
33
|
-
description: e instanceof Error ? e.message : String(e),
|
|
34
|
-
icon: 'i-lucide-triangle-alert',
|
|
35
|
-
color: 'error',
|
|
36
|
-
})
|
|
31
|
+
present(e, 'modelPreset.health.toast.reseedFailed')
|
|
37
32
|
} finally {
|
|
38
33
|
const next = new Set(busy.value)
|
|
39
34
|
next.delete(id)
|
|
@@ -48,12 +43,7 @@ async function reseedAll() {
|
|
|
48
43
|
try {
|
|
49
44
|
await presets.reseedMany(ids)
|
|
50
45
|
} catch (e) {
|
|
51
|
-
toast.
|
|
52
|
-
title: t('modelPreset.health.toast.reseedFailed'),
|
|
53
|
-
description: e instanceof Error ? e.message : String(e),
|
|
54
|
-
icon: 'i-lucide-triangle-alert',
|
|
55
|
-
color: 'error',
|
|
56
|
-
})
|
|
46
|
+
present(e, 'modelPreset.health.toast.reseedFailed')
|
|
57
47
|
} finally {
|
|
58
48
|
const next = new Set(busy.value)
|
|
59
49
|
for (const id of ids) next.delete(id)
|
|
@@ -13,6 +13,7 @@ const { t } = useI18n()
|
|
|
13
13
|
const ui = useUiStore()
|
|
14
14
|
const store = useReleaseHealthStore()
|
|
15
15
|
const toast = useToast()
|
|
16
|
+
const { present } = usePipelineErrorToast()
|
|
16
17
|
const { confirmAction, toastDone } = useConfirmAction()
|
|
17
18
|
|
|
18
19
|
const open = computed({
|
|
@@ -36,15 +37,6 @@ const pagerDuty = reactive({ apiToken: '', fromEmail: '' })
|
|
|
36
37
|
const incidentIo = reactive({ apiKey: '' })
|
|
37
38
|
const incidentBusy = ref(false)
|
|
38
39
|
|
|
39
|
-
function notifyError(title: string, e: unknown) {
|
|
40
|
-
toast.add({
|
|
41
|
-
title,
|
|
42
|
-
description: e instanceof Error ? e.message : String(e),
|
|
43
|
-
icon: 'i-lucide-triangle-alert',
|
|
44
|
-
color: 'error',
|
|
45
|
-
})
|
|
46
|
-
}
|
|
47
|
-
|
|
48
40
|
watch(
|
|
49
41
|
open,
|
|
50
42
|
async (isOpen) => {
|
|
@@ -56,7 +48,7 @@ watch(
|
|
|
56
48
|
if (site) datadog.site = site
|
|
57
49
|
await store.loadIncident()
|
|
58
50
|
} catch (e) {
|
|
59
|
-
|
|
51
|
+
present(e, 'settings.observabilityConnection.toast.loadFailed')
|
|
60
52
|
}
|
|
61
53
|
},
|
|
62
54
|
{ immediate: true },
|
|
@@ -90,7 +82,7 @@ async function saveIncident() {
|
|
|
90
82
|
color: 'success',
|
|
91
83
|
})
|
|
92
84
|
} catch (e) {
|
|
93
|
-
|
|
85
|
+
present(e, 'settings.observabilityConnection.toast.incidentSaveFailed')
|
|
94
86
|
} finally {
|
|
95
87
|
incidentBusy.value = false
|
|
96
88
|
}
|
|
@@ -104,7 +96,7 @@ async function disconnectIncident() {
|
|
|
104
96
|
await store.removeIncident()
|
|
105
97
|
toastDone('disconnect', noun)
|
|
106
98
|
} catch (e) {
|
|
107
|
-
|
|
99
|
+
present(e, 'settings.observabilityConnection.toast.incidentDisconnectFailed')
|
|
108
100
|
} finally {
|
|
109
101
|
incidentBusy.value = false
|
|
110
102
|
}
|
|
@@ -125,7 +117,7 @@ async function saveConnection() {
|
|
|
125
117
|
color: 'success',
|
|
126
118
|
})
|
|
127
119
|
} catch (e) {
|
|
128
|
-
|
|
120
|
+
present(e, 'settings.observabilityConnection.toast.connectFailed')
|
|
129
121
|
} finally {
|
|
130
122
|
busy.value = false
|
|
131
123
|
}
|
|
@@ -139,7 +131,7 @@ async function disconnect() {
|
|
|
139
131
|
await store.removeConnection()
|
|
140
132
|
toastDone('disconnect', noun)
|
|
141
133
|
} catch (e) {
|
|
142
|
-
|
|
134
|
+
present(e, 'settings.observabilityConnection.toast.disconnectFailed')
|
|
143
135
|
} finally {
|
|
144
136
|
busy.value = false
|
|
145
137
|
}
|
|
@@ -19,6 +19,7 @@ const store = useOpenRouterStore()
|
|
|
19
19
|
const apiKeys = useApiKeysStore()
|
|
20
20
|
const models = useModelsStore()
|
|
21
21
|
const toast = useToast()
|
|
22
|
+
const { present } = usePipelineErrorToast()
|
|
22
23
|
|
|
23
24
|
const open = computed({
|
|
24
25
|
get: () => ui.openRouterOpen,
|
|
@@ -158,12 +159,7 @@ async function connectKey() {
|
|
|
158
159
|
color: 'success',
|
|
159
160
|
})
|
|
160
161
|
} catch (e) {
|
|
161
|
-
toast.
|
|
162
|
-
title: t('settings.openRouterCatalog.toast.connectFailed'),
|
|
163
|
-
description: e instanceof Error ? e.message : String(e),
|
|
164
|
-
icon: 'i-lucide-triangle-alert',
|
|
165
|
-
color: 'error',
|
|
166
|
-
})
|
|
162
|
+
present(e, 'settings.openRouterCatalog.toast.connectFailed')
|
|
167
163
|
} finally {
|
|
168
164
|
connectingKey.value = false
|
|
169
165
|
}
|
|
@@ -200,12 +196,7 @@ async function save() {
|
|
|
200
196
|
color: 'success',
|
|
201
197
|
})
|
|
202
198
|
} catch (e) {
|
|
203
|
-
toast.
|
|
204
|
-
title: t('settings.openRouterCatalog.toast.saveFailed'),
|
|
205
|
-
description: e instanceof Error ? e.message : String(e),
|
|
206
|
-
icon: 'i-lucide-triangle-alert',
|
|
207
|
-
color: 'error',
|
|
208
|
-
})
|
|
199
|
+
present(e, 'settings.openRouterCatalog.toast.saveFailed')
|
|
209
200
|
} finally {
|
|
210
201
|
busy.value = false
|
|
211
202
|
}
|
|
@@ -13,6 +13,7 @@ import SecretInput from '~/components/common/SecretInput.vue'
|
|
|
13
13
|
const { t } = useI18n()
|
|
14
14
|
const store = usePackageRegistriesStore()
|
|
15
15
|
const toast = useToast()
|
|
16
|
+
const { present } = usePipelineErrorToast()
|
|
16
17
|
const { confirmAction, toastDone } = useConfirmAction()
|
|
17
18
|
|
|
18
19
|
// The registry vendors a workspace can connect. Fixed set — the host derives from the
|
|
@@ -44,15 +45,6 @@ const parsedScopes = computed(() =>
|
|
|
44
45
|
.map((s) => (s.startsWith('@') ? s : `@${s}`)),
|
|
45
46
|
)
|
|
46
47
|
|
|
47
|
-
function notifyError(title: string, e: unknown) {
|
|
48
|
-
toast.add({
|
|
49
|
-
title,
|
|
50
|
-
description: e instanceof Error ? e.message : String(e),
|
|
51
|
-
icon: 'i-lucide-triangle-alert',
|
|
52
|
-
color: 'error',
|
|
53
|
-
})
|
|
54
|
-
}
|
|
55
|
-
|
|
56
48
|
// The tab this renders in only exists once the window's probe resolved `available === true`, so
|
|
57
49
|
// `ensureLoaded()` here would early-return every time and this error branch would be dead code.
|
|
58
50
|
// Read the list outright instead: the window owns the PROBE (a failure there means no tab), the
|
|
@@ -63,7 +55,7 @@ onMounted(async () => {
|
|
|
63
55
|
try {
|
|
64
56
|
await store.load()
|
|
65
57
|
} catch (e) {
|
|
66
|
-
|
|
58
|
+
present(e, 'settings.packageRegistries.toast.loadFailed')
|
|
67
59
|
}
|
|
68
60
|
})
|
|
69
61
|
|
|
@@ -84,7 +76,7 @@ async function addEntry() {
|
|
|
84
76
|
color: 'success',
|
|
85
77
|
})
|
|
86
78
|
} catch (e) {
|
|
87
|
-
|
|
79
|
+
present(e, 'settings.packageRegistries.toast.addFailed')
|
|
88
80
|
} finally {
|
|
89
81
|
busy.value = false
|
|
90
82
|
}
|
|
@@ -98,7 +90,7 @@ async function removeEntry(entryId: string) {
|
|
|
98
90
|
await store.remove(entryId)
|
|
99
91
|
toastDone('remove', noun)
|
|
100
92
|
} catch (e) {
|
|
101
|
-
|
|
93
|
+
present(e, 'settings.packageRegistries.toast.removeFailed')
|
|
102
94
|
} finally {
|
|
103
95
|
busy.value = false
|
|
104
96
|
}
|
|
@@ -36,6 +36,7 @@ const emit = defineEmits<{ connected: [] }>()
|
|
|
36
36
|
const { t } = useI18n()
|
|
37
37
|
const store = useProviderConnectionsStore()
|
|
38
38
|
const toast = useToast()
|
|
39
|
+
const { present } = usePipelineErrorToast()
|
|
39
40
|
const { confirmAction } = useConfirmAction()
|
|
40
41
|
|
|
41
42
|
const descriptor = computed(() => store.descriptorFor(props.kind))
|
|
@@ -205,15 +206,6 @@ function buildFlatPayload() {
|
|
|
205
206
|
return isNativeConfig.value ? buildConfigPayload() : buildManifestPayload()
|
|
206
207
|
}
|
|
207
208
|
|
|
208
|
-
function notifyError(title: string, e: unknown) {
|
|
209
|
-
toast.add({
|
|
210
|
-
title,
|
|
211
|
-
description: e instanceof Error ? e.message : String(e),
|
|
212
|
-
icon: 'i-lucide-triangle-alert',
|
|
213
|
-
color: 'error',
|
|
214
|
-
})
|
|
215
|
-
}
|
|
216
|
-
|
|
217
209
|
function toastSaved() {
|
|
218
210
|
toast.add({
|
|
219
211
|
title: t('settings.providerConnection.toast.saved', { title: title.value }),
|
|
@@ -246,7 +238,7 @@ async function saveNative() {
|
|
|
246
238
|
resetDraft()
|
|
247
239
|
toastSaved()
|
|
248
240
|
} catch (e) {
|
|
249
|
-
|
|
241
|
+
present(e, 'settings.providerConnection.toast.saveFailed')
|
|
250
242
|
} finally {
|
|
251
243
|
busy.value = false
|
|
252
244
|
}
|
|
@@ -280,7 +272,7 @@ async function saveManifest(payload: {
|
|
|
280
272
|
emit('connected')
|
|
281
273
|
toastSaved()
|
|
282
274
|
} catch (e) {
|
|
283
|
-
|
|
275
|
+
present(e, 'settings.providerConnection.toast.saveFailed')
|
|
284
276
|
} finally {
|
|
285
277
|
busy.value = false
|
|
286
278
|
}
|
|
@@ -325,7 +317,7 @@ async function saveConfig(payload: {
|
|
|
325
317
|
emit('connected')
|
|
326
318
|
toastSaved()
|
|
327
319
|
} catch (e) {
|
|
328
|
-
|
|
320
|
+
present(e, 'settings.providerConnection.toast.saveFailed')
|
|
329
321
|
} finally {
|
|
330
322
|
busy.value = false
|
|
331
323
|
}
|
|
@@ -339,7 +331,7 @@ async function remove() {
|
|
|
339
331
|
resetDraft()
|
|
340
332
|
toast.add({ title: t('settings.providerConnection.toast.removed'), icon: 'i-lucide-check' })
|
|
341
333
|
} catch (e) {
|
|
342
|
-
|
|
334
|
+
present(e, 'settings.providerConnection.toast.removeFailed')
|
|
343
335
|
} finally {
|
|
344
336
|
busy.value = false
|
|
345
337
|
}
|
|
@@ -9,7 +9,7 @@ const { t } = useI18n()
|
|
|
9
9
|
const ui = useUiStore()
|
|
10
10
|
const presets = useRiskPoliciesStore()
|
|
11
11
|
const { newPresets, outdated, hasIssues } = useRiskPolicyHealth()
|
|
12
|
-
const
|
|
12
|
+
const { present } = usePipelineErrorToast()
|
|
13
13
|
|
|
14
14
|
const open = computed({
|
|
15
15
|
get: () => ui.riskPolicyHealthOpen,
|
|
@@ -28,12 +28,7 @@ async function reseed(id: string) {
|
|
|
28
28
|
try {
|
|
29
29
|
await presets.reseed(id)
|
|
30
30
|
} catch (e) {
|
|
31
|
-
toast.
|
|
32
|
-
title: t('riskPolicy.health.toast.reseedFailed'),
|
|
33
|
-
description: e instanceof Error ? e.message : String(e),
|
|
34
|
-
icon: 'i-lucide-triangle-alert',
|
|
35
|
-
color: 'error',
|
|
36
|
-
})
|
|
31
|
+
present(e, 'riskPolicy.health.toast.reseedFailed')
|
|
37
32
|
} finally {
|
|
38
33
|
const next = new Set(busy.value)
|
|
39
34
|
next.delete(id)
|