@swiss-ai-hub/web 0.319.0 → 0.321.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/{pages/auth/login.vue → components/Auth/LoginPanel.vue} +3 -33
- package/components/FormKit/AgentSelector.vue +20 -7
- package/components/FormKit/CronInput.vue +208 -0
- package/components/FormKit/Repeater.vue +1 -4
- package/components/FormKit/VectorStoreInput.vue +43 -6
- package/components/Knowledge/Database/CreateModal.vue +315 -0
- package/components/Knowledge/Database/EmptyCard.vue +34 -0
- package/components/Knowledge/DeleteConfirmModal.vue +87 -0
- package/components/Knowledge/Document/List.vue +20 -9
- package/components/Knowledge/Namespace/Card.vue +18 -0
- package/components/Knowledge/Namespace/CreateModal.vue +11 -3
- package/components/Role/AccessCapabilities.vue +4 -4
- package/components/Role/AccessCapabilityGroup.vue +22 -6
- package/components/Role/AccessRulesEditor.vue +15 -12
- package/composables/app/useAppVersion.ts +37 -20
- package/composables/auth/useAuth.ts +3 -11
- package/composables/document/useCreateDatabase.ts +21 -0
- package/composables/document/useDeleteDatabase.ts +29 -0
- package/composables/document/useDeleteDocument.ts +1 -1
- package/composables/document/useDeleteDocuments.ts +1 -1
- package/composables/document/useDeleteNamespace.ts +30 -0
- package/composables/document/useIngestors.ts +23 -0
- package/composables/document/useScheduledDeletions.ts +16 -8
- package/composables/form/useCreateInstanceForm.ts +2 -2
- package/composables/form/useFormKitTransform.ts +44 -15
- package/formkit.config.ts +57 -3
- package/i18n/locales/de.yaml +84 -5
- package/i18n/locales/en.yaml +80 -3
- package/i18n/locales/fr.yaml +82 -3
- package/i18n/locales/it.yaml +84 -3
- package/middleware/agent-admin.ts +31 -0
- package/middleware/auth.global.ts +34 -16
- package/package.json +1 -1
- package/pages/[tenant]/service/agents.vue +3 -0
- package/pages/[tenant]/service/knowledge/[db]/[namespace]/[document_id].vue +12 -5
- package/pages/[tenant]/service/knowledge.vue +138 -0
- package/pages/auth/login/[idp].vue +57 -0
- package/pages/auth/login/index.vue +46 -0
- package/plugins/0.runtime-config.client.ts +5 -0
- package/plugins/oidc-client.ts +1 -1
- package/sdk/client/client/client.gen.ts +1 -3
- package/sdk/client/client/types.gen.ts +7 -13
- package/sdk/client/client/utils.gen.ts +1 -2
- package/sdk/client/core/queryKeySerializer.gen.ts +1 -6
- package/sdk/client/core/types.gen.ts +5 -3
- package/sdk/client/index.ts +47 -4
- package/sdk/client/schemas.gen.ts +1787 -780
- package/sdk/client/sdk.gen.ts +231 -1
- package/sdk/client/types.gen.ts +1103 -226
- package/plugins/keycloak-client.ts +0 -41
|
@@ -21,36 +21,14 @@
|
|
|
21
21
|
</a>
|
|
22
22
|
<div class="flex flex-col gap-2 text-center">
|
|
23
23
|
<h2 class="text-xl text-white">
|
|
24
|
-
|
|
24
|
+
<slot name="heading" />
|
|
25
25
|
</h2>
|
|
26
26
|
<p class="text-surface-400">
|
|
27
|
-
|
|
27
|
+
<slot name="message" />
|
|
28
28
|
</p>
|
|
29
29
|
</div>
|
|
30
30
|
<div class="flex flex-col gap-3">
|
|
31
|
-
<
|
|
32
|
-
v-if="isLoading"
|
|
33
|
-
class="!h-8 !w-8"
|
|
34
|
-
/>
|
|
35
|
-
<template v-else>
|
|
36
|
-
<Button
|
|
37
|
-
v-for="idp in authProviders ?? []"
|
|
38
|
-
:key="idp.alias"
|
|
39
|
-
:label="t('auth.login.loginWith', { provider: idp.display_name })"
|
|
40
|
-
:icon="`pi ${idp.icon}`"
|
|
41
|
-
icon-pos="right"
|
|
42
|
-
class="!bg-white !text-black"
|
|
43
|
-
@click="login(idp.alias || undefined)"
|
|
44
|
-
/>
|
|
45
|
-
<Button
|
|
46
|
-
v-if="(authProviders?.length ?? 0) === 0"
|
|
47
|
-
:label="t('auth.login.title')"
|
|
48
|
-
icon="pi pi-sign-in"
|
|
49
|
-
icon-pos="right"
|
|
50
|
-
class="!bg-white !text-black"
|
|
51
|
-
@click="login()"
|
|
52
|
-
/>
|
|
53
|
-
</template>
|
|
31
|
+
<slot />
|
|
54
32
|
</div>
|
|
55
33
|
</div>
|
|
56
34
|
<a
|
|
@@ -68,13 +46,5 @@
|
|
|
68
46
|
<script setup lang="ts">
|
|
69
47
|
import logo from '@core/assets/images/logo.png'
|
|
70
48
|
|
|
71
|
-
definePageMeta({
|
|
72
|
-
layout: 'anonymous',
|
|
73
|
-
})
|
|
74
|
-
|
|
75
49
|
const { t } = useI18n()
|
|
76
|
-
const { login } = useAuth()
|
|
77
|
-
const { authProviders, isLoading } = useAuthProviders()
|
|
78
|
-
|
|
79
|
-
const companyName = 'bbv Software Services AG'
|
|
80
50
|
</script>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="flex flex-col gap-4">
|
|
3
|
-
<!-- Agent Class Selection -->
|
|
4
|
-
<div>
|
|
3
|
+
<!-- Agent Class Selection (omitted when the config pins the class) -->
|
|
4
|
+
<div v-if="!pinnedClass">
|
|
5
5
|
<label
|
|
6
6
|
for="agent-class-select"
|
|
7
7
|
class="mb-1 block text-sm font-medium"
|
|
@@ -139,6 +139,7 @@ interface AgentSelectorProps {
|
|
|
139
139
|
value?: AgentSelectorValue | null
|
|
140
140
|
attrs: Record<string, unknown>
|
|
141
141
|
startEvent?: string
|
|
142
|
+
agentClass?: string
|
|
142
143
|
classPlaceholder?: string
|
|
143
144
|
idPlaceholder?: string
|
|
144
145
|
filter?: boolean
|
|
@@ -151,6 +152,9 @@ const { tenantId } = useTenant()
|
|
|
151
152
|
|
|
152
153
|
// Get custom props from context (FormKit passes them there, not as direct props)
|
|
153
154
|
const startEvent = computed(() => props.context.startEvent)
|
|
155
|
+
// Set by configs that already know which blueprint answers: the class dropdown is not rendered and only
|
|
156
|
+
// this class's profiles are offered.
|
|
157
|
+
const pinnedClass = computed(() => props.context.agentClass)
|
|
154
158
|
const classPlaceholder = computed(() => props.context.classPlaceholder)
|
|
155
159
|
const idPlaceholder = computed(() => props.context.idPlaceholder)
|
|
156
160
|
const filter = computed(() => props.context.filter ?? true)
|
|
@@ -166,7 +170,7 @@ const currentValue = computed(() => props.context.value ?? null)
|
|
|
166
170
|
|
|
167
171
|
// Selected class (computed property that syncs with the form value)
|
|
168
172
|
const selectedClass = computed({
|
|
169
|
-
get: () => currentValue.value?.agent_class ?? null,
|
|
173
|
+
get: () => currentValue.value?.agent_class ?? pinnedClass.value ?? null,
|
|
170
174
|
set: (value: string | null) => {
|
|
171
175
|
if (value) {
|
|
172
176
|
// When class changes, reset agent ID and fetch instances
|
|
@@ -290,16 +294,25 @@ async function fetchInstances(agentClass: string) {
|
|
|
290
294
|
|
|
291
295
|
// Fetch classes on mount
|
|
292
296
|
onMounted(() => {
|
|
293
|
-
|
|
297
|
+
// A pinned class needs no class list — nothing renders it, and the profile dropdown is driven by
|
|
298
|
+
// fetchInstances alone.
|
|
299
|
+
if (!pinnedClass.value) {
|
|
300
|
+
fetchClasses()
|
|
301
|
+
}
|
|
294
302
|
|
|
295
|
-
//
|
|
296
|
-
|
|
297
|
-
|
|
303
|
+
// Pinned, or already chosen: either way the profile dropdown needs that class's instances up front.
|
|
304
|
+
const classToLoad = currentValue.value?.agent_class ?? pinnedClass.value
|
|
305
|
+
if (classToLoad) {
|
|
306
|
+
fetchInstances(classToLoad)
|
|
298
307
|
}
|
|
299
308
|
})
|
|
300
309
|
|
|
301
310
|
// Refetch if startEvent changes
|
|
302
311
|
watch(startEvent, () => {
|
|
312
|
+
// A pinned class is not filtered by start event, and its class list is never fetched — so the check below
|
|
313
|
+
// would find no match and clear a perfectly valid selection.
|
|
314
|
+
if (pinnedClass.value) return
|
|
315
|
+
|
|
303
316
|
// Clear selection if current class no longer matches filter
|
|
304
317
|
if (selectedClass.value && !filteredClassOptions.value.some(opt => opt.name === selectedClass.value)) {
|
|
305
318
|
props.context.node.input(null)
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="flex flex-col gap-4">
|
|
3
|
+
<div class="grid grid-cols-2 gap-3 sm:grid-cols-5">
|
|
4
|
+
<div
|
|
5
|
+
v-for="position in POSITIONS"
|
|
6
|
+
:key="position.key"
|
|
7
|
+
>
|
|
8
|
+
<label
|
|
9
|
+
:for="`cron-${position.key}`"
|
|
10
|
+
class="mb-1 block text-sm font-medium"
|
|
11
|
+
>
|
|
12
|
+
{{ t(`lib.cron.${position.key}.label`) }}
|
|
13
|
+
</label>
|
|
14
|
+
<InputText
|
|
15
|
+
:id="`cron-${position.key}`"
|
|
16
|
+
:model-value="schedule[position.key]"
|
|
17
|
+
:aria-label="t(`lib.cron.${position.key}.label`)"
|
|
18
|
+
:invalid="Boolean(errors[position.key])"
|
|
19
|
+
:aria-invalid="Boolean(errors[position.key])"
|
|
20
|
+
class="w-full font-mono"
|
|
21
|
+
@update:model-value="(value) => updatePosition(position.key, value ?? '')"
|
|
22
|
+
/>
|
|
23
|
+
<small
|
|
24
|
+
v-if="errors[position.key]"
|
|
25
|
+
class="mt-1 block text-red-500"
|
|
26
|
+
>
|
|
27
|
+
{{ errors[position.key] }}
|
|
28
|
+
</small>
|
|
29
|
+
<small
|
|
30
|
+
v-else
|
|
31
|
+
class="mt-1 block text-surface-500"
|
|
32
|
+
>{{ position.range }}</small>
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
|
|
36
|
+
<div>
|
|
37
|
+
<label
|
|
38
|
+
for="cron-timezone"
|
|
39
|
+
class="mb-1 block text-sm font-medium"
|
|
40
|
+
>
|
|
41
|
+
{{ t('lib.cron.timezone.label') }}
|
|
42
|
+
</label>
|
|
43
|
+
<Select
|
|
44
|
+
v-model="timezone"
|
|
45
|
+
:aria-label="t('lib.cron.timezone.label')"
|
|
46
|
+
input-id="cron-timezone"
|
|
47
|
+
:options="timezoneOptions"
|
|
48
|
+
:placeholder="timezonePlaceholder ?? t('lib.cron.timezone.placeholder')"
|
|
49
|
+
:filter="filter"
|
|
50
|
+
class="w-full"
|
|
51
|
+
/>
|
|
52
|
+
<small class="mt-1 block text-surface-500">{{ t('lib.cron.timezone.help') }}</small>
|
|
53
|
+
</div>
|
|
54
|
+
|
|
55
|
+
<small class="text-surface-500">
|
|
56
|
+
{{ t('lib.cron.expression') }} <code class="font-mono">{{ expression }}</code>
|
|
57
|
+
</small>
|
|
58
|
+
</div>
|
|
59
|
+
</template>
|
|
60
|
+
|
|
61
|
+
<script setup lang="ts">
|
|
62
|
+
/**
|
|
63
|
+
* Editor for a backend `CronSchedule`: the five cron positions plus an IANA timezone.
|
|
64
|
+
*
|
|
65
|
+
* The emitted value is the schedule object in the backend's own field names, so it round-trips through
|
|
66
|
+
* `config_data` untouched — the scheduler reads those keys directly. Positions are kept separate rather than
|
|
67
|
+
* joined into one "0 12 * * *" string because that is how `CronSchedule` stores them, and flattening here would
|
|
68
|
+
* make every consumer parse it back apart.
|
|
69
|
+
*
|
|
70
|
+
* Deliberately has no presets and no plain-language summary — that is #1581, which extends this component rather
|
|
71
|
+
* than replacing it.
|
|
72
|
+
*/
|
|
73
|
+
|
|
74
|
+
import { createMessage } from '@formkit/core'
|
|
75
|
+
|
|
76
|
+
import type { FormKitNode } from '@formkit/core'
|
|
77
|
+
|
|
78
|
+
interface CronSchedule {
|
|
79
|
+
minute: string
|
|
80
|
+
hour: string
|
|
81
|
+
day_of_month: string
|
|
82
|
+
month: string
|
|
83
|
+
day_of_week: string
|
|
84
|
+
timezone: string
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
type CronPosition = Exclude<keyof CronSchedule, 'timezone'>
|
|
88
|
+
|
|
89
|
+
interface CronInputProps {
|
|
90
|
+
context: {
|
|
91
|
+
node: FormKitNode
|
|
92
|
+
value?: CronSchedule | null
|
|
93
|
+
attrs: Record<string, unknown>
|
|
94
|
+
timezonePlaceholder?: string
|
|
95
|
+
filter?: boolean
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const props = defineProps<CronInputProps>()
|
|
100
|
+
const { t } = useI18n()
|
|
101
|
+
|
|
102
|
+
const POSITIONS: { key: CronPosition, range: string, min: number, max: number }[] = [
|
|
103
|
+
{ key: 'minute', range: '0-59', min: 0, max: 59 },
|
|
104
|
+
{ key: 'hour', range: '0-23', min: 0, max: 23 },
|
|
105
|
+
{ key: 'day_of_month', range: '1-31', min: 1, max: 31 },
|
|
106
|
+
{ key: 'month', range: '1-12', min: 1, max: 12 },
|
|
107
|
+
{ key: 'day_of_week', range: '0-6', min: 0, max: 6 },
|
|
108
|
+
]
|
|
109
|
+
|
|
110
|
+
// Deliberately permissive: croniter accepts names (MON-FRI, JAN), steps, lists and the day-of-week specials, and a
|
|
111
|
+
// stricter pattern here would reject schedules the backend is happy to run. This catches the two things that are
|
|
112
|
+
// wrong in every cron dialect — a blank position and a stray character — and leaves grammar to the server, which
|
|
113
|
+
// now rejects a malformed cron on save rather than storing it.
|
|
114
|
+
const ALLOWED_CHARACTERS = /^[0-9A-Za-z*,\-/?#]+$/
|
|
115
|
+
|
|
116
|
+
// Blocking so the surrounding form cannot be submitted while a position is empty or malformed. Without it the only
|
|
117
|
+
// feedback is a 400 from the save endpoint, and before that endpoint validated, a blank position was stored and
|
|
118
|
+
// silently broke every run of the profile — not only its scheduled ones.
|
|
119
|
+
const ERROR_MESSAGE_KEY = 'cronPositions'
|
|
120
|
+
|
|
121
|
+
// Shown in the inputs the moment the field is enabled, never applied behind the user's back. `CronSchedule`
|
|
122
|
+
// requires all five positions and has no defaults, so leaving them blank would save a schedule the scheduler then
|
|
123
|
+
// skips with only a log line to say why. Hourly is the least surprising visible starting point — `* * * * *` would
|
|
124
|
+
// commit an admin to a run every minute for merely ticking the enable box.
|
|
125
|
+
const HOURLY: CronSchedule = {
|
|
126
|
+
minute: '0',
|
|
127
|
+
hour: '*',
|
|
128
|
+
day_of_month: '*',
|
|
129
|
+
month: '*',
|
|
130
|
+
day_of_week: '*',
|
|
131
|
+
timezone: resolveBrowserTimezone(),
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const timezonePlaceholder = computed(() => props.context.timezonePlaceholder)
|
|
135
|
+
const filter = computed(() => props.context.filter ?? true)
|
|
136
|
+
|
|
137
|
+
const schedule = computed<CronSchedule>(() => props.context.value ?? HOURLY)
|
|
138
|
+
|
|
139
|
+
const errors = computed<Partial<Record<CronPosition, string>>>(() =>
|
|
140
|
+
Object.fromEntries(
|
|
141
|
+
POSITIONS.map(position => [position.key, positionError(position)]).filter(([, message]) => message),
|
|
142
|
+
),
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
const expression = computed(() =>
|
|
146
|
+
POSITIONS.map(position => schedule.value[position.key] || '?').join(' '),
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
const timezone = computed({
|
|
150
|
+
get: () => schedule.value.timezone,
|
|
151
|
+
set: (value: string) => emitValue({ ...schedule.value, timezone: value }),
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
const timezoneOptions = computed<string[]>(() => {
|
|
155
|
+
// Not every engine implements supportedValuesOf; without it the admin can still keep the resolved zone.
|
|
156
|
+
const supported = (Intl as { supportedValuesOf?: (key: string) => string[] }).supportedValuesOf
|
|
157
|
+
return supported ? supported('timeZone') : [schedule.value.timezone, 'UTC']
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
function resolveBrowserTimezone(): string {
|
|
161
|
+
try {
|
|
162
|
+
return Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC'
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
return 'UTC'
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function positionError(position: { key: CronPosition, min: number, max: number }): string | undefined {
|
|
170
|
+
const value = schedule.value[position.key]
|
|
171
|
+
if (!value) return t('lib.cron.errors.required')
|
|
172
|
+
if (!ALLOWED_CHARACTERS.test(value)) return t('lib.cron.errors.invalid')
|
|
173
|
+
|
|
174
|
+
// Only plain integers are range-checked. Anything with a step, range or name is left to the server, whose
|
|
175
|
+
// croniter knows the dialect far better than a regex here could.
|
|
176
|
+
const outOfRange = value
|
|
177
|
+
.split(',')
|
|
178
|
+
.some(term => /^\d+$/.test(term) && (Number(term) < position.min || Number(term) > position.max))
|
|
179
|
+
|
|
180
|
+
return outOfRange ? t('lib.cron.errors.range', { min: position.min, max: position.max }) : undefined
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function updatePosition(position: CronPosition, value: string) {
|
|
184
|
+
emitValue({ ...schedule.value, [position]: value.trim() })
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Always emits every key: a partial schedule passes the generated submission schema (which cannot carry
|
|
188
|
+
// CronSchedule's croniter and timezone validators) and is then rejected by the save endpoint.
|
|
189
|
+
function emitValue(value: CronSchedule) {
|
|
190
|
+
props.context.node.input({ ...value })
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
watch(errors, (current) => {
|
|
194
|
+
props.context.node.store.remove(ERROR_MESSAGE_KEY)
|
|
195
|
+
const first = Object.values(current)[0]
|
|
196
|
+
if (!first) return
|
|
197
|
+
|
|
198
|
+
props.context.node.store.set(
|
|
199
|
+
createMessage({ blocking: true, key: ERROR_MESSAGE_KEY, type: 'error', value: first, visible: true }),
|
|
200
|
+
)
|
|
201
|
+
}, { immediate: true })
|
|
202
|
+
|
|
203
|
+
// Seed the stored value so enabling the field yields a complete, valid schedule even if the admin saves without
|
|
204
|
+
// touching an input — what the inputs show and what gets saved must not diverge.
|
|
205
|
+
onMounted(() => {
|
|
206
|
+
if (!props.context.value) emitValue(HOURLY)
|
|
207
|
+
})
|
|
208
|
+
</script>
|
|
@@ -99,11 +99,8 @@ const isRemoveDisabled = computed(() => {
|
|
|
99
99
|
|
|
100
100
|
function addItem() {
|
|
101
101
|
if (isAddDisabled.value) return
|
|
102
|
-
if (!modelValue.value) {
|
|
103
|
-
modelValue.value = []
|
|
104
|
-
}
|
|
105
102
|
rowKeys.value.push(makeRowKey())
|
|
106
|
-
modelValue.value.
|
|
103
|
+
modelValue.value = [...items.value, props.defaultItem ? cloneDeep(props.defaultItem) : {}]
|
|
107
104
|
}
|
|
108
105
|
|
|
109
106
|
function removeItem(index: number) {
|
|
@@ -44,8 +44,28 @@
|
|
|
44
44
|
</Select>
|
|
45
45
|
</div>
|
|
46
46
|
|
|
47
|
-
<!-- Namespace
|
|
48
|
-
<div
|
|
47
|
+
<!-- Namespace scope (shown when database selected) -->
|
|
48
|
+
<div
|
|
49
|
+
v-if="selectedDatabase"
|
|
50
|
+
class="flex items-center gap-2"
|
|
51
|
+
>
|
|
52
|
+
<ToggleSwitch
|
|
53
|
+
v-model="allNamespaces"
|
|
54
|
+
input-id="vector-store-all-namespaces"
|
|
55
|
+
/>
|
|
56
|
+
<label
|
|
57
|
+
for="vector-store-all-namespaces"
|
|
58
|
+
class="text-sm"
|
|
59
|
+
>{{ t('lib.vectorStore.allNamespaces.label') }}</label>
|
|
60
|
+
</div>
|
|
61
|
+
<small
|
|
62
|
+
v-if="selectedDatabase && allNamespaces"
|
|
63
|
+
class="-mt-3 text-surface-500"
|
|
64
|
+
>
|
|
65
|
+
{{ t('lib.vectorStore.allNamespaces.help', { count: namespaceOptions.length }) }}
|
|
66
|
+
</small>
|
|
67
|
+
|
|
68
|
+
<div v-if="selectedDatabase && !allNamespaces">
|
|
49
69
|
<label
|
|
50
70
|
for="vector-store-namespaces-select"
|
|
51
71
|
class="mb-1 block text-sm font-medium"
|
|
@@ -104,6 +124,7 @@ import type { DatabaseDto } from '@core/sdk/client'
|
|
|
104
124
|
interface VectorStoreInputValue {
|
|
105
125
|
collection_name: string
|
|
106
126
|
index_namespaces: string[]
|
|
127
|
+
all_namespaces: boolean
|
|
107
128
|
allowed_metadata_filter_fields: string[]
|
|
108
129
|
}
|
|
109
130
|
|
|
@@ -154,7 +175,7 @@ const selectedDatabase = computed({
|
|
|
154
175
|
set: (value: string | null) => {
|
|
155
176
|
if (value) {
|
|
156
177
|
// When database changes, reset namespaces and allowed filter fields
|
|
157
|
-
emitValue(value, [], [])
|
|
178
|
+
emitValue(value, [], false, [])
|
|
158
179
|
}
|
|
159
180
|
else {
|
|
160
181
|
props.context.node.input(null)
|
|
@@ -162,12 +183,22 @@ const selectedDatabase = computed({
|
|
|
162
183
|
},
|
|
163
184
|
})
|
|
164
185
|
|
|
186
|
+
// Whole-database scope: switching it on discards any named namespaces so the two never coexist
|
|
187
|
+
const allNamespaces = computed({
|
|
188
|
+
get: () => currentValue.value?.all_namespaces ?? false,
|
|
189
|
+
set: (value: boolean) => {
|
|
190
|
+
if (selectedDatabase.value) {
|
|
191
|
+
emitValue(selectedDatabase.value, value ? [] : selectedNamespaces.value, value, allowedFilterFields.value)
|
|
192
|
+
}
|
|
193
|
+
},
|
|
194
|
+
})
|
|
195
|
+
|
|
165
196
|
// Selected namespaces (computed property that syncs with the form value)
|
|
166
197
|
const selectedNamespaces = computed({
|
|
167
198
|
get: () => currentValue.value?.index_namespaces ?? [],
|
|
168
199
|
set: (value: string[]) => {
|
|
169
200
|
if (selectedDatabase.value) {
|
|
170
|
-
emitValue(selectedDatabase.value, value, allowedFilterFields.value)
|
|
201
|
+
emitValue(selectedDatabase.value, value, false, allowedFilterFields.value)
|
|
171
202
|
}
|
|
172
203
|
},
|
|
173
204
|
})
|
|
@@ -179,7 +210,7 @@ const chipsInputContext = computed(() => ({
|
|
|
179
210
|
node: {
|
|
180
211
|
input: (value: string[]) => {
|
|
181
212
|
if (selectedDatabase.value) {
|
|
182
|
-
emitValue(selectedDatabase.value, selectedNamespaces.value, value)
|
|
213
|
+
emitValue(selectedDatabase.value, selectedNamespaces.value, allNamespaces.value, value)
|
|
183
214
|
}
|
|
184
215
|
},
|
|
185
216
|
},
|
|
@@ -189,10 +220,16 @@ const chipsInputContext = computed(() => ({
|
|
|
189
220
|
}))
|
|
190
221
|
|
|
191
222
|
// Emit complete value object
|
|
192
|
-
function emitValue(
|
|
223
|
+
function emitValue(
|
|
224
|
+
collectionName: string,
|
|
225
|
+
namespaces: string[],
|
|
226
|
+
wholeDatabase: boolean,
|
|
227
|
+
allowedFilterFieldKeys: string[],
|
|
228
|
+
) {
|
|
193
229
|
props.context.node.input({
|
|
194
230
|
collection_name: collectionName,
|
|
195
231
|
index_namespaces: namespaces,
|
|
232
|
+
all_namespaces: wholeDatabase,
|
|
196
233
|
allowed_metadata_filter_fields: allowedFilterFieldKeys,
|
|
197
234
|
})
|
|
198
235
|
}
|