@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
|
@@ -1,29 +1,57 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<CompositionConfiguration
|
|
3
|
+
ref="configurationRef"
|
|
3
4
|
v-model="newComposition"
|
|
5
|
+
v-model:interaction-link="draftInteractionLink"
|
|
4
6
|
:disabled="isLoading"
|
|
5
|
-
@
|
|
6
|
-
@save="onSubmit"
|
|
7
|
+
@custom-action="onCustomAction"
|
|
7
8
|
/>
|
|
8
9
|
</template>
|
|
9
10
|
|
|
10
11
|
<script setup lang="ts">
|
|
11
|
-
import { CompositionCreate } from "@raclettejs/core"
|
|
12
|
-
import { ref } from "vue"
|
|
12
|
+
import { CompositionCreate, InteractionLinkCreate } from "@raclettejs/core"
|
|
13
|
+
import { computed, ref } from "vue"
|
|
13
14
|
import CompositionConfiguration from "../components/compositionConfiguration/CompositionConfiguration.vue"
|
|
14
15
|
import {
|
|
15
16
|
usePluginApi,
|
|
16
17
|
useRouteState,
|
|
17
18
|
} from "@raclettejs/core/orchestrator/composables"
|
|
19
|
+
import { DEFAULT_SLOT_NAME } from "@raclettejs/core/orchestrator/router/routeParserHelper"
|
|
20
|
+
import { prefillInteractionLinkFromComposition } from "../composables/prefillInteractionLinkFromComposition"
|
|
18
21
|
|
|
19
|
-
|
|
22
|
+
defineProps<{
|
|
20
23
|
uuid: string
|
|
21
24
|
}>()
|
|
22
25
|
|
|
23
26
|
const { $data } = usePluginApi("raclette__core")
|
|
24
27
|
const { triggerInteractionLinkById } = useRouteState()
|
|
25
28
|
|
|
26
|
-
const
|
|
29
|
+
const configurationRef = ref<InstanceType<typeof CompositionConfiguration>>()
|
|
30
|
+
|
|
31
|
+
const {
|
|
32
|
+
execute: executeCreate,
|
|
33
|
+
isLoading: isCreating,
|
|
34
|
+
error: createError,
|
|
35
|
+
data: createResultData,
|
|
36
|
+
} = $data.composition.create({
|
|
37
|
+
options: { notify: false },
|
|
38
|
+
})
|
|
39
|
+
const {
|
|
40
|
+
execute: executeUpdate,
|
|
41
|
+
isLoading: isUpdating,
|
|
42
|
+
error: updateError,
|
|
43
|
+
} = $data.composition.update({
|
|
44
|
+
options: { notify: false },
|
|
45
|
+
})
|
|
46
|
+
const {
|
|
47
|
+
execute: executeInteractionLinkCreate,
|
|
48
|
+
isLoading: isCreatingInteractionLink,
|
|
49
|
+
error: interactionLinkCreateError,
|
|
50
|
+
} = $data.interactionLink.create()
|
|
51
|
+
|
|
52
|
+
const isLoading = computed(
|
|
53
|
+
() => isCreating.value || isUpdating.value || isCreatingInteractionLink.value,
|
|
54
|
+
)
|
|
27
55
|
|
|
28
56
|
const newComposition = ref<CompositionCreate>({
|
|
29
57
|
title: "",
|
|
@@ -39,14 +67,122 @@ const newComposition = ref<CompositionCreate>({
|
|
|
39
67
|
widgetsLayout: [[]],
|
|
40
68
|
})
|
|
41
69
|
|
|
70
|
+
const createdCompositionId = ref<string | null>(null)
|
|
71
|
+
|
|
72
|
+
const draftInteractionLink = ref<InteractionLinkCreate>({
|
|
73
|
+
composition: "",
|
|
74
|
+
description: "",
|
|
75
|
+
slotType: DEFAULT_SLOT_NAME,
|
|
76
|
+
triggers: [],
|
|
77
|
+
tags: [],
|
|
78
|
+
queryParameters: [],
|
|
79
|
+
isLandingPage: false,
|
|
80
|
+
})
|
|
81
|
+
|
|
42
82
|
const navigateBack = () =>
|
|
43
83
|
triggerInteractionLinkById("compositionListInteractionLink")
|
|
44
84
|
|
|
45
|
-
|
|
46
|
-
|
|
85
|
+
type CompositionWriteResult = {
|
|
86
|
+
result?: Array<{ _id?: string }>
|
|
87
|
+
response?: {
|
|
88
|
+
data?: {
|
|
89
|
+
body?: {
|
|
90
|
+
items?: Array<{ _id?: string }>
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const getCreatedId = (executeResult: unknown): string | undefined => {
|
|
97
|
+
if (!executeResult || typeof executeResult !== "object") {
|
|
98
|
+
return undefined
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const payload = executeResult as CompositionWriteResult
|
|
102
|
+
const fromHandler = payload.result?.[0]?._id
|
|
103
|
+
if (fromHandler) {
|
|
104
|
+
return fromHandler
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return payload.response?.data?.body?.items?.[0]?._id
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const getCreatedIdFromStore = (): string | undefined => {
|
|
111
|
+
if (!createResultData.value) {
|
|
112
|
+
return undefined
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const items = Object.values(createResultData.value) as Array<{ _id?: string }>
|
|
116
|
+
return items[0]?._id
|
|
117
|
+
}
|
|
47
118
|
|
|
48
|
-
|
|
119
|
+
const persistComposition = async (): Promise<boolean> => {
|
|
120
|
+
if (createdCompositionId.value) {
|
|
121
|
+
await executeUpdate({
|
|
122
|
+
...newComposition.value,
|
|
123
|
+
_id: createdCompositionId.value,
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
if (updateError.value) {
|
|
127
|
+
return false
|
|
128
|
+
}
|
|
129
|
+
} else {
|
|
130
|
+
const result = await executeCreate(newComposition.value)
|
|
131
|
+
|
|
132
|
+
if (createError.value) {
|
|
133
|
+
return false
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const createdId = getCreatedId(result) ?? getCreatedIdFromStore()
|
|
137
|
+
|
|
138
|
+
if (!createdId) {
|
|
139
|
+
return false
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
createdCompositionId.value = createdId
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
draftInteractionLink.value.composition = createdCompositionId.value
|
|
146
|
+
prefillInteractionLinkFromComposition(
|
|
147
|
+
draftInteractionLink.value,
|
|
148
|
+
newComposition.value,
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
return true
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const onCustomAction = async (actionId: string) => {
|
|
155
|
+
if (actionId === "save-and-continue") {
|
|
156
|
+
const saved = await persistComposition()
|
|
157
|
+
|
|
158
|
+
if (saved) {
|
|
159
|
+
configurationRef.value?.goToStep(3, { skipValidation: true })
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (actionId === "finish-without-il") {
|
|
49
166
|
navigateBack()
|
|
167
|
+
return
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (actionId === "save-il") {
|
|
171
|
+
if (!createdCompositionId.value) {
|
|
172
|
+
const saved = await persistComposition()
|
|
173
|
+
|
|
174
|
+
if (!saved) {
|
|
175
|
+
return
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
draftInteractionLink.value.composition = createdCompositionId.value!
|
|
180
|
+
|
|
181
|
+
await executeInteractionLinkCreate(draftInteractionLink.value)
|
|
182
|
+
|
|
183
|
+
if (!interactionLinkCreateError.value) {
|
|
184
|
+
navigateBack()
|
|
185
|
+
}
|
|
50
186
|
}
|
|
51
187
|
}
|
|
52
188
|
</script>
|
|
@@ -47,7 +47,11 @@ const { data } = $data.composition.get({
|
|
|
47
47
|
},
|
|
48
48
|
})
|
|
49
49
|
|
|
50
|
-
const {
|
|
50
|
+
const {
|
|
51
|
+
execute: executeUpdate,
|
|
52
|
+
isLoading,
|
|
53
|
+
error: updateError,
|
|
54
|
+
} = $data.composition.update()
|
|
51
55
|
const editableComposition = ref<Composition>()
|
|
52
56
|
|
|
53
57
|
const composition = computed<Composition | undefined>(() => {
|
|
@@ -61,6 +65,11 @@ const composition = computed<Composition | undefined>(() => {
|
|
|
61
65
|
const navigateBack = () =>
|
|
62
66
|
triggerInteractionLinkById("compositionListInteractionLink")
|
|
63
67
|
|
|
64
|
-
const onSubmit = async (finish = false) =>
|
|
68
|
+
const onSubmit = async (finish = false) => {
|
|
65
69
|
await executeUpdate(toRaw(editableComposition.value))
|
|
70
|
+
|
|
71
|
+
if (finish && !updateError.value) {
|
|
72
|
+
navigateBack()
|
|
73
|
+
}
|
|
74
|
+
}
|
|
66
75
|
</script>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="tw:p-4">
|
|
2
|
+
<div class="tw:p-4 tw:pt-2">
|
|
3
3
|
<BaseDataTable
|
|
4
4
|
:items="parsedCompositions"
|
|
5
5
|
:exportable-items="exportableCompositions"
|
|
@@ -16,7 +16,11 @@
|
|
|
16
16
|
prepend-icon="mdi-plus"
|
|
17
17
|
variant="flat"
|
|
18
18
|
color="success"
|
|
19
|
-
:text="
|
|
19
|
+
:text="
|
|
20
|
+
$t('core.baseDataTable.createDataButton', {
|
|
21
|
+
dataName: $t('workbench.compositionList.dataName'),
|
|
22
|
+
})
|
|
23
|
+
"
|
|
20
24
|
@click="goToCreate('compositionCreateInteractionLink')"
|
|
21
25
|
/>
|
|
22
26
|
</template>
|
|
@@ -94,7 +98,11 @@
|
|
|
94
98
|
</template>
|
|
95
99
|
|
|
96
100
|
<script setup lang="ts">
|
|
97
|
-
import {
|
|
101
|
+
import {
|
|
102
|
+
usePluginApi,
|
|
103
|
+
useRouteState,
|
|
104
|
+
} from "@raclettejs/core/orchestrator/composables"
|
|
105
|
+
import { DEFAULT_SLOT_NAME } from "@raclettejs/core/orchestrator/router/routeParserHelper"
|
|
98
106
|
import { computed, watch, ref } from "vue"
|
|
99
107
|
import type { Composition } from "@raclettejs/core"
|
|
100
108
|
import { formatDistance } from "date-fns"
|
|
@@ -153,6 +161,16 @@ const props = defineProps<{
|
|
|
153
161
|
}>()
|
|
154
162
|
|
|
155
163
|
const { $data, $socket, $user } = usePluginApi("raclette__core")
|
|
164
|
+
const { compositionSlots } = useRouteState()
|
|
165
|
+
|
|
166
|
+
const COMPOSITION_LIST_PATH = "composition-list"
|
|
167
|
+
|
|
168
|
+
const isCompositionListActive = () =>
|
|
169
|
+
compositionSlots.value.some(
|
|
170
|
+
(slot) =>
|
|
171
|
+
slot.slotName === DEFAULT_SLOT_NAME &&
|
|
172
|
+
slot.slotValue === COMPOSITION_LIST_PATH,
|
|
173
|
+
)
|
|
156
174
|
const {
|
|
157
175
|
data: compositions,
|
|
158
176
|
execute,
|
|
@@ -291,9 +309,11 @@ const restore = (_id: string) => {
|
|
|
291
309
|
const handleRowClick = (item: Composition) =>
|
|
292
310
|
goToEditById("compositionEditInteractionLink", item._id)
|
|
293
311
|
|
|
294
|
-
$socket.on("compositionCreated", () =>
|
|
295
|
-
|
|
296
|
-
)
|
|
312
|
+
$socket.on("compositionCreated", () => {
|
|
313
|
+
if (isCompositionListActive()) {
|
|
314
|
+
execute({ isDeleted: showDeleted.value })
|
|
315
|
+
}
|
|
316
|
+
})
|
|
297
317
|
watch(
|
|
298
318
|
showDeleted,
|
|
299
319
|
() => {
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// Auto-generated frontend configuration for plugin: pacifico__core
|
|
2
|
+
// This file is generated from backend routes - do not edit manually
|
|
3
|
+
// Entity mapping: {}
|
|
4
|
+
|
|
5
|
+
export default {
|
|
6
|
+
pluginName: "core",
|
|
7
|
+
author: "pacifico",
|
|
8
|
+
pluginKey: "pacifico__core",
|
|
9
|
+
routePrefix: "/pacifico__core",
|
|
10
|
+
pluginPath: "/app/src/appPlugins/pacifico__core",
|
|
11
|
+
data: {
|
|
12
|
+
"init-project": {
|
|
13
|
+
type: "init-project",
|
|
14
|
+
operations: {
|
|
15
|
+
create: {
|
|
16
|
+
target: "/pacifico__core/init-project",
|
|
17
|
+
method: "post"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
package/plugins/pacifico__interactionLinks/frontend/components/InteractionLinkConfiguration.vue
CHANGED
|
@@ -33,11 +33,7 @@ import { useI18n } from "vue-i18n"
|
|
|
33
33
|
import { computed, useTemplateRef } from "vue"
|
|
34
34
|
import { Step } from "@app/components/stepNavigator/StepNavigatorTypes"
|
|
35
35
|
import StepNavigator from "@app/components/stepNavigator/StepNavigator.vue"
|
|
36
|
-
import {
|
|
37
|
-
DEFAULT_SLOT_NAME,
|
|
38
|
-
DEFAULT_INLINE_SLOT_NAME,
|
|
39
|
-
DEFAULT_MODAL_SLOT_NAME,
|
|
40
|
-
} from "@raclettejs/core/orchestrator/router/routeParserHelper"
|
|
36
|
+
import { useInteractionLinkBehaviorFields } from "@app/composables/useInteractionLinkBehaviorFields"
|
|
41
37
|
|
|
42
38
|
defineProps<{ disabled?: boolean; isEditing?: boolean }>()
|
|
43
39
|
|
|
@@ -53,6 +49,7 @@ const interactionLink = defineModel<
|
|
|
53
49
|
const dynamicFormRef = useTemplateRef("dynamicFormRef")
|
|
54
50
|
|
|
55
51
|
const { t } = useI18n()
|
|
52
|
+
const { behaviorFields } = useInteractionLinkBehaviorFields()
|
|
56
53
|
|
|
57
54
|
const inputFields = computed<
|
|
58
55
|
DynamicFormItem<InteractionLinkCreate | InteractionLinkUpdate>[]
|
|
@@ -68,66 +65,7 @@ const inputFields = computed<
|
|
|
68
65
|
prependIcon: "file-document",
|
|
69
66
|
validation: { required: true },
|
|
70
67
|
},
|
|
71
|
-
|
|
72
|
-
title: t("workbench.interactionLinkConfiguration.description.title"),
|
|
73
|
-
description: t(
|
|
74
|
-
"workbench.interactionLinkConfiguration.description.description",
|
|
75
|
-
),
|
|
76
|
-
inputType: "textarea",
|
|
77
|
-
field: "description",
|
|
78
|
-
prependIcon: "text",
|
|
79
|
-
},
|
|
80
|
-
{
|
|
81
|
-
title: t("workbench.interactionLinkConfiguration.tags.title"),
|
|
82
|
-
description: t("workbench.interactionLinkConfiguration.tags.description"),
|
|
83
|
-
inputType: "tags",
|
|
84
|
-
field: "tags",
|
|
85
|
-
prependIcon: "tag-multiple",
|
|
86
|
-
},
|
|
87
|
-
{
|
|
88
|
-
divider: t("workbench.interactionLinkConfiguration.dividers.behavior"),
|
|
89
|
-
},
|
|
90
|
-
{
|
|
91
|
-
title: t("workbench.interactionLinkConfiguration.slotType.title"),
|
|
92
|
-
description: t(
|
|
93
|
-
"workbench.interactionLinkConfiguration.slotType.description",
|
|
94
|
-
),
|
|
95
|
-
inputType: "select",
|
|
96
|
-
field: "slotType",
|
|
97
|
-
selectItems: [
|
|
98
|
-
DEFAULT_SLOT_NAME,
|
|
99
|
-
DEFAULT_MODAL_SLOT_NAME,
|
|
100
|
-
DEFAULT_INLINE_SLOT_NAME,
|
|
101
|
-
],
|
|
102
|
-
prependIcon: "dock-window",
|
|
103
|
-
validation: {
|
|
104
|
-
required: true,
|
|
105
|
-
},
|
|
106
|
-
},
|
|
107
|
-
{
|
|
108
|
-
title: t("workbench.interactionLinkConfiguration.triggers.title"),
|
|
109
|
-
description: t(
|
|
110
|
-
"workbench.interactionLinkConfiguration.triggers.description",
|
|
111
|
-
),
|
|
112
|
-
inputType: "triggers",
|
|
113
|
-
field: "triggers",
|
|
114
|
-
},
|
|
115
|
-
{
|
|
116
|
-
title: t("workbench.interactionLinkConfiguration.queryParameters.title"),
|
|
117
|
-
description: t(
|
|
118
|
-
"workbench.interactionLinkConfiguration.queryParameters.description",
|
|
119
|
-
),
|
|
120
|
-
inputType: "list",
|
|
121
|
-
field: "queryParameters",
|
|
122
|
-
},
|
|
123
|
-
{
|
|
124
|
-
title: t("workbench.interactionLinkConfiguration.isLandingPage.title"),
|
|
125
|
-
description: t(
|
|
126
|
-
"workbench.interactionLinkConfiguration.isLandingPage.description",
|
|
127
|
-
),
|
|
128
|
-
inputType: "switch",
|
|
129
|
-
field: "isLandingPage",
|
|
130
|
-
},
|
|
68
|
+
...behaviorFields.value,
|
|
131
69
|
])
|
|
132
70
|
|
|
133
71
|
const validateForm = (): boolean => {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="tw:p-4">
|
|
2
|
+
<div class="tw:p-4 tw:pt-2">
|
|
3
3
|
<BaseDataTable
|
|
4
4
|
:items="pluginMetadataFiles"
|
|
5
5
|
:headers
|
|
@@ -7,8 +7,21 @@
|
|
|
7
7
|
:show-filters="true"
|
|
8
8
|
:show-actions-column="false"
|
|
9
9
|
:row-click-handler="onRowClick"
|
|
10
|
+
:is-row-clickable="isPluginRowClickable"
|
|
10
11
|
:show-export="false"
|
|
11
|
-
|
|
12
|
+
>
|
|
13
|
+
<template #item.name="{ item }">
|
|
14
|
+
<span class="tw:flex tw:items-center tw:gap-2">
|
|
15
|
+
<v-icon
|
|
16
|
+
v-if="isPluginRowClickable(item)"
|
|
17
|
+
icon="mdi-pencil"
|
|
18
|
+
size="small"
|
|
19
|
+
class="tw:opacity-70"
|
|
20
|
+
/>
|
|
21
|
+
{{ item.name }}
|
|
22
|
+
</span>
|
|
23
|
+
</template>
|
|
24
|
+
</BaseDataTable>
|
|
12
25
|
</div>
|
|
13
26
|
</template>
|
|
14
27
|
|
|
@@ -48,10 +61,14 @@ const headers = computed<
|
|
|
48
61
|
},
|
|
49
62
|
])
|
|
50
63
|
|
|
64
|
+
const isPluginRowClickable = (item: PluginMetadata) =>
|
|
65
|
+
Boolean(item.pluginKey && hasPluginSettingsWidget(item.pluginKey))
|
|
66
|
+
|
|
51
67
|
const onRowClick = (item: PluginMetadata) => {
|
|
52
|
-
|
|
68
|
+
const { pluginKey } = item
|
|
69
|
+
if (pluginKey && isPluginRowClickable(item)) {
|
|
53
70
|
triggerInteractionLinkById("pluginDetailInteractionLink", {
|
|
54
|
-
pluginKey
|
|
71
|
+
pluginKey,
|
|
55
72
|
})
|
|
56
73
|
}
|
|
57
74
|
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { keyGroup } from "./valkeyExplorer"
|
|
2
|
+
import { sanitizeCacheEntryFields } from "./cacheEntrySanitizer"
|
|
3
|
+
|
|
4
|
+
/** Max string length stored per row (larger values are truncated for transport). */
|
|
5
|
+
export const MAX_VALUE_LENGTH = 16_384
|
|
6
|
+
|
|
7
|
+
export type CacheEntrySource = {
|
|
8
|
+
key: string
|
|
9
|
+
keyGroup: string
|
|
10
|
+
value: string
|
|
11
|
+
valuePreview?: string
|
|
12
|
+
truncated: boolean
|
|
13
|
+
valueParsed?: unknown
|
|
14
|
+
ttl: number
|
|
15
|
+
ttlLabel: string
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const truncateValue = (
|
|
19
|
+
raw: string,
|
|
20
|
+
): { value: string; valuePreview?: string; truncated: boolean } => {
|
|
21
|
+
if (raw.length <= MAX_VALUE_LENGTH) {
|
|
22
|
+
return { value: raw, truncated: false }
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
value: raw.slice(0, MAX_VALUE_LENGTH),
|
|
27
|
+
valuePreview: `${raw.slice(0, 512)}…`,
|
|
28
|
+
truncated: true,
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const formatTtlLabel = (ttl: number): string => {
|
|
33
|
+
if (ttl === -1) return "no expiry"
|
|
34
|
+
if (ttl === -2) return "missing"
|
|
35
|
+
return `${ttl}s`
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export type BuildCacheEntrySourceOptions = {
|
|
39
|
+
/** Parse the full raw value for `valueParsed` even when the transport string is truncated. */
|
|
40
|
+
attachFullParsed?: boolean
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export const buildCacheEntrySource = (
|
|
44
|
+
key: string,
|
|
45
|
+
rawValue: string,
|
|
46
|
+
ttl: number,
|
|
47
|
+
options: BuildCacheEntrySourceOptions = {},
|
|
48
|
+
): CacheEntrySource => {
|
|
49
|
+
const { attachFullParsed = false } = options
|
|
50
|
+
const truncatedFields = truncateValue(rawValue ?? "")
|
|
51
|
+
const sanitized = sanitizeCacheEntryFields(truncatedFields)
|
|
52
|
+
|
|
53
|
+
let valueParsed = sanitized.valueParsed
|
|
54
|
+
|
|
55
|
+
if (attachFullParsed && truncatedFields.truncated) {
|
|
56
|
+
const fullSanitized = sanitizeCacheEntryFields({
|
|
57
|
+
value: rawValue ?? "",
|
|
58
|
+
truncated: false,
|
|
59
|
+
})
|
|
60
|
+
if (fullSanitized.valueParsed !== undefined) {
|
|
61
|
+
valueParsed = fullSanitized.valueParsed
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
key,
|
|
67
|
+
keyGroup: keyGroup(key),
|
|
68
|
+
value: sanitized.value,
|
|
69
|
+
valuePreview: sanitized.valuePreview,
|
|
70
|
+
truncated: sanitized.truncated,
|
|
71
|
+
valueParsed,
|
|
72
|
+
ttl,
|
|
73
|
+
ttlLabel: formatTtlLabel(ttl),
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export const toPayloadItems = (entries: CacheEntrySource[]) => {
|
|
78
|
+
const now = new Date().toISOString()
|
|
79
|
+
|
|
80
|
+
return entries.map((entry) => ({
|
|
81
|
+
_id: entry.key,
|
|
82
|
+
_type: "cacheEntry",
|
|
83
|
+
_offline: 0,
|
|
84
|
+
_owner: "",
|
|
85
|
+
_tags: [] as string[],
|
|
86
|
+
_displayName: entry.key,
|
|
87
|
+
_completion: "",
|
|
88
|
+
_project: "",
|
|
89
|
+
_updatedAt: now,
|
|
90
|
+
_createdAt: now,
|
|
91
|
+
_deleted: false,
|
|
92
|
+
_source: entry,
|
|
93
|
+
}))
|
|
94
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
export const HIDE_SECRETS = true
|
|
2
|
+
|
|
3
|
+
export const SECRET_KEY_PATTERNS: RegExp[] = [
|
|
4
|
+
/password/i,
|
|
5
|
+
/secret/i,
|
|
6
|
+
/token/i,
|
|
7
|
+
/api[_-]?key/i,
|
|
8
|
+
/credential/i,
|
|
9
|
+
/private[_-]?key/i,
|
|
10
|
+
/access[_-]?token/i,
|
|
11
|
+
/refresh[_-]?token/i,
|
|
12
|
+
/authorization/i,
|
|
13
|
+
/bearer/i,
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
const REDACTED_VALUE = "******"
|
|
17
|
+
const PREVIEW_LENGTH = 512
|
|
18
|
+
|
|
19
|
+
export const isSecretKey = (key: string): boolean =>
|
|
20
|
+
SECRET_KEY_PATTERNS.some((pattern) => pattern.test(key))
|
|
21
|
+
|
|
22
|
+
export const redactJsonValue = (value: unknown): unknown => {
|
|
23
|
+
if (Array.isArray(value)) {
|
|
24
|
+
return value.map((item) => redactJsonValue(item))
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (value !== null && typeof value === "object") {
|
|
28
|
+
const redacted: Record<string, unknown> = {}
|
|
29
|
+
|
|
30
|
+
for (const [key, nestedValue] of Object.entries(
|
|
31
|
+
value as Record<string, unknown>,
|
|
32
|
+
)) {
|
|
33
|
+
redacted[key] = isSecretKey(key)
|
|
34
|
+
? REDACTED_VALUE
|
|
35
|
+
: redactJsonValue(nestedValue)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return redacted
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return value
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
type SanitizeInput = {
|
|
45
|
+
value: string
|
|
46
|
+
valuePreview?: string
|
|
47
|
+
truncated: boolean
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
type SanitizeResult = {
|
|
51
|
+
value: string
|
|
52
|
+
valuePreview?: string
|
|
53
|
+
truncated: boolean
|
|
54
|
+
valueParsed?: unknown
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const buildPreview = (value: string): string =>
|
|
58
|
+
`${value.slice(0, PREVIEW_LENGTH)}…`
|
|
59
|
+
|
|
60
|
+
export const sanitizeCacheEntryFields = ({
|
|
61
|
+
value,
|
|
62
|
+
valuePreview,
|
|
63
|
+
truncated,
|
|
64
|
+
}: SanitizeInput): SanitizeResult => {
|
|
65
|
+
let parsed: unknown
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
parsed = JSON.parse(value)
|
|
69
|
+
} catch {
|
|
70
|
+
return { value, valuePreview, truncated }
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (!HIDE_SECRETS) {
|
|
74
|
+
return { value, valuePreview, truncated, valueParsed: parsed }
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const valueParsed = redactJsonValue(parsed)
|
|
78
|
+
const redactedValue = JSON.stringify(valueParsed)
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
value: redactedValue,
|
|
82
|
+
valuePreview: truncated ? buildPreview(redactedValue) : valuePreview,
|
|
83
|
+
truncated,
|
|
84
|
+
valueParsed,
|
|
85
|
+
}
|
|
86
|
+
}
|