adminforth 1.3.54-next.17 → 1.3.54-next.18
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/dataConnectors/clickhouse.ts +5 -1
- package/dataConnectors/postgres.ts +5 -1
- package/dataConnectors/sqlite.ts +5 -1
- package/dist/dataConnectors/clickhouse.js +6 -1
- package/dist/dataConnectors/postgres.js +6 -1
- package/dist/dataConnectors/sqlite.js +6 -1
- package/package.json +1 -1
- package/spa/package-lock.json +58 -1
- package/spa/package.json +2 -1
- package/spa/src/App.vue +2 -2
- package/spa/src/components/ResourceForm.vue +37 -2
- package/spa/src/components/ValueRenderer.vue +27 -0
- package/spa/src/views/CreateView.vue +1 -1
- package/spa/src/views/EditView.vue +1 -1
|
@@ -117,7 +117,11 @@ class ClickhouseConnector extends AdminForthBaseConnector implements IAdminForth
|
|
|
117
117
|
return !!value;
|
|
118
118
|
} else if (field.type == AdminForthDataTypes.JSON) {
|
|
119
119
|
if (field._underlineType.startsWith('String') || field._underlineType.startsWith('FixedString')) {
|
|
120
|
-
|
|
120
|
+
try {
|
|
121
|
+
return JSON.parse(value);
|
|
122
|
+
} catch (e) {
|
|
123
|
+
return {'error': `Failed to parse JSON: ${e.message}`}
|
|
124
|
+
}
|
|
121
125
|
} else {
|
|
122
126
|
console.error(`AdminForth: JSON field is not a string but ${field._underlineType}, this is not supported yet`);
|
|
123
127
|
}
|
|
@@ -153,7 +153,11 @@ class PostgresConnector extends AdminForthBaseConnector implements IAdminForthDa
|
|
|
153
153
|
|
|
154
154
|
if (field.type == AdminForthDataTypes.JSON) {
|
|
155
155
|
if (typeof value == 'string') {
|
|
156
|
-
|
|
156
|
+
try {
|
|
157
|
+
return JSON.parse(value);
|
|
158
|
+
} catch (e) {
|
|
159
|
+
return {'error': `Failed to parse JSON: ${e.message}`}
|
|
160
|
+
}
|
|
157
161
|
} else if (typeof value == 'object') {
|
|
158
162
|
return value;
|
|
159
163
|
} else {
|
package/dataConnectors/sqlite.ts
CHANGED
|
@@ -87,7 +87,11 @@ class SQLiteConnector extends AdminForthBaseConnector implements IAdminForthData
|
|
|
87
87
|
return !!value;
|
|
88
88
|
} else if (field.type == AdminForthDataTypes.JSON) {
|
|
89
89
|
if (field._underlineType == 'text' || field._underlineType == 'varchar') {
|
|
90
|
-
|
|
90
|
+
try {
|
|
91
|
+
return JSON.parse(value);
|
|
92
|
+
} catch (e) {
|
|
93
|
+
return {'error': `Failed to parse JSON: ${e.message}`}
|
|
94
|
+
}
|
|
91
95
|
} else {
|
|
92
96
|
console.error(`AdminForth: JSON field is not a string/text but ${field._underlineType}, this is not supported yet`);
|
|
93
97
|
}
|
|
@@ -145,7 +145,12 @@ class ClickhouseConnector extends AdminForthBaseConnector {
|
|
|
145
145
|
}
|
|
146
146
|
else if (field.type == AdminForthDataTypes.JSON) {
|
|
147
147
|
if (field._underlineType.startsWith('String') || field._underlineType.startsWith('FixedString')) {
|
|
148
|
-
|
|
148
|
+
try {
|
|
149
|
+
return JSON.parse(value);
|
|
150
|
+
}
|
|
151
|
+
catch (e) {
|
|
152
|
+
return { 'error': `Failed to parse JSON: ${e.message}` };
|
|
153
|
+
}
|
|
149
154
|
}
|
|
150
155
|
else {
|
|
151
156
|
console.error(`AdminForth: JSON field is not a string but ${field._underlineType}, this is not supported yet`);
|
|
@@ -154,7 +154,12 @@ class PostgresConnector extends AdminForthBaseConnector {
|
|
|
154
154
|
}
|
|
155
155
|
if (field.type == AdminForthDataTypes.JSON) {
|
|
156
156
|
if (typeof value == 'string') {
|
|
157
|
-
|
|
157
|
+
try {
|
|
158
|
+
return JSON.parse(value);
|
|
159
|
+
}
|
|
160
|
+
catch (e) {
|
|
161
|
+
return { 'error': `Failed to parse JSON: ${e.message}` };
|
|
162
|
+
}
|
|
158
163
|
}
|
|
159
164
|
else if (typeof value == 'object') {
|
|
160
165
|
return value;
|
|
@@ -120,7 +120,12 @@ class SQLiteConnector extends AdminForthBaseConnector {
|
|
|
120
120
|
}
|
|
121
121
|
else if (field.type == AdminForthDataTypes.JSON) {
|
|
122
122
|
if (field._underlineType == 'text' || field._underlineType == 'varchar') {
|
|
123
|
-
|
|
123
|
+
try {
|
|
124
|
+
return JSON.parse(value);
|
|
125
|
+
}
|
|
126
|
+
catch (e) {
|
|
127
|
+
return { 'error': `Failed to parse JSON: ${e.message}` };
|
|
128
|
+
}
|
|
124
129
|
}
|
|
125
130
|
else {
|
|
126
131
|
console.error(`AdminForth: JSON field is not a string/text but ${field._underlineType}, this is not supported yet`);
|
package/package.json
CHANGED
package/spa/package-lock.json
CHANGED
|
@@ -42,7 +42,8 @@
|
|
|
42
42
|
"tailwindcss": "^3.4.3",
|
|
43
43
|
"typescript": "~5.4.0",
|
|
44
44
|
"vite": "^5.2.13",
|
|
45
|
-
"vue-tsc": "^2.0.11"
|
|
45
|
+
"vue-tsc": "^2.0.11",
|
|
46
|
+
"vue3-json-viewer": "^2.2.2"
|
|
46
47
|
}
|
|
47
48
|
},
|
|
48
49
|
"node_modules/@alloc/quick-lru": {
|
|
@@ -1785,6 +1786,18 @@
|
|
|
1785
1786
|
"node": ">= 6"
|
|
1786
1787
|
}
|
|
1787
1788
|
},
|
|
1789
|
+
"node_modules/clipboard": {
|
|
1790
|
+
"version": "2.0.11",
|
|
1791
|
+
"resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.11.tgz",
|
|
1792
|
+
"integrity": "sha512-C+0bbOqkezLIsmWSvlsXS0Q0bmkugu7jcfMIACB+RDEntIzQIkdr148we28AfSloQLRdZlYL/QYyrq05j/3Faw==",
|
|
1793
|
+
"dev": true,
|
|
1794
|
+
"license": "MIT",
|
|
1795
|
+
"dependencies": {
|
|
1796
|
+
"good-listener": "^1.2.2",
|
|
1797
|
+
"select": "^1.1.2",
|
|
1798
|
+
"tiny-emitter": "^2.0.0"
|
|
1799
|
+
}
|
|
1800
|
+
},
|
|
1788
1801
|
"node_modules/color-convert": {
|
|
1789
1802
|
"version": "2.0.1",
|
|
1790
1803
|
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
|
@@ -1908,6 +1921,13 @@
|
|
|
1908
1921
|
"node": ">=0.10.0"
|
|
1909
1922
|
}
|
|
1910
1923
|
},
|
|
1924
|
+
"node_modules/delegate": {
|
|
1925
|
+
"version": "3.2.0",
|
|
1926
|
+
"resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz",
|
|
1927
|
+
"integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==",
|
|
1928
|
+
"dev": true,
|
|
1929
|
+
"license": "MIT"
|
|
1930
|
+
},
|
|
1911
1931
|
"node_modules/diacritics": {
|
|
1912
1932
|
"version": "1.3.0",
|
|
1913
1933
|
"resolved": "https://registry.npmjs.org/diacritics/-/diacritics-1.3.0.tgz",
|
|
@@ -2571,6 +2591,16 @@
|
|
|
2571
2591
|
"url": "https://github.com/sponsors/sindresorhus"
|
|
2572
2592
|
}
|
|
2573
2593
|
},
|
|
2594
|
+
"node_modules/good-listener": {
|
|
2595
|
+
"version": "1.2.2",
|
|
2596
|
+
"resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz",
|
|
2597
|
+
"integrity": "sha512-goW1b+d9q/HIwbVYZzZ6SsTr4IgE+WA44A0GmPIQstuOrgsFcT7VEJ48nmr9GaRtNu0XTKacFLGnBPAM6Afouw==",
|
|
2598
|
+
"dev": true,
|
|
2599
|
+
"license": "MIT",
|
|
2600
|
+
"dependencies": {
|
|
2601
|
+
"delegate": "^3.1.2"
|
|
2602
|
+
}
|
|
2603
|
+
},
|
|
2574
2604
|
"node_modules/graphemer": {
|
|
2575
2605
|
"version": "1.4.0",
|
|
2576
2606
|
"resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
|
|
@@ -3742,6 +3772,13 @@
|
|
|
3742
3772
|
"node": ">=14.0.0"
|
|
3743
3773
|
}
|
|
3744
3774
|
},
|
|
3775
|
+
"node_modules/select": {
|
|
3776
|
+
"version": "1.1.2",
|
|
3777
|
+
"resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz",
|
|
3778
|
+
"integrity": "sha512-OwpTSOfy6xSs1+pwcNrv0RBMOzI39Lp3qQKUTPVVPRjCdNa5JH/oPRiqsesIskK8TVgmRiHwO4KXlV2Li9dANA==",
|
|
3779
|
+
"dev": true,
|
|
3780
|
+
"license": "MIT"
|
|
3781
|
+
},
|
|
3745
3782
|
"node_modules/semver": {
|
|
3746
3783
|
"version": "7.6.2",
|
|
3747
3784
|
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz",
|
|
@@ -4047,6 +4084,13 @@
|
|
|
4047
4084
|
"node": ">=0.8"
|
|
4048
4085
|
}
|
|
4049
4086
|
},
|
|
4087
|
+
"node_modules/tiny-emitter": {
|
|
4088
|
+
"version": "2.1.0",
|
|
4089
|
+
"resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz",
|
|
4090
|
+
"integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==",
|
|
4091
|
+
"dev": true,
|
|
4092
|
+
"license": "MIT"
|
|
4093
|
+
},
|
|
4050
4094
|
"node_modules/to-regex-range": {
|
|
4051
4095
|
"version": "5.0.1",
|
|
4052
4096
|
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
|
@@ -4433,6 +4477,19 @@
|
|
|
4433
4477
|
"typescript": "*"
|
|
4434
4478
|
}
|
|
4435
4479
|
},
|
|
4480
|
+
"node_modules/vue3-json-viewer": {
|
|
4481
|
+
"version": "2.2.2",
|
|
4482
|
+
"resolved": "https://registry.npmjs.org/vue3-json-viewer/-/vue3-json-viewer-2.2.2.tgz",
|
|
4483
|
+
"integrity": "sha512-56l3XDGggnpwEqZieXsSMhNT4NhtO6d7zuSAxHo4i0UVxymyY2jRb7UMQOU1ztChKALZCAzX7DlgrsnEhxu77A==",
|
|
4484
|
+
"dev": true,
|
|
4485
|
+
"license": "ISC",
|
|
4486
|
+
"dependencies": {
|
|
4487
|
+
"clipboard": "^2.0.10"
|
|
4488
|
+
},
|
|
4489
|
+
"peerDependencies": {
|
|
4490
|
+
"vue": "^3.2.0"
|
|
4491
|
+
}
|
|
4492
|
+
},
|
|
4436
4493
|
"node_modules/which": {
|
|
4437
4494
|
"version": "2.0.2",
|
|
4438
4495
|
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
package/spa/package.json
CHANGED
package/spa/src/App.vue
CHANGED
|
@@ -295,7 +295,7 @@ const theme = ref('light');
|
|
|
295
295
|
function toggleTheme() {
|
|
296
296
|
theme.value = theme.value === 'light' ? 'dark' : 'light';
|
|
297
297
|
document.documentElement.classList.toggle('dark');
|
|
298
|
-
window.localStorage.setItem('
|
|
298
|
+
window.localStorage.setItem('af__theme', theme.value);
|
|
299
299
|
|
|
300
300
|
}
|
|
301
301
|
|
|
@@ -390,7 +390,7 @@ onMounted(async () => {
|
|
|
390
390
|
})
|
|
391
391
|
|
|
392
392
|
onBeforeMount(()=>{
|
|
393
|
-
theme.value = window.localStorage.getItem('
|
|
393
|
+
theme.value = window.localStorage.getItem('af__theme') || 'light';
|
|
394
394
|
document.documentElement.classList.toggle('dark', theme.value === 'dark');
|
|
395
395
|
})
|
|
396
396
|
|
|
@@ -104,6 +104,14 @@
|
|
|
104
104
|
@input="setCurrentValue(column.name, $event.target.value)"
|
|
105
105
|
>
|
|
106
106
|
</textarea>
|
|
107
|
+
<textarea
|
|
108
|
+
v-else-if="['json'].includes(column.type)"
|
|
109
|
+
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
|
110
|
+
placeholder="Text"
|
|
111
|
+
:value="currentValues[column.name]"
|
|
112
|
+
@input="setCurrentValue(column.name, $event.target.value)"
|
|
113
|
+
>
|
|
114
|
+
</textarea>
|
|
107
115
|
<input
|
|
108
116
|
v-else
|
|
109
117
|
:type="!column.masked || unmasked[column.name] ? 'text' : 'password'"
|
|
@@ -155,7 +163,6 @@ import { useRouter, useRoute } from 'vue-router';
|
|
|
155
163
|
const router = useRouter();
|
|
156
164
|
const route = useRoute();
|
|
157
165
|
const props = defineProps({
|
|
158
|
-
loading: Boolean,
|
|
159
166
|
resource: Object,
|
|
160
167
|
record: Object,
|
|
161
168
|
validating: Boolean,
|
|
@@ -193,6 +200,13 @@ const columnError = (column) => {
|
|
|
193
200
|
) {
|
|
194
201
|
return 'This field is required';
|
|
195
202
|
}
|
|
203
|
+
if (column.type === 'json' && currentValues.value[column.name]) {
|
|
204
|
+
try {
|
|
205
|
+
JSON.parse(currentValues.value[column.name]);
|
|
206
|
+
} catch (e) {
|
|
207
|
+
return 'Invalid JSON';
|
|
208
|
+
}
|
|
209
|
+
}
|
|
196
210
|
if ( column.type === 'string' || column.type === 'text' ) {
|
|
197
211
|
if ( column.maxLength && currentValues.value[column.name]?.length > column.maxLength ) {
|
|
198
212
|
return `This field must be shorter than ${column.maxLength} characters`;
|
|
@@ -243,11 +257,32 @@ const setCurrentValue = (key, value) => {
|
|
|
243
257
|
}
|
|
244
258
|
|
|
245
259
|
currentValues.value = { ...currentValues.value };
|
|
246
|
-
|
|
260
|
+
|
|
261
|
+
//json fields should transform to object
|
|
262
|
+
const up = {...currentValues.value};
|
|
263
|
+
props.resource.columns.forEach((column) => {
|
|
264
|
+
if (column.type === 'json' && up[column.name]) {
|
|
265
|
+
try {
|
|
266
|
+
up[column.name] = JSON.parse(up[column.name]);
|
|
267
|
+
} catch (e) {
|
|
268
|
+
// do nothing
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
emit('update:record', up);
|
|
247
273
|
};
|
|
248
274
|
|
|
249
275
|
onMounted(() => {
|
|
276
|
+
|
|
250
277
|
currentValues.value = Object.assign({}, props.record);
|
|
278
|
+
// json values should transform to string
|
|
279
|
+
props.resource.columns.forEach((column) => {
|
|
280
|
+
if (column.type === 'json' && currentValues.value[column.name]) {
|
|
281
|
+
currentValues.value[column.name] = JSON.stringify(currentValues.value[column.name], null, 2);
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
console.log('currentValues', currentValues.value);
|
|
285
|
+
|
|
251
286
|
initFlowbite();
|
|
252
287
|
emit('update:isValid', isValid.value);
|
|
253
288
|
});
|
|
@@ -29,6 +29,9 @@
|
|
|
29
29
|
<span v-else-if="column.type === 'richtext'">
|
|
30
30
|
<div v-html="protectAgainstXSS(record[column.name])" class="allow-lists"></div>
|
|
31
31
|
</span>
|
|
32
|
+
<span v-else-if="column.type === 'json'">
|
|
33
|
+
<JsonViewer :value="record[column.name]" copyable sort :theme="theme" />
|
|
34
|
+
</span>
|
|
32
35
|
<span v-else>
|
|
33
36
|
{{ checkEmptyValues(record[column.name],route.meta.type) }}
|
|
34
37
|
</span>
|
|
@@ -44,13 +47,19 @@ import timezone from 'dayjs/plugin/timezone';
|
|
|
44
47
|
import {checkEmptyValues} from '@/utils';
|
|
45
48
|
import { useRoute, useRouter } from 'vue-router';
|
|
46
49
|
import sanitizeHtml from 'sanitize-html';
|
|
50
|
+
import { JsonViewer } from "vue3-json-viewer";
|
|
51
|
+
import "vue3-json-viewer/dist/index.css";
|
|
47
52
|
|
|
48
53
|
|
|
49
54
|
import { useCoreStore } from '@/stores/core';
|
|
55
|
+
import { computed } from 'vue';
|
|
50
56
|
|
|
51
57
|
const coreStore = useCoreStore();
|
|
52
58
|
const route = useRoute();
|
|
53
59
|
|
|
60
|
+
const theme = computed(() => {
|
|
61
|
+
return window.localStorage.getItem('af__theme') || 'light';
|
|
62
|
+
});
|
|
54
63
|
|
|
55
64
|
dayjs.extend(utc);
|
|
56
65
|
dayjs.extend(timezone);
|
|
@@ -111,4 +120,22 @@ function formatTime(time) {
|
|
|
111
120
|
}
|
|
112
121
|
|
|
113
122
|
}
|
|
123
|
+
</style>
|
|
124
|
+
|
|
125
|
+
<style lang="scss" >
|
|
126
|
+
|
|
127
|
+
.jv-container .jv-code {
|
|
128
|
+
padding: 10px 10px;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.jv-container .jv-button[class] {
|
|
132
|
+
@apply text-lightPrimary;
|
|
133
|
+
@apply dark:text-darkPrimary;
|
|
134
|
+
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.jv-container.jv-dark {
|
|
138
|
+
background: transparent;
|
|
139
|
+
}
|
|
140
|
+
|
|
114
141
|
</style>
|