@cat-factory/app 0.97.0 → 0.98.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.
- package/app/components/environments/EnvironmentSetupWizard.vue +654 -0
- package/app/components/layout/SideBar.vue +14 -0
- package/app/components/panels/inspector/FrontendConfig.vue +17 -7
- package/app/components/panels/inspector/ServiceTestConfig.vue +48 -7
- package/app/composables/api/environments.ts +7 -1
- package/app/composables/api/preflights.ts +16 -0
- package/app/composables/useApi.ts +2 -0
- package/app/pages/index.vue +4 -0
- package/app/stores/environmentWizard.ts +489 -0
- package/app/stores/preflights.ts +48 -0
- package/app/stores/ui.ts +21 -0
- package/i18n/locales/en.json +86 -2
- package/i18n/locales/es.json +86 -2
- package/i18n/locales/fr.json +86 -2
- package/i18n/locales/he.json +86 -2
- package/i18n/locales/ja.json +86 -2
- package/i18n/locales/pl.json +86 -2
- package/i18n/locales/tr.json +86 -2
- package/i18n/locales/uk.json +86 -2
- package/package.json +2 -2
|
@@ -12,6 +12,7 @@ import type {
|
|
|
12
12
|
} from '~/types/domain'
|
|
13
13
|
import FrontendBindingsResolved from '~/components/panels/inspector/FrontendBindingsResolved.vue'
|
|
14
14
|
import InspectorSection from '~/components/panels/inspector/InspectorSection.vue'
|
|
15
|
+
import { apiErrorEnvelope } from '~/composables/api/errors'
|
|
15
16
|
|
|
16
17
|
// Frontend-frame (`type: 'frontend'`) configuration: how to build, serve, and mock this
|
|
17
18
|
// frontend for a self-contained UI test (+ an optional browsable preview on local/node),
|
|
@@ -119,7 +120,9 @@ const showPreview = ref(false)
|
|
|
119
120
|
// frame links to its repo the same way a service does (`github_repos.block_id`).
|
|
120
121
|
const repoLink = computed(() => github.repoForBlock(props.block.id))
|
|
121
122
|
const detecting = ref(false)
|
|
122
|
-
|
|
123
|
+
// The detect failure message to show, or null when there's no error. Holds the SERVER's real
|
|
124
|
+
// message (the backend raises an actionable one for an unreadable repo) instead of a fixed line.
|
|
125
|
+
const detectError = ref<string | null>(null)
|
|
123
126
|
const detectResult = ref<FrontendConfigRecommendation | null>(null)
|
|
124
127
|
|
|
125
128
|
// A detection result is scoped to the inspected block — clear it (and any error) when the
|
|
@@ -128,18 +131,20 @@ watch(
|
|
|
128
131
|
() => props.block.id,
|
|
129
132
|
() => {
|
|
130
133
|
detectResult.value = null
|
|
131
|
-
detectError.value =
|
|
134
|
+
detectError.value = null
|
|
132
135
|
},
|
|
133
136
|
)
|
|
134
137
|
|
|
135
138
|
async function detectFromRepo() {
|
|
136
139
|
const repo = repoLink.value
|
|
137
140
|
if (!repo) {
|
|
138
|
-
|
|
141
|
+
// The frame points at a repo that isn't in the connected-repo projection, so we can't resolve
|
|
142
|
+
// its owner/name to ask the backend. A "sync/connect GitHub" problem, not a read failure.
|
|
143
|
+
detectError.value = t('inspector.detectRepoUnresolved')
|
|
139
144
|
return
|
|
140
145
|
}
|
|
141
146
|
detecting.value = true
|
|
142
|
-
detectError.value =
|
|
147
|
+
detectError.value = null
|
|
143
148
|
detectResult.value = null
|
|
144
149
|
try {
|
|
145
150
|
const result = await infra.detectFrontendConfig({
|
|
@@ -151,8 +156,13 @@ async function detectFromRepo() {
|
|
|
151
156
|
// When nothing was detected the "none" hint tells the user to set the frontend directory —
|
|
152
157
|
// that field lives in the (collapsed) Build group, so open it so the advice is actionable.
|
|
153
158
|
if (!result.detected) showBuild.value = true
|
|
154
|
-
} catch {
|
|
155
|
-
|
|
159
|
+
} catch (e) {
|
|
160
|
+
// Surface the server's real message (an actionable "couldn't read the repo — check App access"
|
|
161
|
+
// for a read fault), falling back to the generic line only when none is available.
|
|
162
|
+
detectError.value =
|
|
163
|
+
apiErrorEnvelope(e)?.message ??
|
|
164
|
+
(e instanceof Error ? e.message : null) ??
|
|
165
|
+
t('inspector.frontendConfig.detect.error')
|
|
156
166
|
} finally {
|
|
157
167
|
detecting.value = false
|
|
158
168
|
}
|
|
@@ -243,7 +253,7 @@ onUnmounted(() => preview.stopPolling(props.block.id))
|
|
|
243
253
|
</p>
|
|
244
254
|
|
|
245
255
|
<p v-if="detectError" class="text-[11px] text-rose-300/80">
|
|
246
|
-
{{
|
|
256
|
+
{{ detectError }}
|
|
247
257
|
</p>
|
|
248
258
|
|
|
249
259
|
<template v-if="detectResult && !detecting">
|
|
@@ -18,6 +18,7 @@ import type {
|
|
|
18
18
|
} from '@cat-factory/contracts'
|
|
19
19
|
import RepoTreeBrowser from '~/components/github/RepoTreeBrowser.vue'
|
|
20
20
|
import InspectorSection from '~/components/panels/inspector/InspectorSection.vue'
|
|
21
|
+
import { apiErrorEnvelope } from '~/composables/api/errors'
|
|
21
22
|
|
|
22
23
|
// Service-level (frame) configuration: the service-owned PROVISIONING — the provision
|
|
23
24
|
// TYPE this service produces (`infraless` / `docker-compose` / `kubernetes` / `custom`)
|
|
@@ -42,6 +43,7 @@ const github = useGitHubStore()
|
|
|
42
43
|
const services = useServicesStore()
|
|
43
44
|
const infra = useInfraConfigStore()
|
|
44
45
|
const agentRuns = useAgentRunsStore()
|
|
46
|
+
const ui = useUiStore()
|
|
45
47
|
const { t } = useI18n()
|
|
46
48
|
|
|
47
49
|
// The custom-manifest-type catalog feeds the `custom` picker. Cheap + shared (coalesced).
|
|
@@ -284,7 +286,10 @@ function applyPicked() {
|
|
|
284
286
|
// every field stays editable, and the engine-level URL/namespace suggestions are surfaced
|
|
285
287
|
// read-only (the workspace handler owns them). Nothing is persisted server-side by detection.
|
|
286
288
|
const detecting = ref(false)
|
|
287
|
-
|
|
289
|
+
// The detect failure message to show, or null when there's no error. Holds the SERVER's real
|
|
290
|
+
// message (the backend now raises an actionable one for an unreadable repo) so the user sees why
|
|
291
|
+
// detection failed instead of a fixed, vague line.
|
|
292
|
+
const detectError = ref<string | null>(null)
|
|
288
293
|
const detectResult = ref<ProvisioningRecommendation | null>(null)
|
|
289
294
|
// Advisory, LOCAL-ONLY selection: which compose `services:` key the user picked. It is NOT persisted
|
|
290
295
|
// (the compose backend targets the file, not a single service), so it lives only in component state
|
|
@@ -298,7 +303,7 @@ watch(
|
|
|
298
303
|
() => props.block.id,
|
|
299
304
|
() => {
|
|
300
305
|
detectResult.value = null
|
|
301
|
-
detectError.value =
|
|
306
|
+
detectError.value = null
|
|
302
307
|
pickedComposeService.value = null
|
|
303
308
|
},
|
|
304
309
|
)
|
|
@@ -308,11 +313,14 @@ async function detectFromRepo() {
|
|
|
308
313
|
if (!ctx) return
|
|
309
314
|
const repo = github.repoFor(ctx.githubId)
|
|
310
315
|
if (!repo) {
|
|
311
|
-
|
|
316
|
+
// The frame points at a repo that isn't in the connected-repo projection, so we can't resolve
|
|
317
|
+
// its owner/name to ask the backend. That's a "sync/connect GitHub" problem, NOT "couldn't read
|
|
318
|
+
// the repo" — say so specifically.
|
|
319
|
+
detectError.value = t('inspector.detectRepoUnresolved')
|
|
312
320
|
return
|
|
313
321
|
}
|
|
314
322
|
detecting.value = true
|
|
315
|
-
detectError.value =
|
|
323
|
+
detectError.value = null
|
|
316
324
|
try {
|
|
317
325
|
const rec = await infra.detectProvisioning({
|
|
318
326
|
owner: repo.owner,
|
|
@@ -342,8 +350,13 @@ async function detectFromRepo() {
|
|
|
342
350
|
board.updateBlock(props.block.id, { provisioning: rec.provisioning })
|
|
343
351
|
if (rec.provisioning.type === 'kubernetes') seedKubeSource(rec.provisioning.manifestSource)
|
|
344
352
|
}
|
|
345
|
-
} catch {
|
|
346
|
-
|
|
353
|
+
} catch (e) {
|
|
354
|
+
// Surface the server's real message (an actionable "couldn't read the repo — check App access"
|
|
355
|
+
// for a read fault), falling back to the generic line only when none is available.
|
|
356
|
+
detectError.value =
|
|
357
|
+
apiErrorEnvelope(e)?.message ??
|
|
358
|
+
(e instanceof Error ? e.message : null) ??
|
|
359
|
+
t('inspector.testConfig.detect.error')
|
|
347
360
|
} finally {
|
|
348
361
|
detecting.value = false
|
|
349
362
|
}
|
|
@@ -431,6 +444,34 @@ function setSize(value: InstanceSize) {
|
|
|
431
444
|
</p>
|
|
432
445
|
</div>
|
|
433
446
|
|
|
447
|
+
<!-- Nudge into the guided environment setup wizard for a docker-compose service: the wizard
|
|
448
|
+
drives detect → review (recipe + analyst draft) → preflight → save so the single Deployer
|
|
449
|
+
provisions the compose stack, rather than editing the raw path inline. -->
|
|
450
|
+
<div
|
|
451
|
+
v-if="provisionType === 'docker-compose'"
|
|
452
|
+
class="flex items-center justify-between gap-2 rounded border border-primary-800/40 bg-primary-950/20 p-2"
|
|
453
|
+
data-testid="env-setup-nudge"
|
|
454
|
+
>
|
|
455
|
+
<div class="min-w-0">
|
|
456
|
+
<p class="text-[11px] font-medium text-primary-200/90">
|
|
457
|
+
{{ t('inspector.testConfig.envWizard.title') }}
|
|
458
|
+
</p>
|
|
459
|
+
<p class="text-[11px] leading-snug text-slate-500">
|
|
460
|
+
{{ t('inspector.testConfig.envWizard.hint') }}
|
|
461
|
+
</p>
|
|
462
|
+
</div>
|
|
463
|
+
<UButton
|
|
464
|
+
size="xs"
|
|
465
|
+
variant="soft"
|
|
466
|
+
color="primary"
|
|
467
|
+
icon="i-lucide-flask-conical"
|
|
468
|
+
data-testid="env-setup-nudge-open"
|
|
469
|
+
@click="ui.openEnvironmentSetup(props.block.id)"
|
|
470
|
+
>
|
|
471
|
+
{{ t('inspector.testConfig.envWizard.open') }}
|
|
472
|
+
</UButton>
|
|
473
|
+
</div>
|
|
474
|
+
|
|
434
475
|
<!-- Auto-detect a recommended provisioning config from the repo (slice 11). Non-binding:
|
|
435
476
|
it prefills the form below + the kube edit refs; the user confirms/edits everything. -->
|
|
436
477
|
<div v-if="repoContext" class="space-y-2 rounded border border-slate-800 bg-slate-900/40 p-2">
|
|
@@ -452,7 +493,7 @@ function setSize(value: InstanceSize) {
|
|
|
452
493
|
</p>
|
|
453
494
|
|
|
454
495
|
<p v-if="detectError" class="text-[11px] text-rose-300/80">
|
|
455
|
-
{{
|
|
496
|
+
{{ detectError }}
|
|
456
497
|
</p>
|
|
457
498
|
|
|
458
499
|
<template v-if="detectResult && !detecting">
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { listEnvironmentsContract } from '@cat-factory/contracts'
|
|
1
|
+
import { listEnvironmentsContract, provisionEnvironmentContract } from '@cat-factory/contracts'
|
|
2
|
+
import type { ProvisionEnvironmentInput } from '@cat-factory/contracts'
|
|
2
3
|
import type { ApiContext } from './context'
|
|
3
4
|
|
|
4
5
|
/** Ephemeral environments: the workspace's live env handles (used to resolve frontend bindings). */
|
|
@@ -6,5 +7,10 @@ export function environmentsApi({ send, ws }: ApiContext) {
|
|
|
6
7
|
return {
|
|
7
8
|
listEnvironments: (workspaceId: string) =>
|
|
8
9
|
send(listEnvironmentsContract, { pathPrefix: ws(workspaceId) }),
|
|
10
|
+
|
|
11
|
+
// Manually provision an environment for a service frame (outside a pipeline run) — the setup
|
|
12
|
+
// wizard's "trial provision" against the just-saved config. Returns the resulting handle.
|
|
13
|
+
provisionEnvironment: (workspaceId: string, body: ProvisionEnvironmentInput) =>
|
|
14
|
+
send(provisionEnvironmentContract, { pathPrefix: ws(workspaceId), body }),
|
|
9
15
|
}
|
|
10
16
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { runPreflightsContract } from '@cat-factory/contracts'
|
|
2
|
+
import type { PreflightRef } from '@cat-factory/contracts'
|
|
3
|
+
import type { ApiContext } from './context'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Preflight checks (machine-prerequisite probes for a stack recipe): run a set of refs and get one
|
|
7
|
+
* verdict each (pass / fail / warn + detail + remediation). Used by the environment setup wizard's
|
|
8
|
+
* checklist + live re-check. The probes run only on the local (host) facade; the endpoint 503s
|
|
9
|
+
* elsewhere. See PreflightController in @cat-factory/server.
|
|
10
|
+
*/
|
|
11
|
+
export function preflightsApi({ send, ws }: ApiContext) {
|
|
12
|
+
return {
|
|
13
|
+
runPreflights: (workspaceId: string, prerequisites: PreflightRef[]) =>
|
|
14
|
+
send(runPreflightsContract, { pathPrefix: ws(workspaceId), body: { prerequisites } }),
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -21,6 +21,7 @@ import { localSettingsApi } from './api/localSettings'
|
|
|
21
21
|
import { modelsApi } from './api/models'
|
|
22
22
|
import { notificationsApi } from './api/notifications'
|
|
23
23
|
import { packageRegistriesApi } from './api/packageRegistries'
|
|
24
|
+
import { preflightsApi } from './api/preflights'
|
|
24
25
|
import { presetsApi } from './api/presets'
|
|
25
26
|
import { sharedStacksApi } from './api/sharedStacks'
|
|
26
27
|
import { providerConnectionsApi } from './api/providerConnections'
|
|
@@ -113,6 +114,7 @@ export function useApi() {
|
|
|
113
114
|
...specApi(ctx),
|
|
114
115
|
...notificationsApi(ctx),
|
|
115
116
|
...presetsApi(ctx),
|
|
117
|
+
...preflightsApi(ctx),
|
|
116
118
|
...sharedStacksApi(ctx),
|
|
117
119
|
...providerConnectionsApi(ctx),
|
|
118
120
|
...infraHandlersApi(ctx),
|
package/app/pages/index.vue
CHANGED
|
@@ -95,6 +95,9 @@ const PackageRegistriesPanel = defineAsyncComponent(
|
|
|
95
95
|
const InfrastructureWindow = defineAsyncComponent(
|
|
96
96
|
() => import('~/components/settings/InfrastructureWindow.vue'),
|
|
97
97
|
)
|
|
98
|
+
const EnvironmentSetupWizard = defineAsyncComponent(
|
|
99
|
+
() => import('~/components/environments/EnvironmentSetupWizard.vue'),
|
|
100
|
+
)
|
|
98
101
|
const ModelConfigurationPanel = defineAsyncComponent(
|
|
99
102
|
() => import('~/components/settings/ModelConfigurationPanel.vue'),
|
|
100
103
|
)
|
|
@@ -373,6 +376,7 @@ watch(
|
|
|
373
376
|
<ObservabilityConnectionPanel v-if="ui.observabilityConnectionOpen" />
|
|
374
377
|
<PackageRegistriesPanel v-if="ui.packageRegistriesOpen" />
|
|
375
378
|
<InfrastructureWindow v-if="ui.infrastructureOpen" />
|
|
379
|
+
<EnvironmentSetupWizard v-if="ui.environmentWizardOpen" />
|
|
376
380
|
<ModelConfigurationPanel v-if="ui.modelConfigOpen" />
|
|
377
381
|
<LocalModelEndpointsPanel v-if="ui.localModelsOpen" />
|
|
378
382
|
<SandboxPanel v-if="ui.sandboxOpen" />
|