@raclettejs/workbench 0.1.34 → 0.1.36-canary.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/CHANGELOG.md +18 -2
- package/config/compositions.js +18 -0
- package/config/interactionLinks.js +19 -0
- package/i18n/de-DE.json +15 -1
- package/i18n/en-EU.json +52 -1
- package/i18n/sk-SK.json +15 -1
- package/package.json +3 -4
- package/packages.json +3 -1
- package/plugins/pacifico__compositions/frontend/components/compositionConfiguration/CompositionConfiguration.vue +96 -14
- package/plugins/pacifico__compositions/frontend/components/compositionConfiguration/CompositionIntegration.vue +29 -1
- package/plugins/pacifico__compositions/frontend/components/compositionConfiguration/widgetsLayout/WidgetRenderer.vue +1 -6
- package/plugins/pacifico__compositions/frontend/composables/prefillInteractionLinkFromComposition.ts +54 -0
- package/plugins/pacifico__compositions/frontend/widgets/CompositionCreateWidget.vue +145 -9
- package/plugins/pacifico__compositions/frontend/widgets/CompositionEditWidget.vue +11 -2
- package/plugins/pacifico__compositions/frontend/widgets/CompositionListWidget.vue +26 -6
- package/plugins/pacifico__core/frontend/generated-config.ts +22 -0
- package/plugins/pacifico__interactionLinks/frontend/components/InteractionLinkConfiguration.vue +3 -65
- package/plugins/pacifico__interactionLinks/frontend/widgets/InteractionLinkListWidget.vue +1 -1
- package/plugins/pacifico__plugins/frontend/widgets/PluginListWidget.vue +21 -4
- package/plugins/pacifico__tags/frontend/widgets/TagListWidget.vue +1 -1
- package/plugins/pacifico__users/frontend/widgets/UserListWidget.vue +1 -1
- package/plugins/raclette__cache/backend/helpers/cacheEntryBuilder.ts +94 -0
- package/plugins/raclette__cache/backend/helpers/cacheEntrySanitizer.ts +86 -0
- package/plugins/raclette__cache/backend/helpers/valkeyExplorer.ts +89 -0
- package/plugins/raclette__cache/backend/index.ts +21 -0
- package/plugins/raclette__cache/backend/routes/route.cache.cacheEntries.get.ts +118 -0
- package/plugins/raclette__cache/backend/routes/route.cache.cacheEntry.get.ts +91 -0
- package/plugins/raclette__cache/backend/routes.ts +8 -0
- package/plugins/raclette__cache/frontend/components/TableColumnConfigDialog.vue +95 -0
- package/plugins/raclette__cache/frontend/composables/useTableColumnConfig.ts +66 -0
- package/plugins/raclette__cache/frontend/generated-config.ts +33 -0
- package/plugins/raclette__cache/frontend/utils/cacheValueTableRows.ts +340 -0
- package/plugins/raclette__cache/frontend/widgets/CacheListWidget.vue +742 -0
- package/plugins/raclette__cache/raclette.plugin.ts +8 -0
- package/services/frontend/src/app/components/dynamicForm/DynamicForm.vue +3 -1
- package/services/frontend/src/app/components/interactionLink/InteractionLinkBehaviorForm.vue +99 -0
- package/services/frontend/src/app/components/stepNavigator/StepNavigator.vue +175 -24
- package/services/frontend/src/app/components/stepNavigator/StepNavigatorTypes.ts +18 -0
- package/services/frontend/src/app/composables/useInteractionLinkBehaviorFields.ts +83 -0
- package/services/frontend/src/app/composables/useRacletteUserIsAdmin.ts +25 -0
- package/services/frontend/src/orchestrator/assets/styles/themes/dark.ts +1 -0
- package/services/frontend/src/orchestrator/assets/styles/themes/light.ts +1 -1
|
@@ -0,0 +1,742 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="tw:p-4 tw:pt-2">
|
|
3
|
+
<v-alert
|
|
4
|
+
v-if="showValueTruncatedWarning"
|
|
5
|
+
type="warning"
|
|
6
|
+
density="compact"
|
|
7
|
+
class="tw:mb-3"
|
|
8
|
+
:text="$t('workbench.cacheList.truncatedDetailHint')"
|
|
9
|
+
/>
|
|
10
|
+
|
|
11
|
+
<template v-if="!isValueView">
|
|
12
|
+
<BaseDataTable
|
|
13
|
+
:items="tableItems"
|
|
14
|
+
:exportable-items="exportableItems"
|
|
15
|
+
:loading="isLoading"
|
|
16
|
+
:headers="listHeaders"
|
|
17
|
+
:data-name="$t('workbench.cacheList.dataName')"
|
|
18
|
+
:show-filters="true"
|
|
19
|
+
:show-actions-column="true"
|
|
20
|
+
:actions-header-title="$t('workbench.cacheList.tableActions')"
|
|
21
|
+
:group-by="listAllKeys ? 'keyGroup' : undefined"
|
|
22
|
+
item-value="_id"
|
|
23
|
+
:row-click-handler="openDetail"
|
|
24
|
+
>
|
|
25
|
+
<template #toolbar-leading>
|
|
26
|
+
<v-select
|
|
27
|
+
v-model="db"
|
|
28
|
+
density="compact"
|
|
29
|
+
hide-details
|
|
30
|
+
variant="outlined"
|
|
31
|
+
class="tw:min-w-[5rem]"
|
|
32
|
+
:items="dbOptions"
|
|
33
|
+
:label="$t('workbench.cacheList.dbLabel')"
|
|
34
|
+
@update:model-value="reloadFromStart"
|
|
35
|
+
/>
|
|
36
|
+
<v-text-field
|
|
37
|
+
v-model="pattern"
|
|
38
|
+
density="compact"
|
|
39
|
+
hide-details
|
|
40
|
+
variant="outlined"
|
|
41
|
+
class="tw:min-w-[14rem]"
|
|
42
|
+
:label="$t('workbench.cacheList.patternLabel')"
|
|
43
|
+
:placeholder="$t('workbench.cacheList.patternPlaceholder')"
|
|
44
|
+
prepend-inner-icon="mdi-magnify"
|
|
45
|
+
clearable
|
|
46
|
+
@keyup.enter="reloadFromStart"
|
|
47
|
+
@click:clear="reloadFromStart"
|
|
48
|
+
/>
|
|
49
|
+
<v-select
|
|
50
|
+
v-model="limit"
|
|
51
|
+
density="compact"
|
|
52
|
+
hide-details
|
|
53
|
+
variant="outlined"
|
|
54
|
+
class="tw:min-w-[8rem]"
|
|
55
|
+
:items="batchSizeOptions"
|
|
56
|
+
:label="$t('workbench.cacheList.batchSizeLabel')"
|
|
57
|
+
@update:model-value="reloadFromStart"
|
|
58
|
+
/>
|
|
59
|
+
<v-autocomplete
|
|
60
|
+
v-model="pickerKey"
|
|
61
|
+
density="compact"
|
|
62
|
+
hide-details
|
|
63
|
+
variant="outlined"
|
|
64
|
+
class="tw:min-w-[16rem]"
|
|
65
|
+
:items="keyPickerItems"
|
|
66
|
+
:label="$t('workbench.cacheList.keyPickerLabel')"
|
|
67
|
+
@update:model-value="onKeyPickerSelect"
|
|
68
|
+
/>
|
|
69
|
+
<v-btn
|
|
70
|
+
variant="tonal"
|
|
71
|
+
prepend-icon="mdi-refresh"
|
|
72
|
+
:text="$t('workbench.cacheList.refresh')"
|
|
73
|
+
:loading="isLoading"
|
|
74
|
+
@click="reloadFromStart"
|
|
75
|
+
/>
|
|
76
|
+
</template>
|
|
77
|
+
<template #toolbar-end>
|
|
78
|
+
<v-btn
|
|
79
|
+
v-if="hasMore"
|
|
80
|
+
variant="outlined"
|
|
81
|
+
prepend-icon="mdi-chevron-down"
|
|
82
|
+
:text="$t('workbench.cacheList.loadMore')"
|
|
83
|
+
:loading="isLoadingMore"
|
|
84
|
+
@click="loadMore"
|
|
85
|
+
/>
|
|
86
|
+
<span
|
|
87
|
+
v-if="total !== null"
|
|
88
|
+
class="tw:text-sm tw:text-medium-emphasis tw:whitespace-nowrap"
|
|
89
|
+
>
|
|
90
|
+
{{
|
|
91
|
+
$t("workbench.cacheList.showingCount", {
|
|
92
|
+
shown: tableItems.length,
|
|
93
|
+
total,
|
|
94
|
+
})
|
|
95
|
+
}}
|
|
96
|
+
·
|
|
97
|
+
{{ $t("workbench.cacheList.dbActive", { db: activeDb }) }}
|
|
98
|
+
</span>
|
|
99
|
+
</template>
|
|
100
|
+
<template #item.value="{ item }">
|
|
101
|
+
<code class="tw:text-xs tw:break-all tw:line-clamp-2">
|
|
102
|
+
{{ displayValue(item) }}
|
|
103
|
+
</code>
|
|
104
|
+
<v-chip
|
|
105
|
+
v-if="item.truncated"
|
|
106
|
+
size="x-small"
|
|
107
|
+
color="warning"
|
|
108
|
+
class="tw:ml-1"
|
|
109
|
+
:text="$t('workbench.cacheList.truncated')"
|
|
110
|
+
/>
|
|
111
|
+
</template>
|
|
112
|
+
<template #row-actions="{ item }">
|
|
113
|
+
<v-tooltip
|
|
114
|
+
:text="$t('workbench.cacheList.openValueTable')"
|
|
115
|
+
location="top"
|
|
116
|
+
>
|
|
117
|
+
<template #activator="{ props: tooltipProps }">
|
|
118
|
+
<v-btn
|
|
119
|
+
v-bind="tooltipProps"
|
|
120
|
+
icon="mdi-table"
|
|
121
|
+
size="small"
|
|
122
|
+
variant="text"
|
|
123
|
+
color="primary"
|
|
124
|
+
:aria-label="openValueTableAriaLabel(item)"
|
|
125
|
+
@click.stop.prevent="openValueTable(item)"
|
|
126
|
+
/>
|
|
127
|
+
</template>
|
|
128
|
+
</v-tooltip>
|
|
129
|
+
</template>
|
|
130
|
+
</BaseDataTable>
|
|
131
|
+
|
|
132
|
+
<div class="tw:flex tw:justify-center tw:mt-2">
|
|
133
|
+
<v-btn
|
|
134
|
+
prepend-icon="mdi-table-cog"
|
|
135
|
+
rounded
|
|
136
|
+
variant="tonal"
|
|
137
|
+
color="primary"
|
|
138
|
+
@click="listConfigureDialogOpen = true"
|
|
139
|
+
>
|
|
140
|
+
{{ $t("workbench.cacheList.customizeTable") }}
|
|
141
|
+
<template v-if="listDisabledHeaders.length" #append>
|
|
142
|
+
({{ listDisabledHeaders.length }})
|
|
143
|
+
</template>
|
|
144
|
+
</v-btn>
|
|
145
|
+
</div>
|
|
146
|
+
|
|
147
|
+
<TableColumnConfigDialog
|
|
148
|
+
v-model="listConfigureDialogOpen"
|
|
149
|
+
:headers="listAllHeaders"
|
|
150
|
+
:disabled-headers="listDisabledHeaders"
|
|
151
|
+
@toggle-header="handleToggleListHeader"
|
|
152
|
+
@select-all="handleSelectAllListHeaders"
|
|
153
|
+
@deselect-all="handleDeselectAllListHeaders"
|
|
154
|
+
/>
|
|
155
|
+
</template>
|
|
156
|
+
|
|
157
|
+
<template v-else>
|
|
158
|
+
<div class="tw:flex tw:flex-wrap tw:gap-2 tw:items-center tw:justify-between tw:mb-3">
|
|
159
|
+
<div class="tw:flex tw:flex-wrap tw:gap-2 tw:items-center">
|
|
160
|
+
<v-btn
|
|
161
|
+
variant="tonal"
|
|
162
|
+
prepend-icon="mdi-arrow-left"
|
|
163
|
+
:text="$t('workbench.cacheList.backToList')"
|
|
164
|
+
@click="exitValueView"
|
|
165
|
+
/>
|
|
166
|
+
<v-autocomplete
|
|
167
|
+
v-model="pickerKey"
|
|
168
|
+
density="compact"
|
|
169
|
+
hide-details
|
|
170
|
+
variant="outlined"
|
|
171
|
+
class="tw:min-w-[16rem]"
|
|
172
|
+
:items="keyPickerItems"
|
|
173
|
+
:label="$t('workbench.cacheList.keyPickerLabel')"
|
|
174
|
+
clearable
|
|
175
|
+
@update:model-value="onKeyPickerSelect"
|
|
176
|
+
@click:clear="exitValueView"
|
|
177
|
+
/>
|
|
178
|
+
</div>
|
|
179
|
+
<span
|
|
180
|
+
v-if="valueTableEntry"
|
|
181
|
+
class="tw:text-sm tw:text-medium-emphasis tw:whitespace-nowrap"
|
|
182
|
+
>
|
|
183
|
+
{{ $t("workbench.cacheList.detailTtl") }}:
|
|
184
|
+
{{ valueTableEntry.ttlLabel }}
|
|
185
|
+
·
|
|
186
|
+
{{ $t("workbench.cacheList.dbActive", { db: activeDb }) }}
|
|
187
|
+
</span>
|
|
188
|
+
</div>
|
|
189
|
+
|
|
190
|
+
<v-progress-linear
|
|
191
|
+
v-if="isLoadingCacheEntry"
|
|
192
|
+
indeterminate
|
|
193
|
+
class="tw:mb-3"
|
|
194
|
+
/>
|
|
195
|
+
|
|
196
|
+
<BaseDataTable
|
|
197
|
+
v-if="!valueShowPrettyPrint"
|
|
198
|
+
:items="valueTableItems"
|
|
199
|
+
:headers="visibleValueTableHeaders"
|
|
200
|
+
:loading="isLoadingCacheEntry"
|
|
201
|
+
:data-name="valueTableDataName"
|
|
202
|
+
:show-filters="valueTableItems.length > 1"
|
|
203
|
+
:show-export="false"
|
|
204
|
+
:show-actions-column="false"
|
|
205
|
+
item-value="_id"
|
|
206
|
+
>
|
|
207
|
+
<template
|
|
208
|
+
v-for="header in visibleValueTableHeaders"
|
|
209
|
+
:key="header.key"
|
|
210
|
+
#[`item.${header.key}`]="{ item }"
|
|
211
|
+
>
|
|
212
|
+
<code class="tw:text-xs tw:break-all tw:whitespace-pre-wrap">{{
|
|
213
|
+
formatValueTableCell(item[header.key])
|
|
214
|
+
}}</code>
|
|
215
|
+
</template>
|
|
216
|
+
</BaseDataTable>
|
|
217
|
+
|
|
218
|
+
<pre
|
|
219
|
+
v-else-if="valueTableEntry && !isLoadingCacheEntry"
|
|
220
|
+
class="tw:text-xs tw:overflow-auto tw:max-h-[calc(100vh-16rem)] tw:p-3 tw:rounded tw:bg-surface-variant"
|
|
221
|
+
>{{ formatDetailValue(valueTableEntry) }}</pre>
|
|
222
|
+
|
|
223
|
+
<div v-if="!valueShowPrettyPrint" class="tw:flex tw:justify-center tw:mt-2">
|
|
224
|
+
<v-btn
|
|
225
|
+
prepend-icon="mdi-table-cog"
|
|
226
|
+
rounded
|
|
227
|
+
variant="tonal"
|
|
228
|
+
color="primary"
|
|
229
|
+
@click="valueConfigureDialogOpen = true"
|
|
230
|
+
>
|
|
231
|
+
{{ $t("workbench.cacheList.customizeTable") }}
|
|
232
|
+
<template v-if="valueDisabledHeaders.length" #append>
|
|
233
|
+
({{ valueDisabledHeaders.length }})
|
|
234
|
+
</template>
|
|
235
|
+
</v-btn>
|
|
236
|
+
</div>
|
|
237
|
+
|
|
238
|
+
<TableColumnConfigDialog
|
|
239
|
+
v-if="!valueShowPrettyPrint"
|
|
240
|
+
v-model="valueConfigureDialogOpen"
|
|
241
|
+
:headers="valueTableAllHeaders"
|
|
242
|
+
:disabled-headers="valueDisabledHeaders"
|
|
243
|
+
@toggle-header="handleToggleValueHeader"
|
|
244
|
+
@select-all="handleSelectAllValueHeaders"
|
|
245
|
+
@deselect-all="handleDeselectAllValueHeaders"
|
|
246
|
+
/>
|
|
247
|
+
</template>
|
|
248
|
+
|
|
249
|
+
<v-dialog v-model="detailOpen" max-width="720">
|
|
250
|
+
<v-card v-if="selectedEntry">
|
|
251
|
+
<v-card-title class="tw:break-all">
|
|
252
|
+
{{ selectedEntry.key }}
|
|
253
|
+
</v-card-title>
|
|
254
|
+
<v-card-subtitle>
|
|
255
|
+
{{ $t("workbench.cacheList.detailTtl") }}:
|
|
256
|
+
{{ selectedEntry.ttlLabel }}
|
|
257
|
+
</v-card-subtitle>
|
|
258
|
+
<v-card-text>
|
|
259
|
+
<v-alert
|
|
260
|
+
v-if="selectedEntry.truncated"
|
|
261
|
+
type="warning"
|
|
262
|
+
density="compact"
|
|
263
|
+
class="tw:mb-3"
|
|
264
|
+
:text="$t('workbench.cacheList.truncatedDetailHint')"
|
|
265
|
+
/>
|
|
266
|
+
<pre class="tw:text-xs tw:overflow-auto tw:max-h-[24rem]">{{
|
|
267
|
+
formatDetailValue(selectedEntry)
|
|
268
|
+
}}</pre>
|
|
269
|
+
</v-card-text>
|
|
270
|
+
<v-card-actions>
|
|
271
|
+
<v-btn
|
|
272
|
+
variant="tonal"
|
|
273
|
+
color="primary"
|
|
274
|
+
prepend-icon="mdi-table"
|
|
275
|
+
:text="$t('workbench.cacheList.openValueTable')"
|
|
276
|
+
@click="openValueTableFromDetail"
|
|
277
|
+
/>
|
|
278
|
+
<v-spacer />
|
|
279
|
+
<v-btn :text="$t('workbench.global.back')" @click="detailOpen = false" />
|
|
280
|
+
</v-card-actions>
|
|
281
|
+
</v-card>
|
|
282
|
+
</v-dialog>
|
|
283
|
+
</div>
|
|
284
|
+
</template>
|
|
285
|
+
|
|
286
|
+
<script setup lang="ts">
|
|
287
|
+
import { usePluginApi } from "@raclettejs/core/orchestrator/composables"
|
|
288
|
+
import BaseDataTable from "@raclettejs/core/orchestrator/components/dataTable/BaseDataTable.vue"
|
|
289
|
+
import TableColumnConfigDialog from "../components/TableColumnConfigDialog.vue"
|
|
290
|
+
import { useTableColumnConfig } from "../composables/useTableColumnConfig"
|
|
291
|
+
import {
|
|
292
|
+
CACHE_KEY_COLUMN,
|
|
293
|
+
cacheEntriesValueTableRows,
|
|
294
|
+
isScalarFallbackResult,
|
|
295
|
+
type ValueTableHeader,
|
|
296
|
+
} from "../utils/cacheValueTableRows"
|
|
297
|
+
import { computed, onMounted, ref } from "vue"
|
|
298
|
+
import { useI18n } from "vue-i18n"
|
|
299
|
+
|
|
300
|
+
type CacheEntry = {
|
|
301
|
+
_id: string
|
|
302
|
+
key: string
|
|
303
|
+
keyGroup: string
|
|
304
|
+
value: string
|
|
305
|
+
valuePreview?: string
|
|
306
|
+
truncated: boolean
|
|
307
|
+
valueParsed?: unknown
|
|
308
|
+
ttl: number
|
|
309
|
+
ttlLabel: string
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
type CacheEntryPayloadItem = {
|
|
313
|
+
_id?: string
|
|
314
|
+
_source?: CacheEntry
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
type CacheEntriesBody = {
|
|
318
|
+
items?: CacheEntryPayloadItem[]
|
|
319
|
+
hits?: {
|
|
320
|
+
total: number
|
|
321
|
+
offset: number
|
|
322
|
+
limit: number
|
|
323
|
+
hasMore: boolean
|
|
324
|
+
instanceId: string
|
|
325
|
+
pattern: string
|
|
326
|
+
listAll: boolean
|
|
327
|
+
db: number
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
type CacheEntryBody = {
|
|
332
|
+
items?: CacheEntryPayloadItem[]
|
|
333
|
+
hits?: {
|
|
334
|
+
total: number
|
|
335
|
+
key: string
|
|
336
|
+
db: number
|
|
337
|
+
found: boolean
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
type CacheEntriesResponse = {
|
|
342
|
+
body?: CacheEntriesBody
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
type CacheEntryResponse = {
|
|
346
|
+
body?: CacheEntryBody
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
type ExecuteResult = {
|
|
350
|
+
result?: CacheEntry[] | { _source?: CacheEntry }[]
|
|
351
|
+
/** Axios response (useStore) or payload (useStore: false). */
|
|
352
|
+
response?: CacheEntriesResponse | CacheEntryResponse | { data?: CacheEntriesResponse | CacheEntryResponse }
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
const responsePayload = (
|
|
356
|
+
result: ExecuteResult | undefined,
|
|
357
|
+
): CacheEntriesResponse | CacheEntryResponse | undefined => {
|
|
358
|
+
const raw = result?.response
|
|
359
|
+
if (!raw) return undefined
|
|
360
|
+
if ("body" in raw) return raw as CacheEntriesResponse | CacheEntryResponse
|
|
361
|
+
return (raw as { data?: CacheEntriesResponse | CacheEntryResponse }).data
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
defineProps<{
|
|
365
|
+
uuid: string
|
|
366
|
+
}>()
|
|
367
|
+
|
|
368
|
+
const { t } = useI18n()
|
|
369
|
+
const { $data } = usePluginApi()
|
|
370
|
+
|
|
371
|
+
const { execute, isLoading, clear } = $data.cacheEntries.get({
|
|
372
|
+
options: { useStore: true },
|
|
373
|
+
})
|
|
374
|
+
|
|
375
|
+
const { execute: fetchCacheEntry, isLoading: isLoadingCacheEntry } =
|
|
376
|
+
$data.cacheEntry.get({
|
|
377
|
+
options: { useStore: false },
|
|
378
|
+
})
|
|
379
|
+
|
|
380
|
+
const pattern = ref("")
|
|
381
|
+
const db = ref(0)
|
|
382
|
+
const dbOptions = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
|
|
383
|
+
const activeDb = ref(0)
|
|
384
|
+
const listAllKeys = ref(true)
|
|
385
|
+
const tableItems = ref<CacheEntry[]>([])
|
|
386
|
+
const exportableItems = ref<CacheEntry[]>([])
|
|
387
|
+
const total = ref<number | null>(null)
|
|
388
|
+
const hasMore = ref(false)
|
|
389
|
+
const offset = ref(0)
|
|
390
|
+
const batchSizeOptions = [25, 50, 100, 200, 500]
|
|
391
|
+
const limit = ref(100)
|
|
392
|
+
const isLoadingMore = ref(false)
|
|
393
|
+
const detailOpen = ref(false)
|
|
394
|
+
const selectedEntry = ref<CacheEntry | null>(null)
|
|
395
|
+
const pickerKey = ref<string | null>(null)
|
|
396
|
+
const valueTableEntry = ref<CacheEntry | null>(null)
|
|
397
|
+
const valueTableItems = ref<Record<string, string>[]>([])
|
|
398
|
+
const valueTableAllHeaders = ref<ValueTableHeader[]>([])
|
|
399
|
+
const valueShowPrettyPrint = ref(false)
|
|
400
|
+
|
|
401
|
+
const {
|
|
402
|
+
configureDialogOpen: listConfigureDialogOpen,
|
|
403
|
+
disabledHeaders: listDisabledHeaders,
|
|
404
|
+
filterHeaders: filterListHeaders,
|
|
405
|
+
toggleHeader: handleToggleListHeader,
|
|
406
|
+
selectAll: selectAllListHeaders,
|
|
407
|
+
deselectAll: deselectAllListHeaders,
|
|
408
|
+
} = useTableColumnConfig("workbench-cache-list-hidden-columns")
|
|
409
|
+
|
|
410
|
+
const {
|
|
411
|
+
configureDialogOpen: valueConfigureDialogOpen,
|
|
412
|
+
disabledHeaders: valueDisabledHeaders,
|
|
413
|
+
filterHeaders: filterValueHeaders,
|
|
414
|
+
toggleHeader: handleToggleValueHeader,
|
|
415
|
+
selectAll: selectAllValueHeaders,
|
|
416
|
+
deselectAll: deselectAllValueHeaders,
|
|
417
|
+
} = useTableColumnConfig("workbench-cache-value-hidden-columns")
|
|
418
|
+
|
|
419
|
+
const handleSelectAllListHeaders = () => {
|
|
420
|
+
selectAllListHeaders(listAllHeaders.value)
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
const handleDeselectAllListHeaders = () => {
|
|
424
|
+
deselectAllListHeaders(listAllHeaders.value)
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
const handleSelectAllValueHeaders = () => {
|
|
428
|
+
selectAllValueHeaders(valueTableAllHeaders.value)
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
const handleDeselectAllValueHeaders = () => {
|
|
432
|
+
deselectAllValueHeaders(valueTableAllHeaders.value)
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
const isValueView = computed(() => valueTableEntry.value !== null)
|
|
436
|
+
|
|
437
|
+
/** Only warn when transport string was truncated and full parsed data is unavailable. */
|
|
438
|
+
const showValueTruncatedWarning = computed(() => {
|
|
439
|
+
const entry = valueTableEntry.value
|
|
440
|
+
if (!isValueView.value || !entry?.truncated) {
|
|
441
|
+
return false
|
|
442
|
+
}
|
|
443
|
+
return entry.valueParsed === undefined
|
|
444
|
+
})
|
|
445
|
+
|
|
446
|
+
const valueTableDataName = computed(() => {
|
|
447
|
+
const key = valueTableEntry.value?.key
|
|
448
|
+
if (!key) {
|
|
449
|
+
return t("workbench.cacheList.valueTableTitle")
|
|
450
|
+
}
|
|
451
|
+
return `${t("workbench.cacheList.valueTableTitle")}: ${key}`
|
|
452
|
+
})
|
|
453
|
+
|
|
454
|
+
const keyPickerItems = computed(() => {
|
|
455
|
+
const keys = new Set(tableItems.value.map((row) => row.key))
|
|
456
|
+
if (pickerKey.value?.trim()) {
|
|
457
|
+
keys.add(pickerKey.value.trim())
|
|
458
|
+
}
|
|
459
|
+
return [...keys].sort((a, b) => a.localeCompare(b))
|
|
460
|
+
})
|
|
461
|
+
|
|
462
|
+
const exitValueView = () => {
|
|
463
|
+
valueTableEntry.value = null
|
|
464
|
+
valueTableItems.value = []
|
|
465
|
+
valueTableAllHeaders.value = []
|
|
466
|
+
valueShowPrettyPrint.value = false
|
|
467
|
+
pickerKey.value = null
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
const listAllHeaders = computed(() => {
|
|
471
|
+
const base = [
|
|
472
|
+
{
|
|
473
|
+
title: t("workbench.cacheList.tableHeaders.key"),
|
|
474
|
+
key: "key",
|
|
475
|
+
},
|
|
476
|
+
{
|
|
477
|
+
title: t("workbench.cacheList.tableHeaders.ttl"),
|
|
478
|
+
key: "ttlLabel",
|
|
479
|
+
},
|
|
480
|
+
{
|
|
481
|
+
title: t("workbench.cacheList.tableHeaders.value"),
|
|
482
|
+
key: "value",
|
|
483
|
+
},
|
|
484
|
+
]
|
|
485
|
+
|
|
486
|
+
if (!listAllKeys.value) {
|
|
487
|
+
return base
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
return [
|
|
491
|
+
{
|
|
492
|
+
title: t("workbench.cacheList.tableHeaders.keyGroup"),
|
|
493
|
+
key: "keyGroup",
|
|
494
|
+
},
|
|
495
|
+
...base,
|
|
496
|
+
]
|
|
497
|
+
})
|
|
498
|
+
|
|
499
|
+
const listHeaders = computed(() =>
|
|
500
|
+
filterListHeaders(listAllHeaders.value),
|
|
501
|
+
)
|
|
502
|
+
|
|
503
|
+
const visibleValueTableHeaders = computed(() =>
|
|
504
|
+
filterValueHeaders(valueTableAllHeaders.value),
|
|
505
|
+
)
|
|
506
|
+
|
|
507
|
+
const valueHeaderTitle = (key: string) => {
|
|
508
|
+
if (key === CACHE_KEY_COLUMN) {
|
|
509
|
+
return t("workbench.cacheList.tableHeaders.key")
|
|
510
|
+
}
|
|
511
|
+
if (key === "value") {
|
|
512
|
+
return t("workbench.cacheList.valueTableHeaders.value")
|
|
513
|
+
}
|
|
514
|
+
if (key === "entryKey") {
|
|
515
|
+
return t("workbench.cacheList.valueTableHeaders.entryKey")
|
|
516
|
+
}
|
|
517
|
+
if (key === "raw") {
|
|
518
|
+
return t("workbench.cacheList.valueTableHeaders.raw")
|
|
519
|
+
}
|
|
520
|
+
return key
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
const toTableRow = (entry: Partial<CacheEntry> & { key?: string }): CacheEntry => {
|
|
524
|
+
const key = entry.key ?? String(entry._id ?? "")
|
|
525
|
+
return {
|
|
526
|
+
_id: key,
|
|
527
|
+
key,
|
|
528
|
+
keyGroup: entry.keyGroup ?? "",
|
|
529
|
+
value: entry.value ?? "",
|
|
530
|
+
valuePreview: entry.valuePreview,
|
|
531
|
+
truncated: entry.truncated ?? false,
|
|
532
|
+
valueParsed: entry.valueParsed,
|
|
533
|
+
ttl: entry.ttl ?? -2,
|
|
534
|
+
ttlLabel: entry.ttlLabel ?? "",
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
const extractEntries = (result: ExecuteResult | undefined): CacheEntry[] => {
|
|
539
|
+
if (Array.isArray(result?.result) && result.result.length > 0) {
|
|
540
|
+
return result.result.map((row) =>
|
|
541
|
+
toTableRow(
|
|
542
|
+
(row as { _source?: CacheEntry })._source ??
|
|
543
|
+
(row as CacheEntry),
|
|
544
|
+
),
|
|
545
|
+
)
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
const items = responsePayload(result)?.body?.items ?? []
|
|
549
|
+
return items.map((item) =>
|
|
550
|
+
toTableRow(item._source ?? (item as unknown as CacheEntry)),
|
|
551
|
+
)
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
const extractSingleEntry = (
|
|
555
|
+
result: ExecuteResult | undefined,
|
|
556
|
+
): CacheEntry | null => {
|
|
557
|
+
const entries = extractEntries(result)
|
|
558
|
+
return entries[0] ?? null
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
const applyHits = (result: ExecuteResult | undefined) => {
|
|
562
|
+
const hits = responsePayload(result)?.body?.hits
|
|
563
|
+
if (!hits || !("hasMore" in hits)) {
|
|
564
|
+
return
|
|
565
|
+
}
|
|
566
|
+
total.value = hits.total ?? tableItems.value.length
|
|
567
|
+
hasMore.value = hits.hasMore ?? false
|
|
568
|
+
listAllKeys.value = hits.listAll ?? !pattern.value.trim()
|
|
569
|
+
activeDb.value = hits.db ?? db.value
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
const buildQueryParams = () => {
|
|
573
|
+
const trimmed = pattern.value.trim()
|
|
574
|
+
const params: Record<string, number | string> = {
|
|
575
|
+
db: db.value,
|
|
576
|
+
offset: offset.value,
|
|
577
|
+
limit: limit.value,
|
|
578
|
+
}
|
|
579
|
+
if (trimmed.length > 0) {
|
|
580
|
+
params.pattern = trimmed
|
|
581
|
+
}
|
|
582
|
+
return params
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
const fetchEntries = async (append: boolean) => {
|
|
586
|
+
const result = (await execute(buildQueryParams())) as ExecuteResult | undefined
|
|
587
|
+
const entries = extractEntries(result)
|
|
588
|
+
applyHits(result)
|
|
589
|
+
|
|
590
|
+
if (append) {
|
|
591
|
+
const existing = new Set(tableItems.value.map((row) => row._id))
|
|
592
|
+
const merged = [...tableItems.value]
|
|
593
|
+
for (const entry of entries) {
|
|
594
|
+
if (!existing.has(entry._id)) {
|
|
595
|
+
merged.push(entry)
|
|
596
|
+
existing.add(entry._id)
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
tableItems.value = merged
|
|
600
|
+
exportableItems.value = merged
|
|
601
|
+
} else {
|
|
602
|
+
tableItems.value = entries
|
|
603
|
+
exportableItems.value = entries
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
const reloadFromStart = async () => {
|
|
608
|
+
offset.value = 0
|
|
609
|
+
await clear()
|
|
610
|
+
try {
|
|
611
|
+
await fetchEntries(false)
|
|
612
|
+
} catch {
|
|
613
|
+
tableItems.value = []
|
|
614
|
+
exportableItems.value = []
|
|
615
|
+
total.value = null
|
|
616
|
+
hasMore.value = false
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
const loadMore = async () => {
|
|
621
|
+
if (!hasMore.value) return
|
|
622
|
+
isLoadingMore.value = true
|
|
623
|
+
offset.value += limit.value
|
|
624
|
+
try {
|
|
625
|
+
await fetchEntries(true)
|
|
626
|
+
} finally {
|
|
627
|
+
isLoadingMore.value = false
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
const displayValue = (item: CacheEntry) => {
|
|
632
|
+
if (item.truncated && item.valuePreview) {
|
|
633
|
+
return item.valuePreview
|
|
634
|
+
}
|
|
635
|
+
if (item.valueParsed !== undefined) {
|
|
636
|
+
return JSON.stringify(item.valueParsed)
|
|
637
|
+
}
|
|
638
|
+
return item.value
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
const formatDetailValue = (item: CacheEntry) => {
|
|
642
|
+
if (item.valueParsed !== undefined) {
|
|
643
|
+
return JSON.stringify(item.valueParsed, null, 2)
|
|
644
|
+
}
|
|
645
|
+
return item.value
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
const formatValueTableCell = (value: string | undefined) => value ?? ""
|
|
649
|
+
|
|
650
|
+
const openDetail = (item: CacheEntry) => {
|
|
651
|
+
selectedEntry.value = item
|
|
652
|
+
detailOpen.value = true
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
const openValueTableAriaLabel = (item: CacheEntry) =>
|
|
656
|
+
t("workbench.cacheList.openValueTableFor", { key: item.key })
|
|
657
|
+
|
|
658
|
+
const openValueTable = (item: CacheEntry) => {
|
|
659
|
+
void loadEntryForKey(item.key)
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
const openValueTableFromDetail = () => {
|
|
663
|
+
const entry = selectedEntry.value
|
|
664
|
+
if (!entry) {
|
|
665
|
+
return
|
|
666
|
+
}
|
|
667
|
+
detailOpen.value = false
|
|
668
|
+
void loadEntryForKey(entry.key)
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
const applyValueTable = (entry: CacheEntry) => {
|
|
672
|
+
const { rows, headers: shapeHeaders } = cacheEntriesValueTableRows(
|
|
673
|
+
[entry],
|
|
674
|
+
entry,
|
|
675
|
+
)
|
|
676
|
+
valueShowPrettyPrint.value = isScalarFallbackResult(rows, shapeHeaders, entry)
|
|
677
|
+
valueTableItems.value = rows
|
|
678
|
+
valueTableAllHeaders.value = shapeHeaders.map((header) => ({
|
|
679
|
+
key: header.key,
|
|
680
|
+
title: valueHeaderTitle(header.key),
|
|
681
|
+
}))
|
|
682
|
+
valueTableEntry.value = entry
|
|
683
|
+
pickerKey.value = entry.key
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
const stubEntryForKey = (key: string): CacheEntry => {
|
|
687
|
+
const listRow = tableItems.value.find((row) => row.key === key)
|
|
688
|
+
if (listRow) {
|
|
689
|
+
return listRow
|
|
690
|
+
}
|
|
691
|
+
return {
|
|
692
|
+
_id: key,
|
|
693
|
+
key,
|
|
694
|
+
keyGroup: "",
|
|
695
|
+
value: "",
|
|
696
|
+
truncated: false,
|
|
697
|
+
ttl: -2,
|
|
698
|
+
ttlLabel: "",
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
const loadEntryForKey = async (key: string) => {
|
|
703
|
+
const trimmed = key.trim()
|
|
704
|
+
if (!trimmed.length || isLoadingCacheEntry.value) {
|
|
705
|
+
return
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
pickerKey.value = trimmed
|
|
709
|
+
valueTableEntry.value = stubEntryForKey(trimmed)
|
|
710
|
+
valueShowPrettyPrint.value = false
|
|
711
|
+
|
|
712
|
+
try {
|
|
713
|
+
const result = (await fetchCacheEntry({
|
|
714
|
+
key: trimmed,
|
|
715
|
+
db: db.value,
|
|
716
|
+
})) as ExecuteResult | undefined
|
|
717
|
+
const entry = extractSingleEntry(result)
|
|
718
|
+
if (!entry) {
|
|
719
|
+
exitValueView()
|
|
720
|
+
return
|
|
721
|
+
}
|
|
722
|
+
applyValueTable(entry)
|
|
723
|
+
} catch {
|
|
724
|
+
exitValueView()
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
const onKeyPickerSelect = (key: string | null | undefined) => {
|
|
729
|
+
const trimmed = key?.trim() ?? ""
|
|
730
|
+
if (!trimmed.length) {
|
|
731
|
+
if (isValueView.value) {
|
|
732
|
+
exitValueView()
|
|
733
|
+
}
|
|
734
|
+
return
|
|
735
|
+
}
|
|
736
|
+
void loadEntryForKey(trimmed)
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
onMounted(() => {
|
|
740
|
+
reloadFromStart()
|
|
741
|
+
})
|
|
742
|
+
</script>
|