@sfxcode/nuxt-ui-mongocamp 1.1.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/LICENSE +21 -0
- package/README.md +308 -0
- package/dist/module.d.mts +8 -0
- package/dist/module.json +12 -0
- package/dist/module.mjs +35 -0
- package/dist/runtime/components/MongocampAccount.d.vue.ts +3 -0
- package/dist/runtime/components/MongocampAccount.vue +197 -0
- package/dist/runtime/components/MongocampAccount.vue.d.ts +3 -0
- package/dist/runtime/components/MongocampCollectionData.d.vue.ts +6 -0
- package/dist/runtime/components/MongocampCollectionData.vue +511 -0
- package/dist/runtime/components/MongocampCollectionData.vue.d.ts +6 -0
- package/dist/runtime/components/MongocampCollectionInfos.d.vue.ts +9 -0
- package/dist/runtime/components/MongocampCollectionInfos.vue +393 -0
- package/dist/runtime/components/MongocampCollectionInfos.vue.d.ts +9 -0
- package/dist/runtime/components/MongocampCollections.d.vue.ts +7 -0
- package/dist/runtime/components/MongocampCollections.vue +231 -0
- package/dist/runtime/components/MongocampCollections.vue.d.ts +7 -0
- package/dist/runtime/components/MongocampDatabases.d.vue.ts +3 -0
- package/dist/runtime/components/MongocampDatabases.vue +112 -0
- package/dist/runtime/components/MongocampDatabases.vue.d.ts +3 -0
- package/dist/runtime/components/MongocampJobs.d.vue.ts +3 -0
- package/dist/runtime/components/MongocampJobs.vue +354 -0
- package/dist/runtime/components/MongocampJobs.vue.d.ts +3 -0
- package/dist/runtime/components/MongocampLogin.d.vue.ts +3 -0
- package/dist/runtime/components/MongocampLogin.vue +63 -0
- package/dist/runtime/components/MongocampLogin.vue.d.ts +3 -0
- package/dist/runtime/components/MongocampRoleGrants.d.vue.ts +6 -0
- package/dist/runtime/components/MongocampRoleGrants.vue +276 -0
- package/dist/runtime/components/MongocampRoleGrants.vue.d.ts +6 -0
- package/dist/runtime/components/MongocampRoles.d.vue.ts +6 -0
- package/dist/runtime/components/MongocampRoles.vue +272 -0
- package/dist/runtime/components/MongocampRoles.vue.d.ts +6 -0
- package/dist/runtime/components/MongocampUsers.d.vue.ts +3 -0
- package/dist/runtime/components/MongocampUsers.vue +320 -0
- package/dist/runtime/components/MongocampUsers.vue.d.ts +3 -0
- package/dist/runtime/components/MongocampVersion.d.vue.ts +3 -0
- package/dist/runtime/components/MongocampVersion.vue +25 -0
- package/dist/runtime/components/MongocampVersion.vue.d.ts +3 -0
- package/dist/runtime/composables/useMongocampAccount.d.ts +7 -0
- package/dist/runtime/composables/useMongocampAccount.js +28 -0
- package/dist/runtime/composables/useMongocampAdmin.d.ts +14 -0
- package/dist/runtime/composables/useMongocampAdmin.js +48 -0
- package/dist/runtime/composables/useMongocampBucket.d.ts +15 -0
- package/dist/runtime/composables/useMongocampBucket.js +109 -0
- package/dist/runtime/composables/useMongocampCollection.d.ts +17 -0
- package/dist/runtime/composables/useMongocampCollection.js +12 -0
- package/dist/runtime/composables/useMongocampDocument.d.ts +13 -0
- package/dist/runtime/composables/useMongocampDocument.js +18 -0
- package/dist/runtime/composables/useMongocampDynamicForm.d.ts +14 -0
- package/dist/runtime/composables/useMongocampDynamicForm.js +231 -0
- package/dist/runtime/composables/useMongocampIndex.d.ts +9 -0
- package/dist/runtime/composables/useMongocampIndex.js +23 -0
- package/dist/runtime/composables/useMongocampJobs.d.ts +9 -0
- package/dist/runtime/composables/useMongocampJobs.js +25 -0
- package/dist/runtime/composables/useMongocampQuery.d.ts +99 -0
- package/dist/runtime/composables/useMongocampQuery.js +108 -0
- package/dist/runtime/composables/useMongocampQueryBuilder.d.ts +30 -0
- package/dist/runtime/composables/useMongocampQueryBuilder.js +112 -0
- package/dist/runtime/composables/useMongocampSchema.d.ts +14 -0
- package/dist/runtime/composables/useMongocampSchema.js +142 -0
- package/dist/runtime/composables/useMongocampSystem.d.ts +12 -0
- package/dist/runtime/composables/useMongocampSystem.js +21 -0
- package/dist/runtime/plugin.d.ts +6 -0
- package/dist/runtime/plugin.js +23 -0
- package/dist/runtime/server/tsconfig.json +3 -0
- package/dist/types.d.mts +3 -0
- package/package.json +99 -0
|
@@ -0,0 +1,511 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { h, ref, resolveComponent, watch } from "vue";
|
|
3
|
+
import { useMongocampApi } from "#imports";
|
|
4
|
+
import useMongocampCollection from "../composables/useMongocampCollection";
|
|
5
|
+
import { useMongocampBucket } from "../composables/useMongocampBucket";
|
|
6
|
+
import { useMongocampSchema } from "../composables/useMongocampSchema";
|
|
7
|
+
import { columnsToFormKitSchema, documentToFormData, formDataToDocument } from "../composables/useMongocampDynamicForm";
|
|
8
|
+
import useMongocampDocument from "../composables/useMongocampDocument";
|
|
9
|
+
import { useMongocampQuery } from "../composables/useMongocampQuery";
|
|
10
|
+
const props = defineProps({
|
|
11
|
+
collectionName: { type: String, required: true }
|
|
12
|
+
});
|
|
13
|
+
const { documentApi } = useMongocampApi();
|
|
14
|
+
const { pagination, total } = useMongocampCollection();
|
|
15
|
+
const { isBucketCollection, fileIdForRow, downloadingFileIds, uploading, downloadFile, uploadFile } = useMongocampBucket();
|
|
16
|
+
const { schemaFromSamples } = useMongocampSchema();
|
|
17
|
+
const { ensureMetaData } = useMongocampDocument();
|
|
18
|
+
const { like, or } = useMongocampQuery();
|
|
19
|
+
const fileInputRef = ref(null);
|
|
20
|
+
function openFilePicker() {
|
|
21
|
+
fileInputRef.value?.click();
|
|
22
|
+
}
|
|
23
|
+
async function handleFileSelected(event) {
|
|
24
|
+
const input = event.target;
|
|
25
|
+
const file = input.files?.[0];
|
|
26
|
+
input.value = "";
|
|
27
|
+
if (!file) return;
|
|
28
|
+
const success = await uploadFile(props.collectionName, file);
|
|
29
|
+
if (success) await fetchDocuments();
|
|
30
|
+
}
|
|
31
|
+
const UButton = resolveComponent("UButton");
|
|
32
|
+
const UTooltip = resolveComponent("UTooltip");
|
|
33
|
+
function withTooltip(text, node) {
|
|
34
|
+
return h(UTooltip, { text }, { default: () => node });
|
|
35
|
+
}
|
|
36
|
+
const isEditModalOpen = ref(false);
|
|
37
|
+
const isDeleteModalOpen = ref(false);
|
|
38
|
+
const editMode = ref("insert");
|
|
39
|
+
const editDocId = ref(void 0);
|
|
40
|
+
const editJson = ref("{}");
|
|
41
|
+
const editError = ref("");
|
|
42
|
+
const docToDelete = ref(void 0);
|
|
43
|
+
const deleteError = ref("");
|
|
44
|
+
const editColumns = ref([]);
|
|
45
|
+
const editSchema = ref([]);
|
|
46
|
+
const editFormData = ref({});
|
|
47
|
+
const originalRow = ref(void 0);
|
|
48
|
+
function getDocId(row) {
|
|
49
|
+
const id = row._id;
|
|
50
|
+
if (typeof id === "string") return id;
|
|
51
|
+
if (typeof id === "object" && id !== null && "$oid" in id)
|
|
52
|
+
return String(id.$oid);
|
|
53
|
+
return void 0;
|
|
54
|
+
}
|
|
55
|
+
function openInsert() {
|
|
56
|
+
editMode.value = "insert";
|
|
57
|
+
editDocId.value = void 0;
|
|
58
|
+
editError.value = "";
|
|
59
|
+
originalRow.value = void 0;
|
|
60
|
+
editColumns.value = schemaFromSamples(documents.value);
|
|
61
|
+
if (editColumns.value.length > 0) {
|
|
62
|
+
editSchema.value = columnsToFormKitSchema(editColumns.value);
|
|
63
|
+
editFormData.value = {};
|
|
64
|
+
} else {
|
|
65
|
+
editJson.value = "{}";
|
|
66
|
+
}
|
|
67
|
+
isEditModalOpen.value = true;
|
|
68
|
+
}
|
|
69
|
+
function openEdit(row) {
|
|
70
|
+
editMode.value = "update";
|
|
71
|
+
editDocId.value = getDocId(row);
|
|
72
|
+
editError.value = "";
|
|
73
|
+
originalRow.value = row;
|
|
74
|
+
editColumns.value = schemaFromSamples(documents.value);
|
|
75
|
+
if (editColumns.value.length > 0) {
|
|
76
|
+
editSchema.value = columnsToFormKitSchema(editColumns.value);
|
|
77
|
+
editFormData.value = documentToFormData(row, editColumns.value);
|
|
78
|
+
} else {
|
|
79
|
+
editJson.value = JSON.stringify(row, null, 2);
|
|
80
|
+
}
|
|
81
|
+
isEditModalOpen.value = true;
|
|
82
|
+
}
|
|
83
|
+
function confirmDelete(row) {
|
|
84
|
+
docToDelete.value = getDocId(row);
|
|
85
|
+
deleteError.value = "";
|
|
86
|
+
isDeleteModalOpen.value = true;
|
|
87
|
+
}
|
|
88
|
+
async function handleSave() {
|
|
89
|
+
editError.value = "";
|
|
90
|
+
let payload;
|
|
91
|
+
if (editColumns.value.length > 0) {
|
|
92
|
+
payload = formDataToDocument(editFormData.value, editColumns.value, editMode.value === "update" ? originalRow.value : void 0);
|
|
93
|
+
} else {
|
|
94
|
+
try {
|
|
95
|
+
payload = JSON.parse(editJson.value);
|
|
96
|
+
} catch {
|
|
97
|
+
editError.value = "Invalid JSON";
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
ensureMetaData(payload);
|
|
102
|
+
try {
|
|
103
|
+
if (editMode.value === "insert") {
|
|
104
|
+
await documentApi.insert({ collectionName: props.collectionName, requestBody: payload });
|
|
105
|
+
} else {
|
|
106
|
+
if (!editDocId.value) return;
|
|
107
|
+
const body = { ...payload };
|
|
108
|
+
delete body._id;
|
|
109
|
+
await documentApi.update({ collectionName: props.collectionName, documentId: editDocId.value, requestBody: body });
|
|
110
|
+
}
|
|
111
|
+
isEditModalOpen.value = false;
|
|
112
|
+
await fetchDocuments();
|
|
113
|
+
} catch (e) {
|
|
114
|
+
editError.value = e instanceof Error ? e.message : "Error saving document";
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
async function handleDelete() {
|
|
118
|
+
deleteError.value = "";
|
|
119
|
+
if (!docToDelete.value) return;
|
|
120
|
+
try {
|
|
121
|
+
await documentApi._delete({ collectionName: props.collectionName, documentId: docToDelete.value });
|
|
122
|
+
isDeleteModalOpen.value = false;
|
|
123
|
+
await fetchDocuments();
|
|
124
|
+
} catch (e) {
|
|
125
|
+
deleteError.value = e instanceof Error ? e.message : "Error deleting document";
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
function makeActionsColumn() {
|
|
129
|
+
return {
|
|
130
|
+
id: "actions",
|
|
131
|
+
header: "",
|
|
132
|
+
cell: ({ row }) => {
|
|
133
|
+
const buttons = [];
|
|
134
|
+
if (isBucketCollection(props.collectionName)) {
|
|
135
|
+
const fileId = fileIdForRow(props.collectionName, row.original);
|
|
136
|
+
if (fileId) {
|
|
137
|
+
buttons.push(withTooltip("Download file", h(UButton, {
|
|
138
|
+
"icon": "i-lucide-download",
|
|
139
|
+
"color": "neutral",
|
|
140
|
+
"variant": "ghost",
|
|
141
|
+
"size": "sm",
|
|
142
|
+
"aria-label": "Download file",
|
|
143
|
+
"loading": downloadingFileIds.value.has(fileId),
|
|
144
|
+
"onClick": () => downloadFile(props.collectionName, fileId)
|
|
145
|
+
})));
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
buttons.push(
|
|
149
|
+
withTooltip("Edit document", h(UButton, {
|
|
150
|
+
"icon": "i-lucide-pencil",
|
|
151
|
+
"color": "neutral",
|
|
152
|
+
"variant": "ghost",
|
|
153
|
+
"size": "sm",
|
|
154
|
+
"aria-label": "Edit document",
|
|
155
|
+
"onClick": () => openEdit(row.original)
|
|
156
|
+
})),
|
|
157
|
+
withTooltip("Delete document", h(UButton, {
|
|
158
|
+
"icon": "i-lucide-trash-2",
|
|
159
|
+
"color": "error",
|
|
160
|
+
"variant": "ghost",
|
|
161
|
+
"size": "sm",
|
|
162
|
+
"aria-label": "Delete document",
|
|
163
|
+
"onClick": () => confirmDelete(row.original)
|
|
164
|
+
}))
|
|
165
|
+
);
|
|
166
|
+
return h("div", { class: "flex gap-1 justify-end" }, buttons);
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
function sortHeader(column, label) {
|
|
171
|
+
const isSorted = column.getIsSorted();
|
|
172
|
+
return h(UButton, {
|
|
173
|
+
color: "neutral",
|
|
174
|
+
variant: "ghost",
|
|
175
|
+
label,
|
|
176
|
+
icon: isSorted === "asc" ? "i-lucide-arrow-up-narrow-wide" : isSorted === "desc" ? "i-lucide-arrow-down-wide-narrow" : "i-lucide-arrow-up-down",
|
|
177
|
+
class: "-mx-2.5",
|
|
178
|
+
onClick: () => column.toggleSorting(isSorted === "asc")
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
const columns = ref([]);
|
|
182
|
+
const documents = ref([]);
|
|
183
|
+
const loading = ref(false);
|
|
184
|
+
const filterText = ref("");
|
|
185
|
+
const filterableColumns = ref([]);
|
|
186
|
+
const sorting = ref([]);
|
|
187
|
+
const isInitializing = ref(false);
|
|
188
|
+
const detailOpen = ref(false);
|
|
189
|
+
const detailContent = ref("");
|
|
190
|
+
let filterTimer;
|
|
191
|
+
function openDetail(value) {
|
|
192
|
+
detailContent.value = JSON.stringify(value, null, 2);
|
|
193
|
+
detailOpen.value = true;
|
|
194
|
+
}
|
|
195
|
+
function buildLuceneFilter(term) {
|
|
196
|
+
const t = term.trim();
|
|
197
|
+
if (!t || filterableColumns.value.length === 0) return void 0;
|
|
198
|
+
return or(...filterableColumns.value.map((col) => like(col, t))) || void 0;
|
|
199
|
+
}
|
|
200
|
+
function onFilterInput() {
|
|
201
|
+
clearTimeout(filterTimer);
|
|
202
|
+
filterTimer = setTimeout(() => {
|
|
203
|
+
pagination.value.pageIndex = 1;
|
|
204
|
+
fetchDocuments();
|
|
205
|
+
}, 300);
|
|
206
|
+
}
|
|
207
|
+
function sortingToMongoSort(s) {
|
|
208
|
+
return s.map((col) => col.desc ? `-${col.id}` : col.id);
|
|
209
|
+
}
|
|
210
|
+
watch(sorting, () => {
|
|
211
|
+
if (isInitializing.value) return;
|
|
212
|
+
pagination.value.pageIndex = 1;
|
|
213
|
+
fetchDocuments();
|
|
214
|
+
});
|
|
215
|
+
function toIso(value) {
|
|
216
|
+
if (!value) return "";
|
|
217
|
+
try {
|
|
218
|
+
if (value instanceof Date) return value.toISOString();
|
|
219
|
+
if (typeof value === "string" || typeof value === "number") return new Date(value).toISOString();
|
|
220
|
+
if (typeof value === "object") {
|
|
221
|
+
const obj = value;
|
|
222
|
+
if ("$date" in obj) {
|
|
223
|
+
const d = obj.$date;
|
|
224
|
+
if (typeof d === "string" || typeof d === "number") return new Date(d).toISOString();
|
|
225
|
+
if (typeof d === "object" && d !== null && "$numberLong" in d) {
|
|
226
|
+
return new Date(Number(d.$numberLong)).toISOString();
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return String(value);
|
|
231
|
+
} catch {
|
|
232
|
+
return String(value);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
function toDateDisplay(value) {
|
|
236
|
+
const iso = toIso(value);
|
|
237
|
+
if (!iso) return "";
|
|
238
|
+
try {
|
|
239
|
+
return new Date(iso).toLocaleString();
|
|
240
|
+
} catch {
|
|
241
|
+
return iso;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
function isMetaData(value) {
|
|
245
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
246
|
+
const obj = value;
|
|
247
|
+
return "created" in obj || "updated" in obj;
|
|
248
|
+
}
|
|
249
|
+
const CELL_TEXT_MAX_LENGTH = 60;
|
|
250
|
+
function truncateText(value, maxLength = CELL_TEXT_MAX_LENGTH) {
|
|
251
|
+
return value.length > maxLength ? `${value.slice(0, maxLength)}...` : value;
|
|
252
|
+
}
|
|
253
|
+
function makeCell(key, type) {
|
|
254
|
+
return ({ row }) => {
|
|
255
|
+
const raw = row.original[key];
|
|
256
|
+
if (type === "date-time" && raw) {
|
|
257
|
+
return h("span", toDateDisplay(raw));
|
|
258
|
+
}
|
|
259
|
+
if (raw === null || raw === void 0) {
|
|
260
|
+
return h("span", "");
|
|
261
|
+
}
|
|
262
|
+
if (typeof raw === "object") {
|
|
263
|
+
if (!Array.isArray(raw)) {
|
|
264
|
+
const obj = raw;
|
|
265
|
+
if ("$oid" in obj) return h("span", { class: "font-mono text-xs" }, String(obj.$oid));
|
|
266
|
+
if ("$date" in obj) return h("span", toDateDisplay(obj));
|
|
267
|
+
if (isMetaData(obj)) {
|
|
268
|
+
const rows = [];
|
|
269
|
+
if (obj.created) {
|
|
270
|
+
rows.push(h("div", [
|
|
271
|
+
h("span", { class: "text-dimmed" }, "created: "),
|
|
272
|
+
h("span", toDateDisplay(obj.created))
|
|
273
|
+
]));
|
|
274
|
+
}
|
|
275
|
+
if (obj.updated && obj.updated !== obj.created) {
|
|
276
|
+
rows.push(h("div", [
|
|
277
|
+
h("span", { class: "text-dimmed" }, "updated: "),
|
|
278
|
+
h("span", toDateDisplay(obj.updated))
|
|
279
|
+
]));
|
|
280
|
+
}
|
|
281
|
+
if (obj.createdBy) {
|
|
282
|
+
rows.push(h("div", [
|
|
283
|
+
h("span", { class: "text-dimmed" }, "by: "),
|
|
284
|
+
h("span", String(obj.createdBy))
|
|
285
|
+
]));
|
|
286
|
+
}
|
|
287
|
+
return h("div", { class: "flex flex-col gap-0.5 text-xs py-1" }, rows);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
const viewLabel = Array.isArray(raw) ? "View array" : "View object";
|
|
291
|
+
return withTooltip(viewLabel, h(UButton, {
|
|
292
|
+
"icon": Array.isArray(raw) ? "i-lucide-list" : "i-lucide-braces",
|
|
293
|
+
"color": "neutral",
|
|
294
|
+
"variant": "ghost",
|
|
295
|
+
"size": "xs",
|
|
296
|
+
"aria-label": viewLabel,
|
|
297
|
+
"onClick": () => openDetail(raw)
|
|
298
|
+
}));
|
|
299
|
+
}
|
|
300
|
+
const text = String(raw);
|
|
301
|
+
const truncated = truncateText(text);
|
|
302
|
+
return h("span", { title: truncated !== text ? text : void 0 }, truncated);
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
async function fetchDocuments() {
|
|
306
|
+
loading.value = true;
|
|
307
|
+
try {
|
|
308
|
+
const res = await documentApi.listDocumentsRaw({
|
|
309
|
+
collectionName: props.collectionName,
|
|
310
|
+
rowsPerPage: pagination.value.pageSize,
|
|
311
|
+
page: pagination.value.pageIndex,
|
|
312
|
+
sort: sorting.value.length ? sortingToMongoSort(sorting.value) : void 0,
|
|
313
|
+
filter: buildLuceneFilter(filterText.value)
|
|
314
|
+
});
|
|
315
|
+
total.value = +(res.raw.headers.get("x-pagination-count-rows") ?? 0);
|
|
316
|
+
const rows = await res.value();
|
|
317
|
+
documents.value = rows;
|
|
318
|
+
const firstRow = rows[0];
|
|
319
|
+
if (columns.value.length === 0 && firstRow) {
|
|
320
|
+
const keys = Object.keys(firstRow);
|
|
321
|
+
const orderedKeys = [
|
|
322
|
+
...keys.filter((k) => k === "_id"),
|
|
323
|
+
...keys.filter((k) => k !== "_id" && k !== "metaData").sort(),
|
|
324
|
+
...keys.filter((k) => k === "metaData")
|
|
325
|
+
];
|
|
326
|
+
columns.value = orderedKeys.map((key) => ({
|
|
327
|
+
accessorKey: key,
|
|
328
|
+
header: ({ column }) => sortHeader(column, key),
|
|
329
|
+
cell: makeCell(key, "string")
|
|
330
|
+
}));
|
|
331
|
+
filterableColumns.value = orderedKeys.filter((k) => k !== "_id" && k !== "metaData" && typeof firstRow[k] === "string");
|
|
332
|
+
columns.value.push(makeActionsColumn());
|
|
333
|
+
}
|
|
334
|
+
} finally {
|
|
335
|
+
loading.value = false;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
async function init() {
|
|
339
|
+
isInitializing.value = true;
|
|
340
|
+
sorting.value = [];
|
|
341
|
+
pagination.value.pageIndex = 1;
|
|
342
|
+
filterText.value = "";
|
|
343
|
+
filterableColumns.value = [];
|
|
344
|
+
columns.value = [];
|
|
345
|
+
documents.value = [];
|
|
346
|
+
await fetchDocuments();
|
|
347
|
+
isInitializing.value = false;
|
|
348
|
+
}
|
|
349
|
+
watch(() => props.collectionName, init, { immediate: true });
|
|
350
|
+
</script>
|
|
351
|
+
|
|
352
|
+
<template>
|
|
353
|
+
<div class="flex flex-col gap-4">
|
|
354
|
+
<div class="flex items-center justify-between gap-4">
|
|
355
|
+
<UInput
|
|
356
|
+
v-model="filterText"
|
|
357
|
+
icon="i-lucide-search"
|
|
358
|
+
placeholder="Filter rows..."
|
|
359
|
+
size="sm"
|
|
360
|
+
class="flex-1 max-w-xs"
|
|
361
|
+
:disabled="filterableColumns.length === 0"
|
|
362
|
+
@input="onFilterInput"
|
|
363
|
+
/>
|
|
364
|
+
<div class="flex items-center gap-2">
|
|
365
|
+
<span class="text-sm text-gray-500 dark:text-gray-400 shrink-0">
|
|
366
|
+
{{ total }} documents
|
|
367
|
+
</span>
|
|
368
|
+
<UTooltip
|
|
369
|
+
v-if="isBucketCollection(collectionName)"
|
|
370
|
+
text="Upload file"
|
|
371
|
+
>
|
|
372
|
+
<UButton
|
|
373
|
+
icon="i-lucide-upload"
|
|
374
|
+
color="primary"
|
|
375
|
+
variant="ghost"
|
|
376
|
+
size="sm"
|
|
377
|
+
:loading="uploading"
|
|
378
|
+
aria-label="Upload file"
|
|
379
|
+
@click="openFilePicker"
|
|
380
|
+
/>
|
|
381
|
+
</UTooltip>
|
|
382
|
+
<UTooltip
|
|
383
|
+
v-else
|
|
384
|
+
text="Insert document"
|
|
385
|
+
>
|
|
386
|
+
<UButton
|
|
387
|
+
icon="i-lucide-plus"
|
|
388
|
+
color="primary"
|
|
389
|
+
variant="ghost"
|
|
390
|
+
size="sm"
|
|
391
|
+
aria-label="Insert document"
|
|
392
|
+
@click="openInsert"
|
|
393
|
+
/>
|
|
394
|
+
</UTooltip>
|
|
395
|
+
<input
|
|
396
|
+
ref="fileInputRef"
|
|
397
|
+
type="file"
|
|
398
|
+
class="hidden"
|
|
399
|
+
@change="handleFileSelected"
|
|
400
|
+
>
|
|
401
|
+
<UTooltip text="Reload">
|
|
402
|
+
<UButton
|
|
403
|
+
icon="i-lucide-refresh-cw"
|
|
404
|
+
color="neutral"
|
|
405
|
+
variant="ghost"
|
|
406
|
+
:loading="loading"
|
|
407
|
+
aria-label="Reload"
|
|
408
|
+
@click="init"
|
|
409
|
+
/>
|
|
410
|
+
</UTooltip>
|
|
411
|
+
<UPagination
|
|
412
|
+
v-model:page="pagination.pageIndex"
|
|
413
|
+
:total="total"
|
|
414
|
+
:items-per-page="pagination.pageSize"
|
|
415
|
+
@update:page="fetchDocuments"
|
|
416
|
+
/>
|
|
417
|
+
</div>
|
|
418
|
+
</div>
|
|
419
|
+
<UTable
|
|
420
|
+
v-model:sorting="sorting"
|
|
421
|
+
:sorting-options="{ manualSorting: true }"
|
|
422
|
+
:data="documents"
|
|
423
|
+
:columns="columns"
|
|
424
|
+
:loading="loading"
|
|
425
|
+
/>
|
|
426
|
+
|
|
427
|
+
<UModal
|
|
428
|
+
v-model:open="detailOpen"
|
|
429
|
+
title="Details"
|
|
430
|
+
>
|
|
431
|
+
<template #body>
|
|
432
|
+
<pre class="text-xs overflow-auto max-h-[60vh] whitespace-pre-wrap break-all">{{ detailContent }}</pre>
|
|
433
|
+
</template>
|
|
434
|
+
</UModal>
|
|
435
|
+
|
|
436
|
+
<UModal
|
|
437
|
+
v-model:open="isEditModalOpen"
|
|
438
|
+
:title="editMode === 'insert' ? 'Insert Document' : 'Edit Document'"
|
|
439
|
+
:ui="{ footer: 'justify-end' }"
|
|
440
|
+
>
|
|
441
|
+
<template #body>
|
|
442
|
+
<FUDataEdit
|
|
443
|
+
v-if="editColumns.length > 0"
|
|
444
|
+
:data="editFormData"
|
|
445
|
+
:schema="editSchema"
|
|
446
|
+
:submit-label="editMode === 'insert' ? 'Insert' : 'Save'"
|
|
447
|
+
:submit-icon="editMode === 'insert' ? 'i-lucide-plus' : 'i-lucide-save'"
|
|
448
|
+
@data-saved="handleSave"
|
|
449
|
+
/>
|
|
450
|
+
<textarea
|
|
451
|
+
v-else
|
|
452
|
+
v-model="editJson"
|
|
453
|
+
class="w-full h-80 font-mono text-xs p-2 border rounded bg-gray-50 dark:bg-gray-900 border-gray-200 dark:border-gray-700 focus:outline-none resize-y"
|
|
454
|
+
spellcheck="false"
|
|
455
|
+
/>
|
|
456
|
+
<p
|
|
457
|
+
v-if="editError"
|
|
458
|
+
class="mt-2 text-sm text-error-500"
|
|
459
|
+
>
|
|
460
|
+
{{ editError }}
|
|
461
|
+
</p>
|
|
462
|
+
</template>
|
|
463
|
+
<template
|
|
464
|
+
v-if="editColumns.length === 0"
|
|
465
|
+
#footer
|
|
466
|
+
>
|
|
467
|
+
<UButton
|
|
468
|
+
label="Cancel"
|
|
469
|
+
color="neutral"
|
|
470
|
+
variant="ghost"
|
|
471
|
+
@click="isEditModalOpen = false"
|
|
472
|
+
/>
|
|
473
|
+
<UButton
|
|
474
|
+
:label="editMode === 'insert' ? 'Insert' : 'Save'"
|
|
475
|
+
:icon="editMode === 'insert' ? 'i-lucide-plus' : 'i-lucide-save'"
|
|
476
|
+
@click="handleSave"
|
|
477
|
+
/>
|
|
478
|
+
</template>
|
|
479
|
+
</UModal>
|
|
480
|
+
|
|
481
|
+
<UModal
|
|
482
|
+
v-model:open="isDeleteModalOpen"
|
|
483
|
+
title="Delete Document"
|
|
484
|
+
:ui="{ footer: 'justify-end' }"
|
|
485
|
+
>
|
|
486
|
+
<template #body>
|
|
487
|
+
<p>Are you sure you want to delete this document? This action cannot be undone.</p>
|
|
488
|
+
<p
|
|
489
|
+
v-if="deleteError"
|
|
490
|
+
class="mt-2 text-sm text-error-500"
|
|
491
|
+
>
|
|
492
|
+
{{ deleteError }}
|
|
493
|
+
</p>
|
|
494
|
+
</template>
|
|
495
|
+
<template #footer>
|
|
496
|
+
<UButton
|
|
497
|
+
label="Cancel"
|
|
498
|
+
color="neutral"
|
|
499
|
+
variant="ghost"
|
|
500
|
+
@click="isDeleteModalOpen = false"
|
|
501
|
+
/>
|
|
502
|
+
<UButton
|
|
503
|
+
label="Delete"
|
|
504
|
+
color="error"
|
|
505
|
+
icon="i-lucide-trash-2"
|
|
506
|
+
@click="handleDelete"
|
|
507
|
+
/>
|
|
508
|
+
</template>
|
|
509
|
+
</UModal>
|
|
510
|
+
</div>
|
|
511
|
+
</template>
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
type __VLS_Props = {
|
|
2
|
+
collectionName: string;
|
|
3
|
+
};
|
|
4
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
5
|
+
declare const _default: typeof __VLS_export;
|
|
6
|
+
export default _default;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
type __VLS_Props = {
|
|
2
|
+
collectionName: string;
|
|
3
|
+
sampleSize?: number;
|
|
4
|
+
};
|
|
5
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
|
|
6
|
+
sampleSize: number;
|
|
7
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
8
|
+
declare const _default: typeof __VLS_export;
|
|
9
|
+
export default _default;
|