@drax/settings-vue 0.40.0 → 0.42.2
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/package.json +7 -6
- package/src/components/SettingAvConfig.vue +396 -0
- package/src/components/SettingEditor.vue +1 -1
- package/src/components/SettingField.vue +19 -18
- package/src/components/SettingTableConfig.vue +101 -31
- package/src/index.ts +1 -1
- package/src/pages/SettingAvPage.vue +13 -0
- package/src/pages/SettingPage.vue +1 -2
- package/src/routes/SettingRoutes.ts +10 -1
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "0.
|
|
6
|
+
"version": "0.42.2",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"main": "./src/index.ts",
|
|
9
9
|
"module": "./src/index.ts",
|
|
@@ -24,10 +24,11 @@
|
|
|
24
24
|
"format": "prettier --write src/"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@drax/common-front": "^0.
|
|
28
|
-
"@drax/
|
|
29
|
-
"@drax/
|
|
30
|
-
"@drax/settings-
|
|
27
|
+
"@drax/common-front": "^0.42.2",
|
|
28
|
+
"@drax/crud-vue": "^0.42.2",
|
|
29
|
+
"@drax/identity-vue": "^0.42.2",
|
|
30
|
+
"@drax/settings-front": "^0.42.2",
|
|
31
|
+
"@drax/settings-share": "^0.42.2"
|
|
31
32
|
},
|
|
32
33
|
"peerDependencies": {
|
|
33
34
|
"pinia": "^2.2.2",
|
|
@@ -64,5 +65,5 @@
|
|
|
64
65
|
"vue-tsc": "^2.1.10",
|
|
65
66
|
"vuetify": "^3.7.1"
|
|
66
67
|
},
|
|
67
|
-
"gitHead": "
|
|
68
|
+
"gitHead": "6b4f9f50a8e3f0fbdaff7ec913356834a4e2c0b5"
|
|
68
69
|
}
|
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import {useSetting} from "../composables/UseSetting";
|
|
3
|
+
import {onMounted, reactive, ref} from "vue";
|
|
4
|
+
import {useI18n} from "vue-i18n";
|
|
5
|
+
import type {ISetting} from "@drax/settings-share";
|
|
6
|
+
import dayjs from 'dayjs'
|
|
7
|
+
import SettingField from "./SettingField.vue";
|
|
8
|
+
|
|
9
|
+
const {fetchSettings, settingsGrouped, updateSettingValue} = useSetting()
|
|
10
|
+
const {t} = useI18n()
|
|
11
|
+
|
|
12
|
+
onMounted(async () => {
|
|
13
|
+
await fetchSettings()
|
|
14
|
+
onSearchUpdate('')
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
function getTypeColor(type: string): string {
|
|
20
|
+
|
|
21
|
+
//'string' | 'longString' | 'number' | 'enum' | 'boolean' | 'password' |'stringList' | 'numberList' | 'enumList' |'ref' |'secret'
|
|
22
|
+
switch (type) {
|
|
23
|
+
case 'string': return 'blue';
|
|
24
|
+
case 'longString': return 'blue';
|
|
25
|
+
case 'number': return 'purple';
|
|
26
|
+
case 'enum': return 'orange';
|
|
27
|
+
case 'boolean': return 'green';
|
|
28
|
+
case 'password': return 'red';
|
|
29
|
+
case 'stringList': return 'light-blue';
|
|
30
|
+
case 'numberList': return 'purple';
|
|
31
|
+
case 'enumList': return 'orange';
|
|
32
|
+
case 'ref': return 'pink';
|
|
33
|
+
case 'secret': return 'error';
|
|
34
|
+
default: return 'grey';
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
const settingsGroupedFiltered = ref<Record<string, ISetting[]>>({})
|
|
43
|
+
|
|
44
|
+
const search = ref<string>('')
|
|
45
|
+
function onSearchUpdate(value: string) {
|
|
46
|
+
if(!value) settingsGroupedFiltered.value = {...settingsGrouped.value};
|
|
47
|
+
|
|
48
|
+
for (const groupKey of Object.keys(settingsGroupedFiltered.value)) {
|
|
49
|
+
settingsGroupedFiltered.value[groupKey] = settingsGroupedFiltered.value[groupKey].filter((setting: ISetting) =>
|
|
50
|
+
setting.key.toLowerCase().includes(value.toLowerCase()) ||
|
|
51
|
+
(setting.description && setting.description.toLowerCase().includes(value.toLowerCase()))
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
if(!settingsGroupedFiltered.value[groupKey]?.length) {
|
|
55
|
+
delete settingsGroupedFiltered.value[groupKey];
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const showModal = reactive<{
|
|
61
|
+
isOpen: boolean, value?: string,
|
|
62
|
+
setting?: ISetting
|
|
63
|
+
}>({ isOpen: false, value: '', setting: undefined})
|
|
64
|
+
|
|
65
|
+
const openShowModal = (key: string, value: string | undefined, setting: ISetting) => {
|
|
66
|
+
showModal.isOpen = true;
|
|
67
|
+
showModal.value = value;
|
|
68
|
+
showModal.setting = setting
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const closeShowModal = () => {
|
|
72
|
+
showModal.isOpen = false;
|
|
73
|
+
showModal.value = '';
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const getSettingDescription = (key: string, description?: string) => {
|
|
77
|
+
return {
|
|
78
|
+
_id: "123",
|
|
79
|
+
category: "Description",
|
|
80
|
+
key: key,
|
|
81
|
+
label: `${t('setting.descriptionof')} ${key}`,
|
|
82
|
+
type: "longString",
|
|
83
|
+
value: description
|
|
84
|
+
} as ISetting
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const informationUpdatingModal = reactive<{
|
|
88
|
+
isOpen: boolean, setting?: ISetting, readonly: boolean
|
|
89
|
+
}>({isOpen: false, readonly: false})
|
|
90
|
+
|
|
91
|
+
const openInformationUpdatingModal = (setting: ISetting, readonly: boolean) => {
|
|
92
|
+
informationUpdatingModal.isOpen = true
|
|
93
|
+
informationUpdatingModal.setting = setting
|
|
94
|
+
informationUpdatingModal.readonly = readonly
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const closeInformationUpdatingModal = () => {
|
|
98
|
+
informationUpdatingModal.isOpen = false
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const updateLoading = ref<boolean>(false)
|
|
102
|
+
|
|
103
|
+
const form = ref()
|
|
104
|
+
|
|
105
|
+
const alert = reactive<{
|
|
106
|
+
value: boolean, type: 'error' | 'success' | 'warning',
|
|
107
|
+
text: string
|
|
108
|
+
}>({value: false, type: 'success', text: ''})
|
|
109
|
+
|
|
110
|
+
const showSuccess = (text: string) => {
|
|
111
|
+
alert.value = true
|
|
112
|
+
alert.type = "success"
|
|
113
|
+
alert.text = text
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const updateSetting = async (setting?: ISetting) => {
|
|
117
|
+
try {
|
|
118
|
+
if(!setting){
|
|
119
|
+
console.error('[updateSetting] - setting not provided')
|
|
120
|
+
return
|
|
121
|
+
}
|
|
122
|
+
updateLoading.value = true
|
|
123
|
+
const validation = await form.value.validate()
|
|
124
|
+
if(validation && validation.valid === true){
|
|
125
|
+
const settingUpdated = await updateSettingValue(setting._id, setting.value)
|
|
126
|
+
setting.updatedBy = settingUpdated.updatedBy
|
|
127
|
+
}
|
|
128
|
+
closeInformationUpdatingModal()
|
|
129
|
+
showSuccess(t('setting.updated.successfuly'))
|
|
130
|
+
} catch (e) {
|
|
131
|
+
console.error(e)
|
|
132
|
+
} finally {
|
|
133
|
+
updateLoading.value = false
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
</script>
|
|
138
|
+
|
|
139
|
+
<template>
|
|
140
|
+
<div class="d-flex align-center px-16">
|
|
141
|
+
<v-avatar style="border-radius: 7px;" color="menuIcon" class="mr-4">
|
|
142
|
+
<v-icon>mdi-cog</v-icon>
|
|
143
|
+
</v-avatar>
|
|
144
|
+
<v-toolbar-title>{{ t('setting.page.title') }}</v-toolbar-title>
|
|
145
|
+
</div>
|
|
146
|
+
<v-divider class="my-4"></v-divider>
|
|
147
|
+
<div class="d-flex justify-space-between px-16">
|
|
148
|
+
<v-text-field
|
|
149
|
+
:placeholder="t('setting.search')"
|
|
150
|
+
persistent-placeholder
|
|
151
|
+
clearable variant="outlined"
|
|
152
|
+
prepend-inner-icon="mdi-magnify"
|
|
153
|
+
v-model="search"
|
|
154
|
+
@update:model-value="onSearchUpdate"
|
|
155
|
+
></v-text-field>
|
|
156
|
+
</div>
|
|
157
|
+
<div
|
|
158
|
+
class="px-16"
|
|
159
|
+
>
|
|
160
|
+
<div class="pa-0 ma-0" style="width: calc(100%); height: calc(100vh - 315px); overflow-y: auto; scrollbar-width: thin;">
|
|
161
|
+
<v-expansion-panels
|
|
162
|
+
class="pr-1 mb-4" variant="accordion" v-for="groupkey of Object.keys(settingsGroupedFiltered)" :key="groupkey"
|
|
163
|
+
:model-value="1"
|
|
164
|
+
>
|
|
165
|
+
<v-expansion-panel :value="1">
|
|
166
|
+
<template #title>
|
|
167
|
+
{{ groupkey }}
|
|
168
|
+
<v-avatar class="ml-4" color="blue-grey-darken-2" style="border-radius: 4px;" size="24">
|
|
169
|
+
{{ settingsGroupedFiltered[groupkey]?.length }}
|
|
170
|
+
</v-avatar>
|
|
171
|
+
</template>
|
|
172
|
+
<template #text>
|
|
173
|
+
<table style="width: 100%;">
|
|
174
|
+
<thead>
|
|
175
|
+
<v-row>
|
|
176
|
+
<v-col cols="2" class="text-caption">{{t('setting.field.key')}}</v-col>
|
|
177
|
+
<v-col cols="1" class="text-caption">{{t('setting.field.value')}}</v-col>
|
|
178
|
+
<v-col cols="1" class="text-caption">{{t('setting.field.type')}}</v-col>
|
|
179
|
+
<v-col cols="3" class="text-caption">{{t('setting.field.description')}}</v-col>
|
|
180
|
+
<v-col cols="1" class="text-caption">{{t('setting.field.visibility')}}</v-col>
|
|
181
|
+
<v-col cols="1" class="text-caption">{{t('setting.field.permission')}}</v-col>
|
|
182
|
+
<v-col cols="2" class="text-caption">{{t('setting.field.updated')}}</v-col>
|
|
183
|
+
<v-col cols="1"></v-col>
|
|
184
|
+
</v-row>
|
|
185
|
+
</thead>
|
|
186
|
+
<tbody>
|
|
187
|
+
<template :key="setting.key" v-for="setting in settingsGroupedFiltered[groupkey]">
|
|
188
|
+
<v-row class="mt-2">
|
|
189
|
+
<v-col cols="2" class="pr-4">
|
|
190
|
+
<span
|
|
191
|
+
style="display: inline-block; max-width: 100%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;"
|
|
192
|
+
>
|
|
193
|
+
{{ setting.key }}
|
|
194
|
+
</span>
|
|
195
|
+
</v-col>
|
|
196
|
+
<v-col cols="1" class="pr-4 d-flex ga-2">
|
|
197
|
+
<template v-if="setting.value">
|
|
198
|
+
<v-icon size="small" color="grey" style="cursor: pointer;" @click="openShowModal(setting.key, setting.value, setting)">mdi-eye</v-icon>
|
|
199
|
+
<span
|
|
200
|
+
class="text-caption text-grey"
|
|
201
|
+
style="display: inline-block; max-width: 200px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;"
|
|
202
|
+
>
|
|
203
|
+
{{ setting.value }}
|
|
204
|
+
</span>
|
|
205
|
+
</template>
|
|
206
|
+
<span v-else class="text-caption text-grey">-</span>
|
|
207
|
+
</v-col>
|
|
208
|
+
<v-col cols="1" class="pr-4">
|
|
209
|
+
<v-chip :color="getTypeColor(setting.type)" density="compact" style="border-radius: 7px">
|
|
210
|
+
{{ setting.type }}
|
|
211
|
+
</v-chip>
|
|
212
|
+
</v-col>
|
|
213
|
+
<v-col cols="3" class="pr-4 d-flex ga-2">
|
|
214
|
+
<v-icon size="small" color="grey" style="cursor: pointer;" @click="openShowModal(setting.key, setting.description, getSettingDescription(setting.key, setting.description))">mdi-eye</v-icon>
|
|
215
|
+
<span
|
|
216
|
+
class="text-caption text-grey"
|
|
217
|
+
style="display: inline-block; max-width: 700px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;"
|
|
218
|
+
>
|
|
219
|
+
{{ setting.description }}
|
|
220
|
+
</span>
|
|
221
|
+
</v-col>
|
|
222
|
+
<v-col cols="1" class="pr-4">
|
|
223
|
+
<v-chip
|
|
224
|
+
:color="setting.public ? 'green' : 'red'"
|
|
225
|
+
density="compact" style="border-radius: 7px;"
|
|
226
|
+
>
|
|
227
|
+
<v-icon start>{{ setting.public ? 'mdi-web' : 'mdi-lock' }}</v-icon>
|
|
228
|
+
{{ setting.public ? 'Público' : 'Privado' }}
|
|
229
|
+
</v-chip>
|
|
230
|
+
</v-col>
|
|
231
|
+
<v-col cols="1" class="pr-4">
|
|
232
|
+
<span class="text-caption" style="display: inline-block; max-width: 100%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">
|
|
233
|
+
{{ setting.permission ? setting.permission : 'N/A' }}
|
|
234
|
+
</span>
|
|
235
|
+
</v-col>
|
|
236
|
+
<v-col cols="2" class="pr-4">
|
|
237
|
+
<span>
|
|
238
|
+
{{ dayjs(setting.updatedAt).format('DD/MM/YYYY HH:mm') }}<br>
|
|
239
|
+
{{ setting.updatedBy }}
|
|
240
|
+
</span>
|
|
241
|
+
</v-col>
|
|
242
|
+
<v-col cols="1" class="d-flex justify-end">
|
|
243
|
+
<v-menu location="bottom right">
|
|
244
|
+
<template v-slot:activator="{ props }">
|
|
245
|
+
<v-btn icon size="small" variant="text" v-bind="props">
|
|
246
|
+
<v-icon>mdi-dots-vertical</v-icon>
|
|
247
|
+
</v-btn>
|
|
248
|
+
</template>
|
|
249
|
+
<v-list>
|
|
250
|
+
<v-list-item @click="openInformationUpdatingModal(setting, true)">
|
|
251
|
+
<v-icon start>mdi-eye</v-icon>
|
|
252
|
+
{{ t('action.view') }}
|
|
253
|
+
</v-list-item>
|
|
254
|
+
<v-list-item @click="openInformationUpdatingModal(setting, false)">
|
|
255
|
+
<v-icon start>mdi-pencil</v-icon>
|
|
256
|
+
{{ t('action.edit') }}
|
|
257
|
+
</v-list-item>
|
|
258
|
+
</v-list>
|
|
259
|
+
</v-menu>
|
|
260
|
+
</v-col>
|
|
261
|
+
</v-row>
|
|
262
|
+
<v-divider class="my-2"></v-divider>
|
|
263
|
+
</template>
|
|
264
|
+
</tbody>
|
|
265
|
+
</table>
|
|
266
|
+
</template>
|
|
267
|
+
</v-expansion-panel>
|
|
268
|
+
</v-expansion-panels>
|
|
269
|
+
</div>
|
|
270
|
+
</div>
|
|
271
|
+
|
|
272
|
+
<!-- SHOW MODAL -->
|
|
273
|
+
|
|
274
|
+
<v-dialog v-model="showModal.isOpen" width="auto">
|
|
275
|
+
<v-card width="800">
|
|
276
|
+
<v-toolbar class="pl-4">
|
|
277
|
+
<v-icon start>mdi-information</v-icon>
|
|
278
|
+
<v-card-title>{{ showModal.setting?.category === 'Description' ? t('setting.field.description') : t('setting.field.value') }}</v-card-title>
|
|
279
|
+
<v-spacer></v-spacer>
|
|
280
|
+
<v-btn text @click="closeShowModal">
|
|
281
|
+
<v-icon>mdi-close</v-icon>
|
|
282
|
+
</v-btn>
|
|
283
|
+
</v-toolbar>
|
|
284
|
+
<v-card-text>
|
|
285
|
+
<!-- @vue-ignore -->
|
|
286
|
+
<setting-field
|
|
287
|
+
:model-value="showModal.value"
|
|
288
|
+
:setting="showModal.setting"
|
|
289
|
+
:editing="false"
|
|
290
|
+
></setting-field>
|
|
291
|
+
</v-card-text>
|
|
292
|
+
</v-card>
|
|
293
|
+
</v-dialog>
|
|
294
|
+
|
|
295
|
+
<!-- INFORMATION / UPDATING MODAL -->
|
|
296
|
+
|
|
297
|
+
<v-dialog v-model="informationUpdatingModal.isOpen" width="auto">
|
|
298
|
+
<v-card width="1200">
|
|
299
|
+
<v-toolbar class="px-4">
|
|
300
|
+
<v-icon start>{{ informationUpdatingModal.readonly ? 'mdi-eye' : 'mdi-pencil' }}</v-icon>
|
|
301
|
+
<v-card-title>
|
|
302
|
+
{{ informationUpdatingModal.readonly ? t('action.view') : t('action.edit') }} {{t('setting.configurationValue.title')}}
|
|
303
|
+
</v-card-title>
|
|
304
|
+
<v-spacer></v-spacer>
|
|
305
|
+
<v-icon size="small" @click="closeInformationUpdatingModal">mdi-close</v-icon>
|
|
306
|
+
</v-toolbar>
|
|
307
|
+
<v-card-text>
|
|
308
|
+
{{ t('setting.configurationValue.subtitle') }}
|
|
309
|
+
|
|
310
|
+
<v-form ref="form">
|
|
311
|
+
<v-row class="mt-5">
|
|
312
|
+
<v-col cols="12" md="8" class="ma-0 pa-0 px-3" v-if="informationUpdatingModal.setting?.category">
|
|
313
|
+
<v-text-field
|
|
314
|
+
density="compact"
|
|
315
|
+
variant="outlined"
|
|
316
|
+
:label="t('setting.field.category')"
|
|
317
|
+
readonly :model-value="informationUpdatingModal.setting?.category"
|
|
318
|
+
></v-text-field>
|
|
319
|
+
</v-col>
|
|
320
|
+
<v-col cols="12" md="4" class="ma-0 pa-0 px-3">
|
|
321
|
+
<v-checkbox
|
|
322
|
+
:label="t('setting.field.publicinfo')" density="compact"
|
|
323
|
+
readonly :model-value="informationUpdatingModal.setting?.public"
|
|
324
|
+
></v-checkbox>
|
|
325
|
+
</v-col>
|
|
326
|
+
<v-col cols="12" md="8" class="ma-0 pa-0 px-3">
|
|
327
|
+
<v-text-field
|
|
328
|
+
density="compact"
|
|
329
|
+
variant="outlined"
|
|
330
|
+
:label="t('setting.field.key')"
|
|
331
|
+
readonly :model-value="informationUpdatingModal.setting?.key"
|
|
332
|
+
></v-text-field>
|
|
333
|
+
</v-col>
|
|
334
|
+
<v-col cols="12" md="4" class="ma-0 pa-0 px-3">
|
|
335
|
+
<v-text-field
|
|
336
|
+
density="compact"
|
|
337
|
+
variant="outlined"
|
|
338
|
+
:label="t('setting.field.type')"
|
|
339
|
+
readonly :model-value="informationUpdatingModal.setting?.type"
|
|
340
|
+
></v-text-field>
|
|
341
|
+
</v-col>
|
|
342
|
+
<v-col cols="12" md="12" class="ma-0 pa-0 px-3" v-if="informationUpdatingModal.setting?.permission">
|
|
343
|
+
<v-text-field
|
|
344
|
+
density="compact"
|
|
345
|
+
variant="outlined"
|
|
346
|
+
:label="t('setting.field.permission')"
|
|
347
|
+
readonly :model-value="informationUpdatingModal.setting?.permission"
|
|
348
|
+
></v-text-field>
|
|
349
|
+
</v-col>
|
|
350
|
+
<v-col cols="12" md="6" class="ma-0 pa-0 px-3" v-if="informationUpdatingModal.setting?.prefix">
|
|
351
|
+
<v-text-field
|
|
352
|
+
density="compact"
|
|
353
|
+
variant="outlined"
|
|
354
|
+
:label="t('setting.field.prefix')"
|
|
355
|
+
readonly :model-value="informationUpdatingModal.setting?.prefix"
|
|
356
|
+
></v-text-field>
|
|
357
|
+
</v-col>
|
|
358
|
+
<v-col cols="12" md="6" class="ma-0 pa-0 px-3" v-if="informationUpdatingModal.setting?.suffix">
|
|
359
|
+
<v-text-field
|
|
360
|
+
density="compact"
|
|
361
|
+
variant="outlined"
|
|
362
|
+
:label="t('setting.field.suffix')"
|
|
363
|
+
readonly :model-value="informationUpdatingModal.setting?.suffix"
|
|
364
|
+
></v-text-field>
|
|
365
|
+
</v-col>
|
|
366
|
+
<v-col cols="12" md="12" class="ma-0 pa-0 px-3">
|
|
367
|
+
<!-- @vue-ignore -->
|
|
368
|
+
<setting-field
|
|
369
|
+
variant="outlined"
|
|
370
|
+
v-model="informationUpdatingModal.setting.value"
|
|
371
|
+
:setting="informationUpdatingModal.setting"
|
|
372
|
+
:editing="!informationUpdatingModal.readonly"
|
|
373
|
+
></setting-field>
|
|
374
|
+
</v-col>
|
|
375
|
+
</v-row>
|
|
376
|
+
</v-form>
|
|
377
|
+
</v-card-text>
|
|
378
|
+
<v-card-actions>
|
|
379
|
+
<v-spacer></v-spacer>
|
|
380
|
+
<v-btn variant="text" @click="closeInformationUpdatingModal">{{ t('action.close') }}</v-btn>
|
|
381
|
+
<v-btn
|
|
382
|
+
v-if="!informationUpdatingModal.readonly"
|
|
383
|
+
variant="elevated" color="primary"
|
|
384
|
+
@click="updateSetting(informationUpdatingModal.setting)"
|
|
385
|
+
:loading="updateLoading"
|
|
386
|
+
>
|
|
387
|
+
{{ t('action.save') }}
|
|
388
|
+
</v-btn>
|
|
389
|
+
</v-card-actions>
|
|
390
|
+
</v-card>
|
|
391
|
+
</v-dialog>
|
|
392
|
+
</template>
|
|
393
|
+
|
|
394
|
+
<style scoped>
|
|
395
|
+
|
|
396
|
+
</style>
|
|
@@ -1,23 +1,21 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
|
|
3
3
|
import type {ISetting} from "@drax/settings-share";
|
|
4
|
+
import {CrudAutocomplete, useEntityStore} from "@drax/crud-vue";
|
|
4
5
|
import {type PropType, ref, computed} from "vue";
|
|
5
6
|
|
|
6
7
|
|
|
7
8
|
const valueModel = defineModel<any>({type: [String, Number, Boolean, Object, Array], default: false})
|
|
8
9
|
|
|
9
10
|
|
|
10
|
-
const {setting, editing} = defineProps({
|
|
11
|
+
const {setting, editing, variant} = defineProps({
|
|
11
12
|
setting: {type: Object as PropType<ISetting>, required: true},
|
|
12
|
-
editing: {type: Boolean as PropType<boolean>, default: false}
|
|
13
|
+
editing: {type: Boolean as PropType<boolean>, default: false},
|
|
14
|
+
variant: {type: String as PropType<'filled' | 'outlined' | 'underlined'>, default: 'filled'},
|
|
13
15
|
})
|
|
14
16
|
|
|
15
17
|
const visible = ref(false)
|
|
16
18
|
|
|
17
|
-
const variant = computed(() => {
|
|
18
|
-
return editing ? 'filled' : 'underlined'
|
|
19
|
-
})
|
|
20
|
-
|
|
21
19
|
const validateRegex = computed(() => {
|
|
22
20
|
return [(val: any) => {
|
|
23
21
|
if (!setting.regex) return true
|
|
@@ -49,6 +47,14 @@ const validateNumberList = computed(() => {
|
|
|
49
47
|
}]
|
|
50
48
|
})
|
|
51
49
|
|
|
50
|
+
const entityStore = useEntityStore()
|
|
51
|
+
|
|
52
|
+
const getEntity = computed(() => {
|
|
53
|
+
return (entity: string | undefined) => {
|
|
54
|
+
return entity ? entityStore.getEntity(entity) : undefined
|
|
55
|
+
}
|
|
56
|
+
})
|
|
57
|
+
|
|
52
58
|
|
|
53
59
|
</script>
|
|
54
60
|
|
|
@@ -93,7 +99,7 @@ const validateNumberList = computed(() => {
|
|
|
93
99
|
|
|
94
100
|
|
|
95
101
|
<!--password-->
|
|
96
|
-
<v-text-field v-if=" setting.type === 'password'"
|
|
102
|
+
<v-text-field v-if=" setting.type === 'password' || setting.type === 'secret'"
|
|
97
103
|
:name="setting.key"
|
|
98
104
|
v-model="valueModel"
|
|
99
105
|
:label="setting.label"
|
|
@@ -224,17 +230,12 @@ const validateNumberList = computed(() => {
|
|
|
224
230
|
</v-select>
|
|
225
231
|
|
|
226
232
|
<!--ref-->
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
<!-- v-model="valueModel"-->
|
|
234
|
-
<!-- :label="setting.label"-->
|
|
235
|
-
<!-- :placeholder="setting.label"-->
|
|
236
|
-
<!-- color="secondary">-->
|
|
237
|
-
<!-- </v-select>-->
|
|
233
|
+
<crud-autocomplete
|
|
234
|
+
v-if="setting.type ==='ref'"
|
|
235
|
+
v-model="valueModel"
|
|
236
|
+
:entity="getEntity(setting.entity)"
|
|
237
|
+
:field="{name: setting.key, type: 'ref', label: setting.label, default:null}"
|
|
238
|
+
></crud-autocomplete>
|
|
238
239
|
|
|
239
240
|
</template>
|
|
240
241
|
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import {useSetting} from "../composables/UseSetting";
|
|
3
3
|
import SettingEditor from "./SettingEditor.vue";
|
|
4
|
-
import {onMounted, ref} from "vue";
|
|
4
|
+
import {onMounted, ref, computed} from "vue";
|
|
5
5
|
import {useI18n} from "vue-i18n";
|
|
6
6
|
import type {ISetting} from "@drax/settings-share";
|
|
7
|
+
import {CrudAutocomplete, useEntityStore} from "@drax/crud-vue";
|
|
7
8
|
|
|
8
9
|
const {fetchSettings, settingsGrouped} = useSetting()
|
|
9
10
|
const {t, te} = useI18n()
|
|
10
11
|
|
|
11
12
|
onMounted(async () => {
|
|
12
13
|
await fetchSettings()
|
|
14
|
+
onSearchUpdate('')
|
|
13
15
|
})
|
|
14
16
|
|
|
15
17
|
const settingEditing = ref()
|
|
@@ -21,7 +23,7 @@ function edit(setting: ISetting) {
|
|
|
21
23
|
editing.value = true
|
|
22
24
|
}
|
|
23
25
|
|
|
24
|
-
function clearEdit() {
|
|
26
|
+
function clearEdit(newValue: any) {
|
|
25
27
|
editing.value = false
|
|
26
28
|
settingEditing.value = null
|
|
27
29
|
}
|
|
@@ -38,11 +40,47 @@ function getObfuscatedValue(value: string): string {
|
|
|
38
40
|
return '•'.repeat(Math.min(value?.length || 8, 12))
|
|
39
41
|
}
|
|
40
42
|
|
|
43
|
+
const search = ref<string>('')
|
|
44
|
+
function onSearchUpdate(value: string) {
|
|
45
|
+
search.value = value
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const settingsGroupedFiltered = computed(() => {
|
|
49
|
+
if (!search.value) {
|
|
50
|
+
return settingsGrouped.value
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const filtered: Record<string, ISetting[]> = {}
|
|
54
|
+
const searchLower = search.value.toLowerCase()
|
|
55
|
+
|
|
56
|
+
for (const groupKey of Object.keys(settingsGrouped.value)) {
|
|
57
|
+
const filteredSettings = settingsGrouped.value[groupKey].filter((setting: ISetting) =>
|
|
58
|
+
setting.key.toLowerCase().includes(searchLower) ||
|
|
59
|
+
(setting.description && setting.description.toLowerCase().includes(searchLower))
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
if (filteredSettings.length > 0) {
|
|
63
|
+
filtered[groupKey] = filteredSettings
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return filtered
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
const entityStore = useEntityStore()
|
|
71
|
+
|
|
72
|
+
const getEntity = computed(() => {
|
|
73
|
+
return (entity: string | undefined) => {
|
|
74
|
+
return entity ? entityStore.getEntity(entity) : undefined
|
|
75
|
+
}
|
|
76
|
+
})
|
|
77
|
+
|
|
41
78
|
</script>
|
|
42
79
|
|
|
43
80
|
<template>
|
|
44
81
|
<div>
|
|
45
|
-
<
|
|
82
|
+
<h3 class="mb-2 text-h3">{{t('setting.menu')}}</h3>
|
|
83
|
+
<v-divider class="mb-3"></v-divider>
|
|
46
84
|
|
|
47
85
|
<setting-editor
|
|
48
86
|
v-if="editing"
|
|
@@ -52,21 +90,30 @@ function getObfuscatedValue(value: string): string {
|
|
|
52
90
|
></setting-editor>
|
|
53
91
|
|
|
54
92
|
<v-row>
|
|
55
|
-
<v-col
|
|
93
|
+
<v-col>
|
|
94
|
+
<v-text-field
|
|
95
|
+
:placeholder="t('setting.search')"
|
|
96
|
+
persistent-placeholder
|
|
97
|
+
clearable variant="outlined"
|
|
98
|
+
prepend-inner-icon="mdi-magnify"
|
|
99
|
+
v-model="search"
|
|
100
|
+
@update:model-value="onSearchUpdate"
|
|
101
|
+
></v-text-field>
|
|
102
|
+
</v-col>
|
|
103
|
+
|
|
104
|
+
<v-col cols="12" v-for="(category,k) in settingsGroupedFiltered">
|
|
56
105
|
<v-card>
|
|
57
106
|
<v-card-title>
|
|
58
|
-
<h4 class="text-h4 mt-2">{{ k }}</h4>
|
|
107
|
+
<h4 class="text-h4 mt-2 ">{{ k }}</h4>
|
|
59
108
|
</v-card-title>
|
|
60
109
|
<v-card-text>
|
|
61
110
|
|
|
62
111
|
<v-data-table
|
|
63
112
|
hide-default-footer
|
|
64
113
|
:headers="[
|
|
65
|
-
{ title: t('setting.field.
|
|
66
|
-
{ title: t('setting.field.label'), key: 'label', width: '130px', minWidth: '130px' },
|
|
114
|
+
{ title: t('setting.field.variable'), key: 'label', width: '220px', minWidth: '220px', maxWidth: '220px' },
|
|
67
115
|
{ title: t('setting.field.value'), key: 'value' },
|
|
68
|
-
{ title: t('setting.field.
|
|
69
|
-
{ title: t('setting.field.scope'), key:'scope', width: '170px', minWidth: '170px' },
|
|
116
|
+
{ title: t('setting.field.config'), key: 'config', width: '220px', minWidth: '220px', maxWidth: '220px' },
|
|
70
117
|
{ title: t('action.edit'), key: 'actions', width: '70px' },
|
|
71
118
|
|
|
72
119
|
]"
|
|
@@ -74,18 +121,25 @@ function getObfuscatedValue(value: string): string {
|
|
|
74
121
|
|
|
75
122
|
>
|
|
76
123
|
|
|
124
|
+
|
|
125
|
+
<template v-slot:item.label="{ item }">
|
|
126
|
+
<span class="font-weight-bold">{{item.label }}</span><br>
|
|
127
|
+
<span class="font-italic">{{ item.description }}</span>
|
|
128
|
+
</template>
|
|
129
|
+
|
|
77
130
|
<template v-slot:item.value="{ item }">
|
|
78
|
-
<v-card variant="tonal" density="compact" class="my-1"
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
<v-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
<
|
|
87
|
-
|
|
88
|
-
|
|
131
|
+
<v-card :variant="['stringList','numberList','enumList','boolean','ref'].includes(item.type) ? 'flat' : 'tonal'" density="compact" class="my-1">
|
|
132
|
+
<v-card-text>
|
|
133
|
+
{{ item.prefix }}
|
|
134
|
+
<template v-if="item.type === 'boolean'">
|
|
135
|
+
<v-chip size="large" :color="item.value ? 'green' : 'red' " tile>
|
|
136
|
+
{{ item.value }}
|
|
137
|
+
</v-chip>
|
|
138
|
+
</template>
|
|
139
|
+
<template v-else-if="['stringList','numberList','enumList'].includes(item.type)">
|
|
140
|
+
<v-chip v-for="(v,i) in item.value" :key="i">{{v}}</v-chip>
|
|
141
|
+
</template>
|
|
142
|
+
<template v-else-if="['password','secret'].includes(item.type)">
|
|
89
143
|
<span class="d-inline-flex align-center">
|
|
90
144
|
<span class="mr-2">
|
|
91
145
|
{{ isSecretVisible(item.key) ? item.value : getObfuscatedValue(item.value) }}
|
|
@@ -97,26 +151,42 @@ function getObfuscatedValue(value: string): string {
|
|
|
97
151
|
@click="toggleSecretVisibility(item.key)"
|
|
98
152
|
></v-btn>
|
|
99
153
|
</span>
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
154
|
+
</template>
|
|
155
|
+
<template v-else-if="['ref'].includes(item.type)">
|
|
156
|
+
<!--ref-->
|
|
157
|
+
<crud-autocomplete
|
|
158
|
+
readonly
|
|
159
|
+
v-model="item.value"
|
|
160
|
+
:entity="getEntity(item.entity)"
|
|
161
|
+
:field="{name: item.key, type: 'ref', label: item.label, default:null}"
|
|
162
|
+
:clearable="false"
|
|
163
|
+
></crud-autocomplete>
|
|
164
|
+
</template>
|
|
165
|
+
<template v-else>
|
|
166
|
+
{{item.value}}
|
|
167
|
+
</template>
|
|
168
|
+
{{ item.suffix }}
|
|
169
|
+
</v-card-text>
|
|
170
|
+
</v-card>
|
|
106
171
|
</template>
|
|
107
172
|
|
|
108
|
-
<template v-slot:item.
|
|
109
|
-
<div
|
|
110
|
-
|
|
173
|
+
<template v-slot:item.config="{ item }">
|
|
174
|
+
<div class="py-1">
|
|
175
|
+
<span class="font-weight-bold">{{item.key }}</span><br>
|
|
176
|
+
<span class="font-italic">{{ item.type }}</span>
|
|
177
|
+
<div>
|
|
178
|
+
<v-chip tile v-if="item?.permission" density="compact" color="purple" prepend-icon="mdi-key">
|
|
111
179
|
{{
|
|
112
180
|
te('permission.' + item?.permission) ? t('permission.' + item?.permission) : item?.permission
|
|
113
181
|
}}
|
|
114
182
|
</v-chip>
|
|
115
|
-
<v-chip v-else-if="item?.public" color="blue" density="compact">Public</v-chip>
|
|
116
|
-
<v-chip v-else color="orange" density="compact">Private</v-chip>
|
|
183
|
+
<v-chip tile v-else-if="item?.public" color="blue" density="compact" prepend-icon="mdi-earth">Public</v-chip>
|
|
184
|
+
<v-chip tile v-else color="orange" density="compact" prepend-icon="mdi-lock">Private</v-chip>
|
|
185
|
+
</div>
|
|
117
186
|
</div>
|
|
118
187
|
</template>
|
|
119
188
|
|
|
189
|
+
|
|
120
190
|
<template v-slot:item.actions="{ item }">
|
|
121
191
|
<v-btn
|
|
122
192
|
class="mr-1 mt-1"
|
package/src/index.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {useSetting} from "./composables/UseSetting";
|
|
2
2
|
import {useSettingStore} from "./stores/UseSettingStore";
|
|
3
3
|
import SettingCardConfig from "./components/SettingCardConfig.vue";
|
|
4
|
-
import SettingTableConfig from "./components/
|
|
4
|
+
import SettingTableConfig from "./components/SettingAvConfig.vue";
|
|
5
5
|
import SettingLoaded from "./components/SettingLoaded.vue";
|
|
6
6
|
import SettingPage from "./pages/SettingPage.vue";
|
|
7
7
|
import SettingRoutes from "./routes/SettingRoutes";
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import SettingPage from '../pages/SettingPage.vue'
|
|
2
2
|
import SettingCardPage from '../pages/SettingCardPage.vue'
|
|
3
|
+
import SettingAvPage from '../pages/SettingAvPage.vue'
|
|
3
4
|
|
|
4
5
|
const routes = [
|
|
5
6
|
{
|
|
@@ -20,7 +21,15 @@ const routes = [
|
|
|
20
21
|
permission: 'setting:manage'
|
|
21
22
|
}
|
|
22
23
|
},
|
|
23
|
-
|
|
24
|
+
{
|
|
25
|
+
name: 'SettingAvPage',
|
|
26
|
+
path: '/settings/av',
|
|
27
|
+
component: SettingAvPage,
|
|
28
|
+
meta: {
|
|
29
|
+
auth: true,
|
|
30
|
+
permission: 'setting:manage'
|
|
31
|
+
}
|
|
32
|
+
},
|
|
24
33
|
]
|
|
25
34
|
|
|
26
35
|
|