@cat-factory/app 0.171.0 → 0.172.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/layout/DefaultTestEnvBanner.vue +117 -0
- package/app/components/settings/DefaultProvisionTypeSection.vue +176 -0
- package/app/components/settings/InfrastructureWindow.vue +8 -1
- package/app/pages/index.vue +12 -1
- package/app/stores/ui/modals.ts +46 -0
- package/app/stores/workspaceSettings.ts +5 -0
- package/app/utils/defaultProvisioning.spec.ts +100 -0
- package/app/utils/defaultProvisioning.ts +99 -0
- package/i18n/locales/de.json +14 -0
- package/i18n/locales/en.json +14 -0
- package/i18n/locales/es.json +14 -0
- package/i18n/locales/fr.json +14 -0
- package/i18n/locales/he.json +14 -0
- package/i18n/locales/it.json +14 -0
- package/i18n/locales/ja.json +14 -0
- package/i18n/locales/pl.json +14 -0
- package/i18n/locales/tr.json +14 -0
- package/i18n/locales/uk.json +14 -0
- package/package.json +2 -2
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// Convenience prompt to pick the board's DEFAULT test-environment provisioning mechanism — the
|
|
3
|
+
// provision type suggested for every newly added service. Fires on a workspace that has recorded
|
|
4
|
+
// no choice, which covers all three cases the product asked for with ONE condition rather than
|
|
5
|
+
// three: a board created by hand, the board the SPA creates implicitly on first launch, and an
|
|
6
|
+
// older board that predates the setting. All of them simply have `defaultProvisionType == null`.
|
|
7
|
+
//
|
|
8
|
+
// The nag ends the moment a decision is RECORDED, including `infraless` ("services stand up no
|
|
9
|
+
// environment") — that is a real answer, not an absence. See the contracts block comment on
|
|
10
|
+
// `defaultProvisionType` for why the field is nullable rather than defaulted.
|
|
11
|
+
//
|
|
12
|
+
// Positioning/stacking against the sibling advisory banners is owned by the shared click-through
|
|
13
|
+
// column in `pages/index.vue`; this renders only its card and re-enables pointer events on it.
|
|
14
|
+
import { computed } from 'vue'
|
|
15
|
+
import {
|
|
16
|
+
defaultProvisioningConfigUrl,
|
|
17
|
+
needsDefaultProvisioningChoice,
|
|
18
|
+
} from '~/utils/defaultProvisioning'
|
|
19
|
+
|
|
20
|
+
const { t } = useI18n()
|
|
21
|
+
const ui = useUiStore()
|
|
22
|
+
const workspace = useWorkspaceStore()
|
|
23
|
+
const settingsStore = useWorkspaceSettingsStore()
|
|
24
|
+
const { canManageSettings } = useWorkspaceAccess()
|
|
25
|
+
|
|
26
|
+
// Only prompt someone who can actually answer. A member/viewer cannot write workspace settings
|
|
27
|
+
// (the endpoint is `settings.manage`-gated), so for them this would be an un-actionable nag
|
|
28
|
+
// pointing at a screen that would refuse their save.
|
|
29
|
+
const show = computed(
|
|
30
|
+
() =>
|
|
31
|
+
workspace.ready &&
|
|
32
|
+
canManageSettings.value &&
|
|
33
|
+
needsDefaultProvisioningChoice(settingsStore.settings) &&
|
|
34
|
+
!ui.defaultProvisionDismissed,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
// A real, copyable link to the configuration screen — so this can be handed to whoever owns the
|
|
38
|
+
// board's infrastructure instead of being described. Clicking it opens the screen in place
|
|
39
|
+
// (no reload); the `href` is what makes middle-click, "copy link address" and open-in-new-tab
|
|
40
|
+
// work, and the ui store consumes the same param on load so a pasted link lands on the section.
|
|
41
|
+
const configUrl = computed(() => defaultProvisioningConfigUrl())
|
|
42
|
+
|
|
43
|
+
function openConfig(event: MouseEvent) {
|
|
44
|
+
// Leave the modified clicks (new tab / new window / download) to the browser.
|
|
45
|
+
if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey || event.button !== 0) return
|
|
46
|
+
event.preventDefault()
|
|
47
|
+
ui.openDefaultProvisionSettings()
|
|
48
|
+
}
|
|
49
|
+
</script>
|
|
50
|
+
|
|
51
|
+
<template>
|
|
52
|
+
<Transition name="fade">
|
|
53
|
+
<div
|
|
54
|
+
v-if="show"
|
|
55
|
+
class="pointer-events-auto w-full max-w-3xl rounded-2xl border border-sky-500/50 bg-sky-950/90 p-4 shadow-xl backdrop-blur"
|
|
56
|
+
role="status"
|
|
57
|
+
aria-live="polite"
|
|
58
|
+
data-testid="default-test-env-banner"
|
|
59
|
+
>
|
|
60
|
+
<div class="flex items-start gap-3">
|
|
61
|
+
<UIcon name="i-lucide-flask-conical" class="mt-0.5 h-7 w-7 shrink-0 text-sky-400" />
|
|
62
|
+
<div class="min-w-0 flex-1">
|
|
63
|
+
<div class="flex items-start justify-between gap-3">
|
|
64
|
+
<h2 class="text-sm font-semibold text-sky-100">
|
|
65
|
+
{{ t('layout.defaultTestEnvBanner.title') }}
|
|
66
|
+
</h2>
|
|
67
|
+
<UButton
|
|
68
|
+
color="neutral"
|
|
69
|
+
variant="ghost"
|
|
70
|
+
size="xs"
|
|
71
|
+
icon="i-lucide-x"
|
|
72
|
+
:aria-label="t('common.close')"
|
|
73
|
+
data-testid="default-test-env-dismiss"
|
|
74
|
+
@click="ui.dismissDefaultProvision()"
|
|
75
|
+
/>
|
|
76
|
+
</div>
|
|
77
|
+
<p class="mt-1 text-[13px] text-sky-200/90">
|
|
78
|
+
{{ t('layout.defaultTestEnvBanner.body') }}
|
|
79
|
+
</p>
|
|
80
|
+
<div class="mt-3 flex flex-wrap items-center gap-x-3 gap-y-2">
|
|
81
|
+
<UButton
|
|
82
|
+
:to="configUrl"
|
|
83
|
+
size="sm"
|
|
84
|
+
color="info"
|
|
85
|
+
variant="solid"
|
|
86
|
+
icon="i-lucide-settings"
|
|
87
|
+
data-testid="default-test-env-configure"
|
|
88
|
+
@click="openConfig"
|
|
89
|
+
>
|
|
90
|
+
{{ t('layout.defaultTestEnvBanner.action') }}
|
|
91
|
+
</UButton>
|
|
92
|
+
<!-- The URL itself, shown so it can be read and copied, not just clicked. -->
|
|
93
|
+
<a
|
|
94
|
+
:href="configUrl"
|
|
95
|
+
class="min-w-0 truncate font-mono text-[11px] text-sky-300/80 underline decoration-dotted underline-offset-2 hover:text-sky-200"
|
|
96
|
+
data-testid="default-test-env-url"
|
|
97
|
+
@click="openConfig"
|
|
98
|
+
>
|
|
99
|
+
{{ configUrl }}
|
|
100
|
+
</a>
|
|
101
|
+
</div>
|
|
102
|
+
</div>
|
|
103
|
+
</div>
|
|
104
|
+
</div>
|
|
105
|
+
</Transition>
|
|
106
|
+
</template>
|
|
107
|
+
|
|
108
|
+
<style scoped>
|
|
109
|
+
.fade-enter-active,
|
|
110
|
+
.fade-leave-active {
|
|
111
|
+
transition: opacity 0.2s ease;
|
|
112
|
+
}
|
|
113
|
+
.fade-enter-from,
|
|
114
|
+
.fade-leave-to {
|
|
115
|
+
opacity: 0;
|
|
116
|
+
}
|
|
117
|
+
</style>
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// The workspace's DEFAULT test-environment provisioning mechanism — the provision type stamped
|
|
3
|
+
// onto every newly added service frame so the operator declares it once per board instead of
|
|
4
|
+
// once per service. Lives at the top of the Infrastructure window's "Test environments" tab,
|
|
5
|
+
// above the per-type handler configurator, because it answers the question that comes FIRST:
|
|
6
|
+
// what do this board's services produce, before you configure how each type is handled.
|
|
7
|
+
//
|
|
8
|
+
// This is a SUGGESTION for new services, never a run-time override: the engine still reads only
|
|
9
|
+
// a service's own `provisioning`, so saving a different default here never changes what an
|
|
10
|
+
// existing service provisions (each stays editable in its inspector).
|
|
11
|
+
//
|
|
12
|
+
// The section opens on the workspace's recorded choice, or — with nothing recorded — on the first
|
|
13
|
+
// REGISTERED custom provider when the deployment brought one. All of that precedence lives in
|
|
14
|
+
// `utils/defaultProvisioning.ts` so it is unit-tested and shared with the banner that nags about
|
|
15
|
+
// the unset state. Nothing is persisted until the operator saves.
|
|
16
|
+
import { computed, ref, watch } from 'vue'
|
|
17
|
+
import type { ProvisionType } from '@cat-factory/contracts'
|
|
18
|
+
import {
|
|
19
|
+
canSaveDefaultProvisioning,
|
|
20
|
+
suggestDefaultProvisioning,
|
|
21
|
+
type DefaultProvisioningSelection,
|
|
22
|
+
} from '~/utils/defaultProvisioning'
|
|
23
|
+
|
|
24
|
+
const { t } = useI18n()
|
|
25
|
+
const infra = useInfraConfigStore()
|
|
26
|
+
const settingsStore = useWorkspaceSettingsStore()
|
|
27
|
+
const toast = useToast()
|
|
28
|
+
|
|
29
|
+
onMounted(() => {
|
|
30
|
+
void infra.ensureLoaded()
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
// The saved state, and the (unsaved) edit state seeded from it. Re-seeded whenever the saved
|
|
34
|
+
// choice or the custom-type catalog changes — the catalog arrives asynchronously, so without the
|
|
35
|
+
// second dependency a board whose only sensible answer is a registered provider would open unset
|
|
36
|
+
// and never pick it up.
|
|
37
|
+
const selection = ref<DefaultProvisioningSelection>({ type: null, manifestId: null })
|
|
38
|
+
watch(
|
|
39
|
+
[() => settingsStore.settings, () => infra.customTypes],
|
|
40
|
+
([settings, customTypes]) => {
|
|
41
|
+
selection.value = suggestDefaultProvisioning(settings, customTypes)
|
|
42
|
+
},
|
|
43
|
+
{ immediate: true, deep: true },
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
// Whether what's on screen differs from what's stored — drives both the Save button and the
|
|
47
|
+
// "this is only a suggestion so far" hint, so a preselected-but-unsaved custom provider can
|
|
48
|
+
// never read as already configured.
|
|
49
|
+
const dirty = computed(
|
|
50
|
+
() =>
|
|
51
|
+
selection.value.type !== settingsStore.settings.defaultProvisionType ||
|
|
52
|
+
(selection.value.manifestId ?? null) !==
|
|
53
|
+
(settingsStore.settings.defaultProvisionManifestId ?? null),
|
|
54
|
+
)
|
|
55
|
+
const unset = computed(() => settingsStore.settings.defaultProvisionType == null)
|
|
56
|
+
const canSave = computed(() => canSaveDefaultProvisioning(selection.value) && dirty.value)
|
|
57
|
+
|
|
58
|
+
const PROVISION_TYPES = computed<{ value: ProvisionType; label: string }[]>(() => [
|
|
59
|
+
{ value: 'infraless', label: t('inspector.testConfig.provisionTypes.infraless') },
|
|
60
|
+
{ value: 'docker-compose', label: t('inspector.testConfig.provisionTypes.docker-compose') },
|
|
61
|
+
{ value: 'kubernetes', label: t('inspector.testConfig.provisionTypes.kubernetes') },
|
|
62
|
+
{ value: 'cloudflare', label: t('inspector.testConfig.provisionTypes.cloudflare') },
|
|
63
|
+
{ value: 'custom', label: t('inspector.testConfig.provisionTypes.custom') },
|
|
64
|
+
])
|
|
65
|
+
|
|
66
|
+
const customTypeItems = computed(() =>
|
|
67
|
+
infra.customTypes.map((c) => ({ label: `${c.label} (${c.manifestId})`, value: c.manifestId })),
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
function setType(type: ProvisionType) {
|
|
71
|
+
// Switching away from `custom` drops the pinned id, matching the server's normalisation — a
|
|
72
|
+
// retained id would silently reappear on switching back.
|
|
73
|
+
selection.value =
|
|
74
|
+
type === 'custom'
|
|
75
|
+
? { type, manifestId: selection.value.manifestId ?? infra.customTypes[0]?.manifestId ?? null }
|
|
76
|
+
: { type, manifestId: null }
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function setManifestId(manifestId: string) {
|
|
80
|
+
selection.value = { type: 'custom', manifestId: manifestId || null }
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const saving = ref(false)
|
|
84
|
+
async function save() {
|
|
85
|
+
if (!canSave.value || saving.value) return
|
|
86
|
+
saving.value = true
|
|
87
|
+
try {
|
|
88
|
+
await settingsStore.update({
|
|
89
|
+
defaultProvisionType: selection.value.type,
|
|
90
|
+
defaultProvisionManifestId: selection.value.manifestId,
|
|
91
|
+
})
|
|
92
|
+
toast.add({ title: t('settings.defaultProvision.saved'), color: 'success' })
|
|
93
|
+
} catch (e) {
|
|
94
|
+
toast.add({
|
|
95
|
+
title: t('settings.defaultProvision.saveFailed'),
|
|
96
|
+
description: e instanceof Error ? e.message : undefined,
|
|
97
|
+
color: 'error',
|
|
98
|
+
})
|
|
99
|
+
} finally {
|
|
100
|
+
saving.value = false
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
</script>
|
|
104
|
+
|
|
105
|
+
<template>
|
|
106
|
+
<section class="space-y-3" data-testid="default-provision-section">
|
|
107
|
+
<div>
|
|
108
|
+
<h3 class="text-sm font-semibold text-slate-200">
|
|
109
|
+
{{ t('settings.defaultProvision.title') }}
|
|
110
|
+
</h3>
|
|
111
|
+
<p class="mt-1 text-[11px] leading-snug text-slate-500">
|
|
112
|
+
{{ t('settings.defaultProvision.hint') }}
|
|
113
|
+
</p>
|
|
114
|
+
</div>
|
|
115
|
+
|
|
116
|
+
<div class="flex flex-wrap gap-1">
|
|
117
|
+
<UButton
|
|
118
|
+
v-for="p in PROVISION_TYPES"
|
|
119
|
+
:key="p.value"
|
|
120
|
+
:color="selection.type === p.value ? 'primary' : 'neutral'"
|
|
121
|
+
:variant="selection.type === p.value ? 'soft' : 'ghost'"
|
|
122
|
+
size="xs"
|
|
123
|
+
:data-testid="`default-provision-type-${p.value}`"
|
|
124
|
+
@click="setType(p.value)"
|
|
125
|
+
>
|
|
126
|
+
{{ p.label }}
|
|
127
|
+
</UButton>
|
|
128
|
+
</div>
|
|
129
|
+
|
|
130
|
+
<div v-if="selection.type === 'custom'" class="space-y-1">
|
|
131
|
+
<label class="text-[11px] text-slate-400">
|
|
132
|
+
{{ t('inspector.testConfig.customManifestId') }}
|
|
133
|
+
</label>
|
|
134
|
+
<USelect
|
|
135
|
+
v-if="customTypeItems.length"
|
|
136
|
+
:model-value="selection.manifestId ?? ''"
|
|
137
|
+
:items="customTypeItems"
|
|
138
|
+
size="xs"
|
|
139
|
+
class="w-full"
|
|
140
|
+
data-testid="default-provision-manifest-id"
|
|
141
|
+
:placeholder="t('inspector.testConfig.customManifestIdPlaceholder')"
|
|
142
|
+
@update:model-value="(v: string) => setManifestId(v)"
|
|
143
|
+
/>
|
|
144
|
+
<p v-else class="text-[11px] leading-snug text-amber-300/80">
|
|
145
|
+
{{ t('inspector.testConfig.customNoTypes') }}
|
|
146
|
+
</p>
|
|
147
|
+
</div>
|
|
148
|
+
|
|
149
|
+
<!-- A preselected suggestion must never read as already configured: say so explicitly while
|
|
150
|
+
the workspace still has nothing stored. -->
|
|
151
|
+
<p
|
|
152
|
+
v-if="unset && selection.type"
|
|
153
|
+
class="text-[11px] leading-snug text-amber-300/80"
|
|
154
|
+
data-testid="default-provision-suggestion-hint"
|
|
155
|
+
>
|
|
156
|
+
{{ t('settings.defaultProvision.suggestionHint') }}
|
|
157
|
+
</p>
|
|
158
|
+
|
|
159
|
+
<div class="flex items-center gap-2">
|
|
160
|
+
<UButton
|
|
161
|
+
size="xs"
|
|
162
|
+
color="primary"
|
|
163
|
+
icon="i-lucide-check"
|
|
164
|
+
:loading="saving"
|
|
165
|
+
:disabled="!canSave"
|
|
166
|
+
data-testid="default-provision-save"
|
|
167
|
+
@click="save"
|
|
168
|
+
>
|
|
169
|
+
{{ t('settings.defaultProvision.save') }}
|
|
170
|
+
</UButton>
|
|
171
|
+
<span v-if="!unset && !dirty" class="text-[11px] text-slate-500">
|
|
172
|
+
{{ t('settings.defaultProvision.savedState') }}
|
|
173
|
+
</span>
|
|
174
|
+
</div>
|
|
175
|
+
</section>
|
|
176
|
+
</template>
|
|
@@ -13,6 +13,7 @@ import { computed, ref, watch } from 'vue'
|
|
|
13
13
|
import type { ProviderConnectionKind } from '~/types/providerConnections'
|
|
14
14
|
import InfrastructureBackendPicker from '~/components/settings/InfrastructureBackendPicker.vue'
|
|
15
15
|
import InfraHandlersConfigurator from '~/components/settings/InfraHandlersConfigurator.vue'
|
|
16
|
+
import DefaultProvisionTypeSection from '~/components/settings/DefaultProvisionTypeSection.vue'
|
|
16
17
|
import LocalContainerPoolSettings from '~/components/settings/LocalContainerPoolSettings.vue'
|
|
17
18
|
import SharedStacksPanel from '~/components/settings/SharedStacksPanel.vue'
|
|
18
19
|
|
|
@@ -131,10 +132,16 @@ watch([tabs, () => store.loaded], () => {
|
|
|
131
132
|
</template>
|
|
132
133
|
<template #environment>
|
|
133
134
|
<div class="space-y-4">
|
|
135
|
+
<!-- What this board's services PRODUCE by default. Comes first because it is the
|
|
136
|
+
question the per-type handlers below answer "how" for, and because a board that
|
|
137
|
+
has never chosen is the one the setup banner deep-links straight to here. -->
|
|
138
|
+
<DefaultProvisionTypeSection />
|
|
134
139
|
<!-- The Tester's environment is driven by each SERVICE's declared provision type
|
|
135
140
|
(the "what/where"); the workspace configures HOW each type is handled here —
|
|
136
141
|
the engine + connection per provision type, plus the custom-type catalog. -->
|
|
137
|
-
<
|
|
142
|
+
<div class="border-t border-slate-800 pt-4">
|
|
143
|
+
<InfraHandlersConfigurator />
|
|
144
|
+
</div>
|
|
138
145
|
</div>
|
|
139
146
|
</template>
|
|
140
147
|
<template #shared-stacks>
|
package/app/pages/index.vue
CHANGED
|
@@ -9,6 +9,7 @@ import GitHubPatBanner from '~/components/layout/GitHubPatBanner.vue'
|
|
|
9
9
|
import AiProvidersBanner from '~/components/layout/AiProvidersBanner.vue'
|
|
10
10
|
import ProviderConfigBanner from '~/components/layout/ProviderConfigBanner.vue'
|
|
11
11
|
import InfraSetupBanner from '~/components/layout/InfraSetupBanner.vue'
|
|
12
|
+
import DefaultTestEnvBanner from '~/components/layout/DefaultTestEnvBanner.vue'
|
|
12
13
|
// Always-mounted, fast-path surfaces (opened frequently during a run / board edits, or
|
|
13
14
|
// store-driven so they must react from anywhere — kept eager for snappy open/close).
|
|
14
15
|
import PipelineBuilder from '~/components/pipeline/PipelineBuilder.vue'
|
|
@@ -155,6 +156,9 @@ onMounted(() => {
|
|
|
155
156
|
// Honour a `cat-factory k3s` CLI hand-off (`?infraSetup=local-k3s&…`): open the Infrastructure
|
|
156
157
|
// window pre-seeded with the provisioned connection so the user only pastes the token + saves.
|
|
157
158
|
ui.consumeK3sSetupDeepLink()
|
|
159
|
+
// Honour the setup banner's shareable link (`?settings=default-test-env`): open the
|
|
160
|
+
// Infrastructure window on the default test-environment provisioning section.
|
|
161
|
+
ui.consumeDefaultProvisionDeepLink()
|
|
158
162
|
})
|
|
159
163
|
|
|
160
164
|
// Per-session guards so each AI-onboarding dialog auto-opens at most once (later opens are
|
|
@@ -179,6 +183,9 @@ watch(
|
|
|
179
183
|
ui.resetAiOnboarding()
|
|
180
184
|
// Infra-setup banner session dismissals are per-workspace too — clear them on switch.
|
|
181
185
|
ui.resetInfraSetupDismissals()
|
|
186
|
+
// Same for the default test-environment prompt: each board records its own choice, so a
|
|
187
|
+
// dismissal on one must not hide the (independent) prompt for another.
|
|
188
|
+
ui.resetDefaultProvisionDismissal()
|
|
182
189
|
// A different board has its own pipeline library, so re-arm the once-per-session advisory.
|
|
183
190
|
ui.pipelineHealthSeen = false
|
|
184
191
|
}
|
|
@@ -312,7 +319,10 @@ watch(
|
|
|
312
319
|
- AI-readiness (no usable model source, or default preset uses unavailable models).
|
|
313
320
|
- Infrastructure provider (env/runner-pool wired but missing mandatory config).
|
|
314
321
|
- Infra-setup (this deployment needs an executor / test env / storage the operator hasn't
|
|
315
|
-
defined yet, so a class of agents can't run).
|
|
322
|
+
defined yet, so a class of agents can't run).
|
|
323
|
+
- Default test environment (this BOARD has never chosen the provisioning mechanism its
|
|
324
|
+
new services should default to). Last in the column: it asks for a convenience default,
|
|
325
|
+
so it yields to the prompts about things that are outright broken. -->
|
|
316
326
|
<div
|
|
317
327
|
v-if="workspace.ready && !needsGitHubInstall && !githubProbePending"
|
|
318
328
|
class="pointer-events-none absolute inset-x-0 top-0 z-40 flex flex-col items-center gap-2 px-4 pt-4"
|
|
@@ -320,6 +330,7 @@ watch(
|
|
|
320
330
|
<AiProvidersBanner />
|
|
321
331
|
<ProviderConfigBanner />
|
|
322
332
|
<InfraSetupBanner />
|
|
333
|
+
<DefaultTestEnvBanner />
|
|
323
334
|
</div>
|
|
324
335
|
|
|
325
336
|
<!-- Resolving whether the GitHub App is installed, before we decide what to show. -->
|
package/app/stores/ui/modals.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { ref } from 'vue'
|
|
2
2
|
import type { DocumentSourceKind, InfraSetupArea, TaskSourceKind } from '~/types/domain'
|
|
3
3
|
import type { PendingContext } from '~/composables/useContextLinking'
|
|
4
|
+
import {
|
|
5
|
+
DEFAULT_PROVISION_DEEP_LINK_PARAM,
|
|
6
|
+
DEFAULT_PROVISION_DEEP_LINK_VALUE,
|
|
7
|
+
} from '~/utils/defaultProvisioning'
|
|
4
8
|
|
|
5
9
|
/** Values used to seed the add-task form when it is opened from another surface. */
|
|
6
10
|
export interface AddTaskPrefill {
|
|
@@ -761,6 +765,29 @@ function createInfraModals(resetHubReturn: ResetHubReturn) {
|
|
|
761
765
|
const qs = params.toString()
|
|
762
766
|
history.replaceState(null, '', window.location.pathname + (qs ? `?${qs}` : ''))
|
|
763
767
|
}
|
|
768
|
+
// Open the Infrastructure window straight at the default test-environment provisioning
|
|
769
|
+
// mechanism (the section at the top of the Test-environments tab). Distinct from
|
|
770
|
+
// `openProviderConnection('environment')` only in intent today, but it is the target of a
|
|
771
|
+
// SHAREABLE deep link (see `consumeDefaultProvisionDeepLink`), so it gets its own entry point
|
|
772
|
+
// rather than leaving the banner and the URL to duplicate the tab choice independently.
|
|
773
|
+
function openDefaultProvisionSettings() {
|
|
774
|
+
resetHubReturn()
|
|
775
|
+
infrastructureTab.value = 'environment'
|
|
776
|
+
infrastructureOpen.value = true
|
|
777
|
+
}
|
|
778
|
+
// Capture the `?settings=default-test-env` deep link on app load — the URL the setup banner
|
|
779
|
+
// shows, so an operator can send "go configure this" to a teammate rather than describing
|
|
780
|
+
// where the screen lives. Strips the param afterwards (mirroring the k3s hand-off above) so a
|
|
781
|
+
// reload doesn't re-open the window and the link isn't left in history. No-op when absent.
|
|
782
|
+
function consumeDefaultProvisionDeepLink() {
|
|
783
|
+
if (typeof window === 'undefined') return
|
|
784
|
+
const params = new URLSearchParams(window.location.search)
|
|
785
|
+
if (params.get(DEFAULT_PROVISION_DEEP_LINK_PARAM) !== DEFAULT_PROVISION_DEEP_LINK_VALUE) return
|
|
786
|
+
openDefaultProvisionSettings()
|
|
787
|
+
params.delete(DEFAULT_PROVISION_DEEP_LINK_PARAM)
|
|
788
|
+
const qs = params.toString()
|
|
789
|
+
history.replaceState(null, '', window.location.pathname + (qs ? `?${qs}` : ''))
|
|
790
|
+
}
|
|
764
791
|
// Launch the environment setup wizard, optionally preselecting the service frame it targets
|
|
765
792
|
// (the inspector nudge passes the frame; the navbar entry opens it with the pick step active).
|
|
766
793
|
function openEnvironmentSetup(frameId: string | null = null) {
|
|
@@ -781,6 +808,8 @@ function createInfraModals(resetHubReturn: ResetHubReturn) {
|
|
|
781
808
|
consumeK3sSetupDeepLink,
|
|
782
809
|
environmentWizardOpen,
|
|
783
810
|
environmentWizardFrameId,
|
|
811
|
+
openDefaultProvisionSettings,
|
|
812
|
+
consumeDefaultProvisionDeepLink,
|
|
784
813
|
openProviderConnection,
|
|
785
814
|
closeProviderConnection,
|
|
786
815
|
openEnvironmentSetup,
|
|
@@ -815,6 +844,20 @@ function createAiOnboardingModals() {
|
|
|
815
844
|
infraSetupSessionDismissed.value = []
|
|
816
845
|
}
|
|
817
846
|
|
|
847
|
+
// Default-test-environment banner: a single per-SESSION dismissal, cleared on workspace switch
|
|
848
|
+
// like the flags above. There is deliberately no PERMANENT dismissal here (unlike the
|
|
849
|
+
// infra-setup areas): the prompt asks for a DECISION, and every answer — including `infraless`
|
|
850
|
+
// ("services stand up no environment") — is recordable in one click, so "silence this forever
|
|
851
|
+
// without answering" would only ever produce a board nobody can tell apart from an unconfigured
|
|
852
|
+
// one. Dismissing hides it until the next load.
|
|
853
|
+
const defaultProvisionDismissed = ref(false)
|
|
854
|
+
function dismissDefaultProvision() {
|
|
855
|
+
defaultProvisionDismissed.value = true
|
|
856
|
+
}
|
|
857
|
+
function resetDefaultProvisionDismissal() {
|
|
858
|
+
defaultProvisionDismissed.value = false
|
|
859
|
+
}
|
|
860
|
+
|
|
818
861
|
function openAiProviderSetup() {
|
|
819
862
|
aiProviderSetupOpen.value = true
|
|
820
863
|
}
|
|
@@ -857,6 +900,9 @@ function createAiOnboardingModals() {
|
|
|
857
900
|
infraSetupSessionDismissed,
|
|
858
901
|
dismissInfraSetupForSession,
|
|
859
902
|
resetInfraSetupDismissals,
|
|
903
|
+
defaultProvisionDismissed,
|
|
904
|
+
dismissDefaultProvision,
|
|
905
|
+
resetDefaultProvisionDismissal,
|
|
860
906
|
openAiProviderSetup,
|
|
861
907
|
closeAiProviderSetup,
|
|
862
908
|
openAiPresetMismatch,
|
|
@@ -20,6 +20,11 @@ const DEFAULTS: WorkspaceSettings = {
|
|
|
20
20
|
reviewFrictionBlockStuckMinutes: null,
|
|
21
21
|
spendCurrency: null,
|
|
22
22
|
spendMonthlyLimit: null,
|
|
23
|
+
// Null means the operator has never chosen a default test-environment provisioning mechanism,
|
|
24
|
+
// which is what the setup banner nags about. Defaulting it to a type here would silence that
|
|
25
|
+
// banner on every board before the snapshot even lands.
|
|
26
|
+
defaultProvisionType: null,
|
|
27
|
+
defaultProvisionManifestId: null,
|
|
23
28
|
}
|
|
24
29
|
|
|
25
30
|
/**
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import type { CustomManifestType } from '@cat-factory/contracts'
|
|
3
|
+
import {
|
|
4
|
+
canSaveDefaultProvisioning,
|
|
5
|
+
needsDefaultProvisioningChoice,
|
|
6
|
+
suggestDefaultProvisioning,
|
|
7
|
+
} from './defaultProvisioning'
|
|
8
|
+
|
|
9
|
+
function customType(
|
|
10
|
+
manifestId: string,
|
|
11
|
+
source: CustomManifestType['source'] = 'registered',
|
|
12
|
+
): CustomManifestType {
|
|
13
|
+
return { manifestId, label: manifestId, source }
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
describe('needsDefaultProvisioningChoice', () => {
|
|
17
|
+
it('an unset default still owes a decision', () => {
|
|
18
|
+
expect(needsDefaultProvisioningChoice({ defaultProvisionType: null })).toBe(true)
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
it('an explicit infraless is a decision, not an absence', () => {
|
|
22
|
+
// The case that separates "nobody chose" from "we chose no environments" — get this wrong
|
|
23
|
+
// and a workspace that deliberately runs infraless is nagged forever.
|
|
24
|
+
expect(needsDefaultProvisioningChoice({ defaultProvisionType: 'infraless' })).toBe(false)
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('any recorded type settles it', () => {
|
|
28
|
+
expect(needsDefaultProvisioningChoice({ defaultProvisionType: 'kubernetes' })).toBe(false)
|
|
29
|
+
})
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
describe('suggestDefaultProvisioning', () => {
|
|
33
|
+
const unset = { defaultProvisionType: null, defaultProvisionManifestId: null }
|
|
34
|
+
|
|
35
|
+
it('opens on the recorded choice rather than re-suggesting over it', () => {
|
|
36
|
+
expect(
|
|
37
|
+
suggestDefaultProvisioning(
|
|
38
|
+
{ defaultProvisionType: 'kubernetes', defaultProvisionManifestId: null },
|
|
39
|
+
[customType('acme-preview')],
|
|
40
|
+
),
|
|
41
|
+
).toEqual({ type: 'kubernetes', manifestId: null })
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it('round-trips a recorded custom choice with its manifest id', () => {
|
|
45
|
+
expect(
|
|
46
|
+
suggestDefaultProvisioning(
|
|
47
|
+
{ defaultProvisionType: 'custom', defaultProvisionManifestId: 'acme-preview' },
|
|
48
|
+
[customType('acme-preview'), customType('other')],
|
|
49
|
+
),
|
|
50
|
+
).toEqual({ type: 'custom', manifestId: 'acme-preview' })
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
it('suggests the first registered custom provider when nothing is selected', () => {
|
|
54
|
+
expect(
|
|
55
|
+
suggestDefaultProvisioning(unset, [customType('acme-preview'), customType('acme-legacy')]),
|
|
56
|
+
).toEqual({ type: 'custom', manifestId: 'acme-preview' })
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
it('prefers a registered provider over a hand-authored workspace type', () => {
|
|
60
|
+
// A registered type is a deployment-level integration; a workspace row is one someone typed
|
|
61
|
+
// into the UI. Order in the catalog must not decide which one we recommend.
|
|
62
|
+
expect(
|
|
63
|
+
suggestDefaultProvisioning(unset, [
|
|
64
|
+
customType('typed-by-hand', 'workspace'),
|
|
65
|
+
customType('acme-preview', 'registered'),
|
|
66
|
+
]),
|
|
67
|
+
).toEqual({ type: 'custom', manifestId: 'acme-preview' })
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
it('falls back to a workspace-defined type when no provider is registered', () => {
|
|
71
|
+
expect(suggestDefaultProvisioning(unset, [customType('typed-by-hand', 'workspace')])).toEqual({
|
|
72
|
+
type: 'custom',
|
|
73
|
+
manifestId: 'typed-by-hand',
|
|
74
|
+
})
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
it('suggests nothing when there are no custom providers at all', () => {
|
|
78
|
+
// Deliberately does NOT guess a built-in: the picker offering `kubernetes` says nothing
|
|
79
|
+
// about whether this workspace's services actually use it.
|
|
80
|
+
expect(suggestDefaultProvisioning(unset, [])).toEqual({ type: null, manifestId: null })
|
|
81
|
+
})
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
describe('canSaveDefaultProvisioning', () => {
|
|
85
|
+
it('refuses an empty selection', () => {
|
|
86
|
+
expect(canSaveDefaultProvisioning({ type: null, manifestId: null })).toBe(false)
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
it('refuses custom with no manifest id, mirroring the server rule', () => {
|
|
90
|
+
expect(canSaveDefaultProvisioning({ type: 'custom', manifestId: null })).toBe(false)
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
it('accepts custom once a manifest type is named', () => {
|
|
94
|
+
expect(canSaveDefaultProvisioning({ type: 'custom', manifestId: 'acme-preview' })).toBe(true)
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
it('accepts a built-in type, including infraless', () => {
|
|
98
|
+
expect(canSaveDefaultProvisioning({ type: 'infraless', manifestId: null })).toBe(true)
|
|
99
|
+
})
|
|
100
|
+
})
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import type { CustomManifestType, ProvisionType, WorkspaceSettings } from '@cat-factory/contracts'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The workspace-level default test-environment provisioning mechanism: what the SPA nags about
|
|
5
|
+
* when it is unset, and what the config section preselects when the operator first opens it.
|
|
6
|
+
*
|
|
7
|
+
* Pure so both consumers — the banner's visibility and the section's initial selection — agree
|
|
8
|
+
* on the same notion of "nothing chosen yet" without either re-deriving it.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* The query param that deep-links straight to the default-provisioning config section
|
|
13
|
+
* (`?settings=default-test-env`). Defined here — beside the logic both ends share — so the
|
|
14
|
+
* banner that BUILDS the URL and the ui store that CONSUMES it can never drift; a mismatch
|
|
15
|
+
* would produce a link that silently opens nothing.
|
|
16
|
+
*/
|
|
17
|
+
export const DEFAULT_PROVISION_DEEP_LINK_PARAM = 'settings'
|
|
18
|
+
export const DEFAULT_PROVISION_DEEP_LINK_VALUE = 'default-test-env'
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* The shareable absolute URL for the config screen. Built from the CURRENT location (rather
|
|
22
|
+
* than a configured base) so it is correct for whatever origin/path this SPA is actually served
|
|
23
|
+
* from — a deployment mounted under a sub-path included. Returns just the query string when
|
|
24
|
+
* there is no `window` (SSR is off, but the util stays callable in a unit test).
|
|
25
|
+
*/
|
|
26
|
+
export function defaultProvisioningConfigUrl(location?: {
|
|
27
|
+
origin: string
|
|
28
|
+
pathname: string
|
|
29
|
+
}): string {
|
|
30
|
+
const query = `?${DEFAULT_PROVISION_DEEP_LINK_PARAM}=${DEFAULT_PROVISION_DEEP_LINK_VALUE}`
|
|
31
|
+
const loc = location ?? (typeof window === 'undefined' ? undefined : window.location)
|
|
32
|
+
return loc ? `${loc.origin}${loc.pathname}${query}` : query
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** The section's editable selection: a provision type plus, for `custom`, the pinned manifest id. */
|
|
36
|
+
export interface DefaultProvisioningSelection {
|
|
37
|
+
type: ProvisionType | null
|
|
38
|
+
manifestId: string | null
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Whether the workspace still owes a decision. `null` means the operator has never chosen —
|
|
43
|
+
* distinct from an explicit `infraless` ("services stand up no environment"), which is a real
|
|
44
|
+
* choice and silences the prompt. See the contracts block comment on `defaultProvisionType`.
|
|
45
|
+
*/
|
|
46
|
+
export function needsDefaultProvisioningChoice(
|
|
47
|
+
settings: Pick<WorkspaceSettings, 'defaultProvisionType'>,
|
|
48
|
+
): boolean {
|
|
49
|
+
return settings.defaultProvisionType == null
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* The selection the config section opens on.
|
|
54
|
+
*
|
|
55
|
+
* A recorded choice always wins — the section is an editor for it, not a recommender that
|
|
56
|
+
* overrides what the workspace already decided.
|
|
57
|
+
*
|
|
58
|
+
* With nothing recorded, a deployment that has REGISTERED custom providers is telling us it
|
|
59
|
+
* brought its own environment tooling, and that tooling is almost always what its services
|
|
60
|
+
* should use — so the first one is preselected rather than making the operator discover the
|
|
61
|
+
* `custom` tab and then pick from a list only they can interpret. `registered` (code-defined,
|
|
62
|
+
* shipped by the deployment) is preferred over `workspace` (a row somebody typed into the UI):
|
|
63
|
+
* only the former is evidence of a deliberate platform-level integration. Falling back to the
|
|
64
|
+
* first workspace-defined type keeps the suggestion useful on a board whose only custom type
|
|
65
|
+
* was authored by hand.
|
|
66
|
+
*
|
|
67
|
+
* With no custom providers at all there is nothing to suggest, so the section opens UNSET and
|
|
68
|
+
* the operator picks from the built-in types. It deliberately does not guess a built-in: unlike
|
|
69
|
+
* a registered custom provider, the presence of `kubernetes` in the picker says nothing about
|
|
70
|
+
* whether this workspace's services use it.
|
|
71
|
+
*
|
|
72
|
+
* Nothing here is persisted — this is the form's initial value, and the operator still saves.
|
|
73
|
+
*/
|
|
74
|
+
export function suggestDefaultProvisioning(
|
|
75
|
+
settings: Pick<WorkspaceSettings, 'defaultProvisionType' | 'defaultProvisionManifestId'>,
|
|
76
|
+
customTypes: readonly CustomManifestType[],
|
|
77
|
+
): DefaultProvisioningSelection {
|
|
78
|
+
if (settings.defaultProvisionType != null) {
|
|
79
|
+
return {
|
|
80
|
+
type: settings.defaultProvisionType,
|
|
81
|
+
manifestId: settings.defaultProvisionManifestId,
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
const suggested =
|
|
85
|
+
customTypes.find((t) => t.source === 'registered') ?? customTypes[0] ?? undefined
|
|
86
|
+
return suggested
|
|
87
|
+
? { type: 'custom', manifestId: suggested.manifestId }
|
|
88
|
+
: { type: null, manifestId: null }
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Whether a selection can be saved. Mirrors the server's cross-field rule so the button
|
|
93
|
+
* disables instead of round-tripping to a 422: `custom` must name the manifest type it uses,
|
|
94
|
+
* and there is nothing to save until a type is picked.
|
|
95
|
+
*/
|
|
96
|
+
export function canSaveDefaultProvisioning(selection: DefaultProvisioningSelection): boolean {
|
|
97
|
+
if (selection.type == null) return false
|
|
98
|
+
return selection.type !== 'custom' || !!selection.manifestId
|
|
99
|
+
}
|
package/i18n/locales/de.json
CHANGED
|
@@ -934,6 +934,15 @@
|
|
|
934
934
|
},
|
|
935
935
|
"saved": "Modellzugriffsrichtlinie gespeichert",
|
|
936
936
|
"saveFailed": "Modellzugriffsrichtlinie konnte nicht gespeichert werden"
|
|
937
|
+
},
|
|
938
|
+
"defaultProvision": {
|
|
939
|
+
"title": "Standard für neue Services",
|
|
940
|
+
"hint": "Der Bereitstellungsmechanismus, der für jeden künftig hinzugefügten Service vorgeschlagen wird. Bereits vorhandene Services bleiben unverändert, und jeder Service kann davon abweichen.",
|
|
941
|
+
"suggestionHint": "Das ist bislang nur ein Vorschlag. Speichern Sie ihn, um ihn als Standard des Boards zu hinterlegen.",
|
|
942
|
+
"save": "Standard speichern",
|
|
943
|
+
"saved": "Standard-Testumgebung gespeichert.",
|
|
944
|
+
"savedState": "Gespeichert.",
|
|
945
|
+
"saveFailed": "Die Standard-Testumgebung konnte nicht gespeichert werden."
|
|
937
946
|
}
|
|
938
947
|
},
|
|
939
948
|
"inspector": {
|
|
@@ -2156,6 +2165,11 @@
|
|
|
2156
2165
|
"add": "Mitglied konnte nicht hinzugefügt werden",
|
|
2157
2166
|
"remove": "Mitglied konnte nicht entfernt werden"
|
|
2158
2167
|
}
|
|
2168
|
+
},
|
|
2169
|
+
"defaultTestEnvBanner": {
|
|
2170
|
+
"title": "Standard-Testumgebung wählen",
|
|
2171
|
+
"body": "Für dieses Board ist noch nicht festgelegt, wie seine Services eine Testumgebung erhalten. Wählen Sie einen Standard, dann startet jeder künftig hinzugefügte Service damit, statt einzeln eingerichtet zu werden. Pro Service lässt sich das weiterhin ändern.",
|
|
2172
|
+
"action": "Standard wählen"
|
|
2159
2173
|
}
|
|
2160
2174
|
},
|
|
2161
2175
|
"board": {
|
package/i18n/locales/en.json
CHANGED
|
@@ -2190,6 +2190,11 @@
|
|
|
2190
2190
|
"add": "Could not add member",
|
|
2191
2191
|
"remove": "Could not remove member"
|
|
2192
2192
|
}
|
|
2193
|
+
},
|
|
2194
|
+
"defaultTestEnvBanner": {
|
|
2195
|
+
"title": "Pick a default test environment",
|
|
2196
|
+
"body": "This board hasn't chosen how its services get a test environment. Pick a default and every service you add from now on starts with it, instead of being set up one at a time. You can still change it per service.",
|
|
2197
|
+
"action": "Choose default"
|
|
2193
2198
|
}
|
|
2194
2199
|
},
|
|
2195
2200
|
"settings": {
|
|
@@ -3139,6 +3144,15 @@
|
|
|
3139
3144
|
},
|
|
3140
3145
|
"saved": "Model access policy saved",
|
|
3141
3146
|
"saveFailed": "Could not save the model access policy"
|
|
3147
|
+
},
|
|
3148
|
+
"defaultProvision": {
|
|
3149
|
+
"title": "Default for new services",
|
|
3150
|
+
"hint": "The provisioning mechanism suggested for every service you add from now on. It does not change services that already exist, and each service can override it.",
|
|
3151
|
+
"suggestionHint": "This is a suggestion so far. Save it to record it as the board's default.",
|
|
3152
|
+
"save": "Save default",
|
|
3153
|
+
"saved": "Default test environment saved.",
|
|
3154
|
+
"savedState": "Saved.",
|
|
3155
|
+
"saveFailed": "Could not save the default test environment."
|
|
3142
3156
|
}
|
|
3143
3157
|
},
|
|
3144
3158
|
"providers": {
|
package/i18n/locales/es.json
CHANGED
|
@@ -2121,6 +2121,11 @@
|
|
|
2121
2121
|
"add": "No se pudo añadir el miembro",
|
|
2122
2122
|
"remove": "No se pudo quitar el miembro"
|
|
2123
2123
|
}
|
|
2124
|
+
},
|
|
2125
|
+
"defaultTestEnvBanner": {
|
|
2126
|
+
"title": "Elige un entorno de pruebas predeterminado",
|
|
2127
|
+
"body": "Este tablero aún no ha definido cómo obtienen un entorno de pruebas sus servicios. Elige un valor predeterminado y todos los servicios que añadas a partir de ahora empezarán con él, en lugar de configurarse uno a uno. Podrás cambiarlo en cada servicio.",
|
|
2128
|
+
"action": "Elegir predeterminado"
|
|
2124
2129
|
}
|
|
2125
2130
|
},
|
|
2126
2131
|
"settings": {
|
|
@@ -3046,6 +3051,15 @@
|
|
|
3046
3051
|
},
|
|
3047
3052
|
"saved": "Política de acceso a modelos guardada",
|
|
3048
3053
|
"saveFailed": "No se pudo guardar la política de acceso a modelos"
|
|
3054
|
+
},
|
|
3055
|
+
"defaultProvision": {
|
|
3056
|
+
"title": "Predeterminado para servicios nuevos",
|
|
3057
|
+
"hint": "El mecanismo de aprovisionamiento que se sugerirá para cada servicio que añadas a partir de ahora. No modifica los servicios ya existentes y cada servicio puede sustituirlo.",
|
|
3058
|
+
"suggestionHint": "Por ahora es solo una sugerencia. Guárdala para registrarla como predeterminado del tablero.",
|
|
3059
|
+
"save": "Guardar predeterminado",
|
|
3060
|
+
"saved": "Entorno de pruebas predeterminado guardado.",
|
|
3061
|
+
"savedState": "Guardado.",
|
|
3062
|
+
"saveFailed": "No se ha podido guardar el entorno de pruebas predeterminado."
|
|
3049
3063
|
}
|
|
3050
3064
|
},
|
|
3051
3065
|
"providers": {
|
package/i18n/locales/fr.json
CHANGED
|
@@ -2121,6 +2121,11 @@
|
|
|
2121
2121
|
"add": "Impossible d'ajouter le membre",
|
|
2122
2122
|
"remove": "Impossible de retirer le membre"
|
|
2123
2123
|
}
|
|
2124
|
+
},
|
|
2125
|
+
"defaultTestEnvBanner": {
|
|
2126
|
+
"title": "Choisissez un environnement de test par défaut",
|
|
2127
|
+
"body": "Ce tableau n'a pas encore défini comment ses services obtiennent un environnement de test. Choisissez une valeur par défaut et chaque service ajouté ensuite démarrera avec elle, au lieu d'être configuré un par un. Vous pourrez toujours la modifier service par service.",
|
|
2128
|
+
"action": "Choisir la valeur par défaut"
|
|
2124
2129
|
}
|
|
2125
2130
|
},
|
|
2126
2131
|
"settings": {
|
|
@@ -3046,6 +3051,15 @@
|
|
|
3046
3051
|
},
|
|
3047
3052
|
"saved": "Politique d'accès aux modèles enregistrée",
|
|
3048
3053
|
"saveFailed": "Impossible d'enregistrer la politique d'accès aux modèles"
|
|
3054
|
+
},
|
|
3055
|
+
"defaultProvision": {
|
|
3056
|
+
"title": "Valeur par défaut pour les nouveaux services",
|
|
3057
|
+
"hint": "Le mécanisme de provisionnement suggéré pour chaque service ajouté à partir de maintenant. Il ne modifie pas les services existants et chaque service peut le remplacer.",
|
|
3058
|
+
"suggestionHint": "Ce n'est qu'une suggestion pour l'instant. Enregistrez-la pour en faire la valeur par défaut du tableau.",
|
|
3059
|
+
"save": "Enregistrer la valeur par défaut",
|
|
3060
|
+
"saved": "Environnement de test par défaut enregistré.",
|
|
3061
|
+
"savedState": "Enregistré.",
|
|
3062
|
+
"saveFailed": "Impossible d'enregistrer l'environnement de test par défaut."
|
|
3049
3063
|
}
|
|
3050
3064
|
},
|
|
3051
3065
|
"providers": {
|
package/i18n/locales/he.json
CHANGED
|
@@ -2121,6 +2121,11 @@
|
|
|
2121
2121
|
"add": "לא ניתן להוסיף את החבר",
|
|
2122
2122
|
"remove": "לא ניתן להסיר את החבר"
|
|
2123
2123
|
}
|
|
2124
|
+
},
|
|
2125
|
+
"defaultTestEnvBanner": {
|
|
2126
|
+
"title": "בחרו סביבת בדיקות ברירת מחדל",
|
|
2127
|
+
"body": "בלוח הזה עדיין לא נקבע כיצד השירותים שלו מקבלים סביבת בדיקות. בחרו ברירת מחדל, וכל שירות שתוסיפו מכאן ואילך יתחיל איתה במקום להיות מוגדר אחד אחד. תמיד אפשר לשנות זאת עבור שירות מסוים.",
|
|
2128
|
+
"action": "בחירת ברירת מחדל"
|
|
2124
2129
|
}
|
|
2125
2130
|
},
|
|
2126
2131
|
"settings": {
|
|
@@ -3057,6 +3062,15 @@
|
|
|
3057
3062
|
},
|
|
3058
3063
|
"saved": "מדיניות הגישה למודלים נשמרה",
|
|
3059
3064
|
"saveFailed": "לא ניתן היה לשמור את מדיניות הגישה למודלים"
|
|
3065
|
+
},
|
|
3066
|
+
"defaultProvision": {
|
|
3067
|
+
"title": "ברירת מחדל לשירותים חדשים",
|
|
3068
|
+
"hint": "מנגנון ההקצאה שיוצע לכל שירות שתוסיפו מכאן ואילך. הוא אינו משנה שירותים קיימים, וכל שירות יכול לעקוף אותו.",
|
|
3069
|
+
"suggestionHint": "בשלב זה זו הצעה בלבד. שמרו אותה כדי לקבוע אותה כברירת המחדל של הלוח.",
|
|
3070
|
+
"save": "שמירת ברירת מחדל",
|
|
3071
|
+
"saved": "סביבת הבדיקות שנקבעה כברירת מחדל נשמרה.",
|
|
3072
|
+
"savedState": "נשמר.",
|
|
3073
|
+
"saveFailed": "לא ניתן היה לשמור את סביבת הבדיקות שנקבעה כברירת מחדל."
|
|
3060
3074
|
}
|
|
3061
3075
|
},
|
|
3062
3076
|
"providers": {
|
package/i18n/locales/it.json
CHANGED
|
@@ -934,6 +934,15 @@
|
|
|
934
934
|
},
|
|
935
935
|
"saved": "Policy di accesso ai modelli salvata",
|
|
936
936
|
"saveFailed": "Impossibile salvare la policy di accesso ai modelli"
|
|
937
|
+
},
|
|
938
|
+
"defaultProvision": {
|
|
939
|
+
"title": "Predefinito per i nuovi servizi",
|
|
940
|
+
"hint": "Il meccanismo di provisioning suggerito per ogni servizio che aggiungerai d'ora in poi. Non modifica i servizi già esistenti e ogni servizio può sostituirlo.",
|
|
941
|
+
"suggestionHint": "Per ora è solo un suggerimento. Salvalo per registrarlo come predefinito della board.",
|
|
942
|
+
"save": "Salva predefinito",
|
|
943
|
+
"saved": "Ambiente di test predefinito salvato.",
|
|
944
|
+
"savedState": "Salvato.",
|
|
945
|
+
"saveFailed": "Impossibile salvare l'ambiente di test predefinito."
|
|
937
946
|
}
|
|
938
947
|
},
|
|
939
948
|
"inspector": {
|
|
@@ -2156,6 +2165,11 @@
|
|
|
2156
2165
|
"add": "Impossibile aggiungere il membro",
|
|
2157
2166
|
"remove": "Impossibile rimuovere il membro"
|
|
2158
2167
|
}
|
|
2168
|
+
},
|
|
2169
|
+
"defaultTestEnvBanner": {
|
|
2170
|
+
"title": "Scegli un ambiente di test predefinito",
|
|
2171
|
+
"body": "Questa board non ha ancora stabilito come i suoi servizi ottengono un ambiente di test. Scegli un valore predefinito e ogni servizio che aggiungerai partirà con quello, invece di essere configurato uno alla volta. Potrai comunque cambiarlo per singolo servizio.",
|
|
2172
|
+
"action": "Scegli predefinito"
|
|
2159
2173
|
}
|
|
2160
2174
|
},
|
|
2161
2175
|
"board": {
|
package/i18n/locales/ja.json
CHANGED
|
@@ -2121,6 +2121,11 @@
|
|
|
2121
2121
|
"add": "メンバーを追加できませんでした",
|
|
2122
2122
|
"remove": "メンバーを削除できませんでした"
|
|
2123
2123
|
}
|
|
2124
|
+
},
|
|
2125
|
+
"defaultTestEnvBanner": {
|
|
2126
|
+
"title": "既定のテスト環境を選択してください",
|
|
2127
|
+
"body": "このボードでは、サービスがテスト環境をどのように用意するかがまだ決まっていません。既定を選ぶと、以降に追加するサービスは個別に設定しなくてもその設定で始まります。サービスごとの変更も引き続き可能です。",
|
|
2128
|
+
"action": "既定を選択"
|
|
2124
2129
|
}
|
|
2125
2130
|
},
|
|
2126
2131
|
"settings": {
|
|
@@ -3058,6 +3063,15 @@
|
|
|
3058
3063
|
},
|
|
3059
3064
|
"saved": "モデルアクセスポリシーを保存しました",
|
|
3060
3065
|
"saveFailed": "モデルアクセスポリシーを保存できませんでした"
|
|
3066
|
+
},
|
|
3067
|
+
"defaultProvision": {
|
|
3068
|
+
"title": "新規サービスの既定",
|
|
3069
|
+
"hint": "以降に追加するサービスに提案されるプロビジョニング方式です。既存のサービスは変更されず、サービスごとに上書きできます。",
|
|
3070
|
+
"suggestionHint": "現時点では提案にすぎません。保存するとボードの既定として記録されます。",
|
|
3071
|
+
"save": "既定を保存",
|
|
3072
|
+
"saved": "既定のテスト環境を保存しました。",
|
|
3073
|
+
"savedState": "保存済み。",
|
|
3074
|
+
"saveFailed": "既定のテスト環境を保存できませんでした。"
|
|
3061
3075
|
}
|
|
3062
3076
|
},
|
|
3063
3077
|
"providers": {
|
package/i18n/locales/pl.json
CHANGED
|
@@ -2121,6 +2121,11 @@
|
|
|
2121
2121
|
"add": "Nie udało się dodać członka",
|
|
2122
2122
|
"remove": "Nie udało się usunąć członka"
|
|
2123
2123
|
}
|
|
2124
|
+
},
|
|
2125
|
+
"defaultTestEnvBanner": {
|
|
2126
|
+
"title": "Wybierz domyślne środowisko testowe",
|
|
2127
|
+
"body": "Ta tablica nie ma jeszcze ustalonego sposobu, w jaki jej usługi otrzymują środowisko testowe. Wybierz wartość domyślną, a każda kolejno dodawana usługa będzie od niej startować, zamiast być konfigurowana pojedynczo. Nadal możesz to zmienić dla poszczególnej usługi.",
|
|
2128
|
+
"action": "Wybierz domyślne"
|
|
2124
2129
|
}
|
|
2125
2130
|
},
|
|
2126
2131
|
"settings": {
|
|
@@ -3046,6 +3051,15 @@
|
|
|
3046
3051
|
},
|
|
3047
3052
|
"saved": "Zapisano politykę dostępu do modeli",
|
|
3048
3053
|
"saveFailed": "Nie udało się zapisać polityki dostępu do modeli"
|
|
3054
|
+
},
|
|
3055
|
+
"defaultProvision": {
|
|
3056
|
+
"title": "Domyślne dla nowych usług",
|
|
3057
|
+
"hint": "Mechanizm udostępniania sugerowany dla każdej usługi dodanej od teraz. Nie zmienia już istniejących usług, a każda usługa może go nadpisać.",
|
|
3058
|
+
"suggestionHint": "To na razie tylko sugestia. Zapisz ją, aby zapisać jako domyślną dla tablicy.",
|
|
3059
|
+
"save": "Zapisz domyślne",
|
|
3060
|
+
"saved": "Zapisano domyślne środowisko testowe.",
|
|
3061
|
+
"savedState": "Zapisano.",
|
|
3062
|
+
"saveFailed": "Nie udało się zapisać domyślnego środowiska testowego."
|
|
3049
3063
|
}
|
|
3050
3064
|
},
|
|
3051
3065
|
"providers": {
|
package/i18n/locales/tr.json
CHANGED
|
@@ -2121,6 +2121,11 @@
|
|
|
2121
2121
|
"add": "Üye eklenemedi",
|
|
2122
2122
|
"remove": "Üye kaldırılamadı"
|
|
2123
2123
|
}
|
|
2124
|
+
},
|
|
2125
|
+
"defaultTestEnvBanner": {
|
|
2126
|
+
"title": "Varsayılan test ortamı seçin",
|
|
2127
|
+
"body": "Bu pano, servislerinin test ortamını nasıl edineceğini henüz belirlemedi. Bir varsayılan seçin; bundan sonra eklediğiniz her servis tek tek kurulmak yerine bununla başlasın. Servis bazında yine değiştirebilirsiniz.",
|
|
2128
|
+
"action": "Varsayılanı seç"
|
|
2124
2129
|
}
|
|
2125
2130
|
},
|
|
2126
2131
|
"settings": {
|
|
@@ -3058,6 +3063,15 @@
|
|
|
3058
3063
|
},
|
|
3059
3064
|
"saved": "Model erişim politikası kaydedildi",
|
|
3060
3065
|
"saveFailed": "Model erişim politikası kaydedilemedi"
|
|
3066
|
+
},
|
|
3067
|
+
"defaultProvision": {
|
|
3068
|
+
"title": "Yeni servisler için varsayılan",
|
|
3069
|
+
"hint": "Bundan sonra ekleyeceğiniz her servis için önerilecek sağlama mekanizması. Mevcut servisleri değiştirmez ve her servis bunu geçersiz kılabilir.",
|
|
3070
|
+
"suggestionHint": "Bu şimdilik yalnızca bir öneri. Panonun varsayılanı olarak kaydetmek için kaydedin.",
|
|
3071
|
+
"save": "Varsayılanı kaydet",
|
|
3072
|
+
"saved": "Varsayılan test ortamı kaydedildi.",
|
|
3073
|
+
"savedState": "Kaydedildi.",
|
|
3074
|
+
"saveFailed": "Varsayılan test ortamı kaydedilemedi."
|
|
3061
3075
|
}
|
|
3062
3076
|
},
|
|
3063
3077
|
"providers": {
|
package/i18n/locales/uk.json
CHANGED
|
@@ -2121,6 +2121,11 @@
|
|
|
2121
2121
|
"add": "Не вдалося додати учасника",
|
|
2122
2122
|
"remove": "Не вдалося вилучити учасника"
|
|
2123
2123
|
}
|
|
2124
|
+
},
|
|
2125
|
+
"defaultTestEnvBanner": {
|
|
2126
|
+
"title": "Оберіть типове тестове середовище",
|
|
2127
|
+
"body": "Ця дошка ще не визначила, як її сервіси отримують тестове середовище. Оберіть типове значення, і кожен доданий надалі сервіс починатиме з нього, замість налаштування по одному. Для окремого сервісу це завжди можна змінити.",
|
|
2128
|
+
"action": "Обрати типове"
|
|
2124
2129
|
}
|
|
2125
2130
|
},
|
|
2126
2131
|
"settings": {
|
|
@@ -3046,6 +3051,15 @@
|
|
|
3046
3051
|
},
|
|
3047
3052
|
"saved": "Політику доступу до моделей збережено",
|
|
3048
3053
|
"saveFailed": "Не вдалося зберегти політику доступу до моделей"
|
|
3054
|
+
},
|
|
3055
|
+
"defaultProvision": {
|
|
3056
|
+
"title": "Типове для нових сервісів",
|
|
3057
|
+
"hint": "Механізм розгортання, який пропонуватиметься для кожного сервісу, доданого відтепер. Він не змінює вже наявні сервіси, і кожен сервіс може його перевизначити.",
|
|
3058
|
+
"suggestionHint": "Наразі це лише пропозиція. Збережіть її, щоб записати як типове значення дошки.",
|
|
3059
|
+
"save": "Зберегти типове",
|
|
3060
|
+
"saved": "Типове тестове середовище збережено.",
|
|
3061
|
+
"savedState": "Збережено.",
|
|
3062
|
+
"saveFailed": "Не вдалося зберегти типове тестове середовище."
|
|
3049
3063
|
}
|
|
3050
3064
|
},
|
|
3051
3065
|
"providers": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cat-factory/app",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.172.0",
|
|
4
4
|
"description": "Reusable Nuxt layer for the Agent Architecture Board SPA (components, stores, composables, pages). Consume it from a thin deployment app via `extends: ['@cat-factory/app']` and point it at your backend with NUXT_PUBLIC_API_BASE. See deploy/frontend for an example.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"valibot": "^1.4.2",
|
|
41
41
|
"vue": "3.5.40",
|
|
42
42
|
"wretch": "^3.0.9",
|
|
43
|
-
"@cat-factory/contracts": "0.
|
|
43
|
+
"@cat-factory/contracts": "0.184.0"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
46
|
"@toad-contracts/testing": "0.3.2",
|