@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.
Files changed (67) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +308 -0
  3. package/dist/module.d.mts +8 -0
  4. package/dist/module.json +12 -0
  5. package/dist/module.mjs +35 -0
  6. package/dist/runtime/components/MongocampAccount.d.vue.ts +3 -0
  7. package/dist/runtime/components/MongocampAccount.vue +197 -0
  8. package/dist/runtime/components/MongocampAccount.vue.d.ts +3 -0
  9. package/dist/runtime/components/MongocampCollectionData.d.vue.ts +6 -0
  10. package/dist/runtime/components/MongocampCollectionData.vue +511 -0
  11. package/dist/runtime/components/MongocampCollectionData.vue.d.ts +6 -0
  12. package/dist/runtime/components/MongocampCollectionInfos.d.vue.ts +9 -0
  13. package/dist/runtime/components/MongocampCollectionInfos.vue +393 -0
  14. package/dist/runtime/components/MongocampCollectionInfos.vue.d.ts +9 -0
  15. package/dist/runtime/components/MongocampCollections.d.vue.ts +7 -0
  16. package/dist/runtime/components/MongocampCollections.vue +231 -0
  17. package/dist/runtime/components/MongocampCollections.vue.d.ts +7 -0
  18. package/dist/runtime/components/MongocampDatabases.d.vue.ts +3 -0
  19. package/dist/runtime/components/MongocampDatabases.vue +112 -0
  20. package/dist/runtime/components/MongocampDatabases.vue.d.ts +3 -0
  21. package/dist/runtime/components/MongocampJobs.d.vue.ts +3 -0
  22. package/dist/runtime/components/MongocampJobs.vue +354 -0
  23. package/dist/runtime/components/MongocampJobs.vue.d.ts +3 -0
  24. package/dist/runtime/components/MongocampLogin.d.vue.ts +3 -0
  25. package/dist/runtime/components/MongocampLogin.vue +63 -0
  26. package/dist/runtime/components/MongocampLogin.vue.d.ts +3 -0
  27. package/dist/runtime/components/MongocampRoleGrants.d.vue.ts +6 -0
  28. package/dist/runtime/components/MongocampRoleGrants.vue +276 -0
  29. package/dist/runtime/components/MongocampRoleGrants.vue.d.ts +6 -0
  30. package/dist/runtime/components/MongocampRoles.d.vue.ts +6 -0
  31. package/dist/runtime/components/MongocampRoles.vue +272 -0
  32. package/dist/runtime/components/MongocampRoles.vue.d.ts +6 -0
  33. package/dist/runtime/components/MongocampUsers.d.vue.ts +3 -0
  34. package/dist/runtime/components/MongocampUsers.vue +320 -0
  35. package/dist/runtime/components/MongocampUsers.vue.d.ts +3 -0
  36. package/dist/runtime/components/MongocampVersion.d.vue.ts +3 -0
  37. package/dist/runtime/components/MongocampVersion.vue +25 -0
  38. package/dist/runtime/components/MongocampVersion.vue.d.ts +3 -0
  39. package/dist/runtime/composables/useMongocampAccount.d.ts +7 -0
  40. package/dist/runtime/composables/useMongocampAccount.js +28 -0
  41. package/dist/runtime/composables/useMongocampAdmin.d.ts +14 -0
  42. package/dist/runtime/composables/useMongocampAdmin.js +48 -0
  43. package/dist/runtime/composables/useMongocampBucket.d.ts +15 -0
  44. package/dist/runtime/composables/useMongocampBucket.js +109 -0
  45. package/dist/runtime/composables/useMongocampCollection.d.ts +17 -0
  46. package/dist/runtime/composables/useMongocampCollection.js +12 -0
  47. package/dist/runtime/composables/useMongocampDocument.d.ts +13 -0
  48. package/dist/runtime/composables/useMongocampDocument.js +18 -0
  49. package/dist/runtime/composables/useMongocampDynamicForm.d.ts +14 -0
  50. package/dist/runtime/composables/useMongocampDynamicForm.js +231 -0
  51. package/dist/runtime/composables/useMongocampIndex.d.ts +9 -0
  52. package/dist/runtime/composables/useMongocampIndex.js +23 -0
  53. package/dist/runtime/composables/useMongocampJobs.d.ts +9 -0
  54. package/dist/runtime/composables/useMongocampJobs.js +25 -0
  55. package/dist/runtime/composables/useMongocampQuery.d.ts +99 -0
  56. package/dist/runtime/composables/useMongocampQuery.js +108 -0
  57. package/dist/runtime/composables/useMongocampQueryBuilder.d.ts +30 -0
  58. package/dist/runtime/composables/useMongocampQueryBuilder.js +112 -0
  59. package/dist/runtime/composables/useMongocampSchema.d.ts +14 -0
  60. package/dist/runtime/composables/useMongocampSchema.js +142 -0
  61. package/dist/runtime/composables/useMongocampSystem.d.ts +12 -0
  62. package/dist/runtime/composables/useMongocampSystem.js +21 -0
  63. package/dist/runtime/plugin.d.ts +6 -0
  64. package/dist/runtime/plugin.js +23 -0
  65. package/dist/runtime/server/tsconfig.json +3 -0
  66. package/dist/types.d.mts +3 -0
  67. package/package.json +99 -0
@@ -0,0 +1,393 @@
1
+ <script setup>
2
+ import { computed, h, reactive, ref, resolveComponent, watch } from "vue";
3
+ import { useMongocampApi, useMongocampIndex, useMongocampSchema, useToast } from "#imports";
4
+ const props = defineProps({
5
+ collectionName: { type: String, required: true },
6
+ sampleSize: { type: Number, required: false, default: 500 }
7
+ });
8
+ const { collectionApi, documentApi } = useMongocampApi();
9
+ const { schemaFromSamples, schemaToTsInterface } = useMongocampSchema();
10
+ const { listIndexes, createIndex, createUniqueIndex, createTextIndex, createExpiringIndex, deleteIndex } = useMongocampIndex();
11
+ const UBadge = resolveComponent("UBadge");
12
+ const UIcon = resolveComponent("UIcon");
13
+ const UButton = resolveComponent("UButton");
14
+ const UTooltip = resolveComponent("UTooltip");
15
+ function withTooltip(text, node) {
16
+ return h(UTooltip, { text }, { default: () => node });
17
+ }
18
+ const info = ref(null);
19
+ const columnDefinitions = ref([]);
20
+ const indexes = ref([]);
21
+ const loading = ref(false);
22
+ async function fetchIndexes() {
23
+ if (!props.collectionName) return;
24
+ indexes.value = await listIndexes(props.collectionName);
25
+ }
26
+ async function fetchInfo() {
27
+ if (!props.collectionName) return;
28
+ loading.value = true;
29
+ try {
30
+ info.value = await collectionApi.getCollectionInformation({ collectionName: props.collectionName });
31
+ const samples = await documentApi.listDocuments({
32
+ collectionName: props.collectionName,
33
+ rowsPerPage: props.sampleSize,
34
+ page: 1
35
+ });
36
+ columnDefinitions.value = schemaFromSamples(samples);
37
+ await fetchIndexes();
38
+ } finally {
39
+ loading.value = false;
40
+ }
41
+ }
42
+ watch(() => [props.collectionName, props.sampleSize], fetchInfo, { immediate: true });
43
+ const TYPE_COLORS = {
44
+ "string": "neutral",
45
+ "number": "warning",
46
+ "integer": "warning",
47
+ "date-time": "info",
48
+ "boolean": "success",
49
+ "object": "secondary",
50
+ "array": "primary"
51
+ };
52
+ const schemaRows = computed(
53
+ () => columnDefinitions.value.map((col) => ({
54
+ field: col.columnKey,
55
+ type: col.columnType,
56
+ required: col.required
57
+ }))
58
+ );
59
+ const schemaTitle = computed(() => props.collectionName);
60
+ const tsInterface = computed(() => schemaToTsInterface(columnDefinitions.value, props.collectionName));
61
+ const toast = useToast();
62
+ async function copySchemaAsTs() {
63
+ await navigator.clipboard.writeText(tsInterface.value);
64
+ toast.add({ title: "Copied to clipboard", description: "TypeScript interface copied.", color: "success" });
65
+ }
66
+ const schemaTableColumns = [
67
+ {
68
+ accessorKey: "field",
69
+ header: "Field"
70
+ },
71
+ {
72
+ accessorKey: "type",
73
+ header: "Type",
74
+ cell: ({ row }) => h(UBadge, {
75
+ label: row.original.type,
76
+ color: TYPE_COLORS[row.original.type] ?? "neutral",
77
+ variant: "subtle"
78
+ })
79
+ },
80
+ {
81
+ accessorKey: "required",
82
+ header: "In all samples",
83
+ cell: ({ row }) => row.original.required ? h(UIcon, { name: "i-lucide-check", class: "size-4 text-success" }) : h("span", { class: "text-dimmed" }, "\u2014")
84
+ }
85
+ ];
86
+ const indexRows = computed(
87
+ () => indexes.value.map((idx) => ({
88
+ name: idx.name,
89
+ keys: Object.entries(idx.keys ?? {}).map(([field, direction]) => `${field}: ${direction}`).join(", "),
90
+ unique: idx.unique,
91
+ text: idx.text,
92
+ expire: idx.expire,
93
+ sizeKb: Math.round((info.value?.indexSizes?.[idx.name] ?? 0) / 1024)
94
+ }))
95
+ );
96
+ const isCreateIndexModalOpen = ref(false);
97
+ const createIndexError = ref("");
98
+ const newIndex = ref({ fieldName: "", sortAscending: true, indexType: "standard", duration: "3600s" });
99
+ const createIndexSchema = reactive([
100
+ {
101
+ $formkit: "nuxtUIInput",
102
+ name: "fieldName",
103
+ label: "Field Name",
104
+ validation: "required"
105
+ },
106
+ {
107
+ $formkit: "nuxtUISwitch",
108
+ name: "sortAscending",
109
+ label: "Ascending"
110
+ },
111
+ {
112
+ $formkit: "nuxtUISelectMenu",
113
+ id: "indexType",
114
+ name: "indexType",
115
+ label: "Index Type",
116
+ options: ["standard", "unique", "text", "expiring"],
117
+ validation: "required"
118
+ },
119
+ {
120
+ $formkit: "nuxtUIInput",
121
+ name: "duration",
122
+ label: "Expire After (e.g. 3600s)",
123
+ help: "Only used for the expiring index type.",
124
+ if: "$get(indexType).value === 'expiring'"
125
+ }
126
+ ]);
127
+ function extractSelectValue(raw) {
128
+ if (typeof raw === "object" && raw !== null) {
129
+ const obj = raw;
130
+ return obj.value ?? obj.label ?? "";
131
+ }
132
+ return String(raw ?? "");
133
+ }
134
+ function openCreateIndex() {
135
+ createIndexError.value = "";
136
+ newIndex.value = { fieldName: "", sortAscending: true, indexType: "standard", duration: "3600s" };
137
+ isCreateIndexModalOpen.value = true;
138
+ }
139
+ async function handleCreateIndex() {
140
+ createIndexError.value = "";
141
+ const { fieldName, sortAscending } = newIndex.value;
142
+ const indexType = extractSelectValue(newIndex.value.indexType);
143
+ try {
144
+ if (indexType === "unique") {
145
+ await createUniqueIndex(props.collectionName, fieldName, sortAscending);
146
+ } else if (indexType === "text") {
147
+ await createTextIndex(props.collectionName, fieldName);
148
+ } else if (indexType === "expiring") {
149
+ await createExpiringIndex(props.collectionName, fieldName, newIndex.value.duration);
150
+ } else {
151
+ await createIndex(props.collectionName, { [fieldName]: sortAscending ? 1 : -1 });
152
+ }
153
+ isCreateIndexModalOpen.value = false;
154
+ await fetchIndexes();
155
+ } catch (e) {
156
+ createIndexError.value = e instanceof Error ? e.message : "Error creating index";
157
+ }
158
+ }
159
+ const isDeleteIndexModalOpen = ref(false);
160
+ const deleteIndexError = ref("");
161
+ const indexToDelete = ref(void 0);
162
+ function confirmDeleteIndex(indexName) {
163
+ indexToDelete.value = indexName;
164
+ deleteIndexError.value = "";
165
+ isDeleteIndexModalOpen.value = true;
166
+ }
167
+ async function handleDeleteIndex() {
168
+ deleteIndexError.value = "";
169
+ if (!indexToDelete.value) return;
170
+ try {
171
+ await deleteIndex(props.collectionName, indexToDelete.value);
172
+ isDeleteIndexModalOpen.value = false;
173
+ await fetchIndexes();
174
+ } catch (e) {
175
+ deleteIndexError.value = e instanceof Error ? e.message : "Error deleting index";
176
+ }
177
+ }
178
+ const INDEX_FLAG_BADGES = [
179
+ { key: "unique", label: "unique", color: "warning" },
180
+ { key: "text", label: "text", color: "info" },
181
+ { key: "expire", label: "expire", color: "secondary" }
182
+ ];
183
+ const indexTableColumns = [
184
+ {
185
+ accessorKey: "name",
186
+ header: "Index"
187
+ },
188
+ {
189
+ accessorKey: "keys",
190
+ header: "Keys",
191
+ cell: ({ row }) => h("span", { class: "font-mono text-xs" }, row.original.keys)
192
+ },
193
+ {
194
+ id: "flags",
195
+ header: "Type",
196
+ cell: ({ row }) => h("div", { class: "flex gap-1" }, INDEX_FLAG_BADGES.filter((flag) => row.original[flag.key]).map((flag) => h(UBadge, { label: flag.label, color: flag.color, variant: "subtle", size: "sm" })))
197
+ },
198
+ {
199
+ accessorKey: "sizeKb",
200
+ header: "Size (KB)"
201
+ },
202
+ {
203
+ id: "actions",
204
+ header: "",
205
+ cell: ({ row }) => row.original.name === "_id_" ? null : h("div", { class: "flex justify-end" }, [
206
+ withTooltip("Delete index", h(UButton, {
207
+ "icon": "i-lucide-trash-2",
208
+ "color": "error",
209
+ "variant": "ghost",
210
+ "size": "sm",
211
+ "aria-label": "Delete index",
212
+ "onClick": () => confirmDeleteIndex(row.original.name)
213
+ }))
214
+ ])
215
+ }
216
+ ];
217
+ </script>
218
+
219
+ <template>
220
+ <div class="flex flex-col gap-4">
221
+ <USkeleton
222
+ v-if="loading"
223
+ class="h-32 w-full"
224
+ />
225
+ <div
226
+ v-else-if="info"
227
+ class="grid grid-cols-2 gap-3 sm:grid-cols-3"
228
+ >
229
+ <UCard>
230
+ <div class="text-sm text-gray-500 dark:text-gray-400">
231
+ Documents
232
+ </div>
233
+ <div class="text-2xl font-semibold">
234
+ {{ info.count }}
235
+ </div>
236
+ </UCard>
237
+ <UCard>
238
+ <div class="text-sm text-gray-500 dark:text-gray-400">
239
+ Data Size
240
+ </div>
241
+ <div class="text-2xl font-semibold">
242
+ {{ Math.round(info.size / 1024) }} KB
243
+ </div>
244
+ </UCard>
245
+ <UCard>
246
+ <div class="text-sm text-gray-500 dark:text-gray-400">
247
+ Storage Size
248
+ </div>
249
+ <div class="text-2xl font-semibold">
250
+ {{ Math.round(info.storageSize / 1024) }} KB
251
+ </div>
252
+ </UCard>
253
+ <UCard>
254
+ <div class="text-sm text-gray-500 dark:text-gray-400">
255
+ Avg Doc Size
256
+ </div>
257
+ <div class="text-2xl font-semibold">
258
+ {{ Math.round(info.avgObjSize) }} B
259
+ </div>
260
+ </UCard>
261
+ <UCard>
262
+ <div class="text-sm text-gray-500 dark:text-gray-400">
263
+ Indexes
264
+ </div>
265
+ <div class="text-2xl font-semibold">
266
+ {{ info.nindexes }}
267
+ </div>
268
+ </UCard>
269
+ <UCard>
270
+ <div class="text-sm text-gray-500 dark:text-gray-400">
271
+ Index Size
272
+ </div>
273
+ <div class="text-2xl font-semibold">
274
+ {{ Math.round(info.totalIndexSize / 1024) }} KB
275
+ </div>
276
+ </UCard>
277
+ </div>
278
+ <UCard v-if="indexRows.length">
279
+ <template #header>
280
+ <div class="flex items-center justify-between gap-2">
281
+ <div class="flex items-center gap-2">
282
+ <UIcon
283
+ name="i-lucide-list-tree"
284
+ class="size-4 text-(--ui-primary)"
285
+ />
286
+ <span class="font-semibold">Indexes</span>
287
+ <UBadge
288
+ :label="`${indexRows.length} indexes`"
289
+ variant="subtle"
290
+ color="neutral"
291
+ size="sm"
292
+ />
293
+ </div>
294
+ <UButton
295
+ icon="i-lucide-plus"
296
+ label="Create Index"
297
+ color="neutral"
298
+ variant="ghost"
299
+ size="sm"
300
+ @click="openCreateIndex"
301
+ />
302
+ </div>
303
+ </template>
304
+ <UTable
305
+ :data="indexRows"
306
+ :columns="indexTableColumns"
307
+ />
308
+ </UCard>
309
+
310
+ <UCard v-if="schemaRows.length">
311
+ <template #header>
312
+ <div class="flex items-center justify-between gap-2">
313
+ <div class="flex items-center gap-2">
314
+ <UIcon
315
+ name="i-lucide-braces"
316
+ class="size-4 text-(--ui-primary)"
317
+ />
318
+ <span class="font-semibold">{{ schemaTitle }}</span>
319
+ <UBadge
320
+ :label="`${schemaRows.length} fields`"
321
+ variant="subtle"
322
+ color="neutral"
323
+ size="sm"
324
+ />
325
+ </div>
326
+ <UButton
327
+ icon="i-lucide-copy"
328
+ label="Copy as TS Interface"
329
+ color="neutral"
330
+ variant="ghost"
331
+ size="sm"
332
+ @click="copySchemaAsTs"
333
+ />
334
+ </div>
335
+ </template>
336
+ <UTable
337
+ :data="schemaRows"
338
+ :columns="schemaTableColumns"
339
+ />
340
+ </UCard>
341
+
342
+ <UModal
343
+ v-model:open="isCreateIndexModalOpen"
344
+ title="Create Index"
345
+ >
346
+ <template #body>
347
+ <FUDataEdit
348
+ :data="newIndex"
349
+ :schema="createIndexSchema"
350
+ submit-label="Create Index"
351
+ submit-icon="i-lucide-plus"
352
+ @data-saved="handleCreateIndex"
353
+ />
354
+ <p
355
+ v-if="createIndexError"
356
+ class="mt-2 text-sm text-error-500"
357
+ >
358
+ {{ createIndexError }}
359
+ </p>
360
+ </template>
361
+ </UModal>
362
+
363
+ <UModal
364
+ v-model:open="isDeleteIndexModalOpen"
365
+ title="Delete Index"
366
+ :ui="{ footer: 'justify-end' }"
367
+ >
368
+ <template #body>
369
+ <p>Are you sure you want to delete the index "{{ indexToDelete }}"? This action cannot be undone.</p>
370
+ <p
371
+ v-if="deleteIndexError"
372
+ class="mt-2 text-sm text-error-500"
373
+ >
374
+ {{ deleteIndexError }}
375
+ </p>
376
+ </template>
377
+ <template #footer>
378
+ <UButton
379
+ label="Cancel"
380
+ color="neutral"
381
+ variant="ghost"
382
+ @click="isDeleteIndexModalOpen = false"
383
+ />
384
+ <UButton
385
+ label="Delete"
386
+ color="error"
387
+ icon="i-lucide-trash-2"
388
+ @click="handleDeleteIndex"
389
+ />
390
+ </template>
391
+ </UModal>
392
+ </div>
393
+ </template>
@@ -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;
@@ -0,0 +1,7 @@
1
+ type __VLS_Props = {
2
+ infoPath?: string;
3
+ dataPath?: string;
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<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
6
+ declare const _default: typeof __VLS_export;
7
+ export default _default;
@@ -0,0 +1,231 @@
1
+ <script setup>
2
+ import { h, ref, resolveComponent } from "vue";
3
+ import { useMongocampApi, useMongocampBucket } from "#imports";
4
+ const props = defineProps({
5
+ infoPath: { type: String, required: false },
6
+ dataPath: { type: String, required: false }
7
+ });
8
+ const { collectionApi } = useMongocampApi();
9
+ const { isBucketCollection, bucketNameFor, bucketActionInFlight, clearBucket, deleteBucket } = useMongocampBucket();
10
+ const collections = ref([]);
11
+ const loading = ref(false);
12
+ async function fetchCollections() {
13
+ loading.value = true;
14
+ try {
15
+ const names = await collectionApi.listCollections();
16
+ const rows = await Promise.all(
17
+ names.filter((name) => !name.startsWith("mc")).map(async (name) => {
18
+ const info = await collectionApi.getCollectionInformation({ collectionName: name });
19
+ return {
20
+ name,
21
+ count: info.count,
22
+ sizekb: Math.round(info.storageSize / 1024),
23
+ indexCount: info.nindexes
24
+ };
25
+ })
26
+ );
27
+ collections.value = rows;
28
+ } finally {
29
+ loading.value = false;
30
+ }
31
+ }
32
+ const UButton = resolveComponent("UButton");
33
+ const UBadge = resolveComponent("UBadge");
34
+ const UTooltip = resolveComponent("UTooltip");
35
+ function withTooltip(text, node) {
36
+ return h(UTooltip, { text }, { default: () => node });
37
+ }
38
+ const globalFilter = ref("");
39
+ const sorting = ref([]);
40
+ const isClearBucketModalOpen = ref(false);
41
+ const bucketToClear = ref(void 0);
42
+ function confirmClearBucket(bucketName) {
43
+ bucketToClear.value = bucketName;
44
+ isClearBucketModalOpen.value = true;
45
+ }
46
+ async function handleClearBucket() {
47
+ if (!bucketToClear.value) return;
48
+ const success = await clearBucket(bucketToClear.value);
49
+ isClearBucketModalOpen.value = false;
50
+ if (success) await fetchCollections();
51
+ }
52
+ const isDeleteBucketModalOpen = ref(false);
53
+ const bucketToDelete = ref(void 0);
54
+ function confirmDeleteBucket(bucketName) {
55
+ bucketToDelete.value = bucketName;
56
+ isDeleteBucketModalOpen.value = true;
57
+ }
58
+ async function handleDeleteBucket() {
59
+ if (!bucketToDelete.value) return;
60
+ const success = await deleteBucket(bucketToDelete.value);
61
+ isDeleteBucketModalOpen.value = false;
62
+ if (success) await fetchCollections();
63
+ }
64
+ function sortHeader(column, label) {
65
+ const isSorted = column.getIsSorted();
66
+ return h(UButton, {
67
+ color: "neutral",
68
+ variant: "ghost",
69
+ label,
70
+ icon: isSorted === "asc" ? "i-lucide-arrow-up-narrow-wide" : isSorted === "desc" ? "i-lucide-arrow-down-wide-narrow" : "i-lucide-arrow-up-down",
71
+ class: "-mx-2.5",
72
+ onClick: () => column.toggleSorting(isSorted === "asc")
73
+ });
74
+ }
75
+ const columns = [
76
+ {
77
+ accessorKey: "name",
78
+ header: ({ column }) => sortHeader(column, "Collection"),
79
+ cell: ({ row }) => h("div", { class: "flex items-center gap-2" }, [
80
+ h("span", row.original.name),
81
+ isBucketCollection(row.original.name) ? h(UBadge, { label: "bucket", color: "neutral", variant: "subtle", size: "sm" }) : null
82
+ ])
83
+ },
84
+ {
85
+ accessorKey: "count",
86
+ header: ({ column }) => sortHeader(column, "Documents")
87
+ },
88
+ {
89
+ accessorKey: "sizekb",
90
+ header: ({ column }) => sortHeader(column, "Size (KB)")
91
+ },
92
+ {
93
+ accessorKey: "indexCount",
94
+ header: ({ column }) => sortHeader(column, "Indexes")
95
+ },
96
+ {
97
+ id: "actions",
98
+ header: "",
99
+ cell: ({ row }) => {
100
+ const buttons = [
101
+ withTooltip("Collection info", h(UButton, {
102
+ "icon": "i-lucide-info",
103
+ "color": "neutral",
104
+ "variant": "ghost",
105
+ "size": "sm",
106
+ "aria-label": "Collection info",
107
+ "to": `${props.infoPath ?? "/secured/admin/collections"}/${row.original.name}`
108
+ })),
109
+ withTooltip("Collection data", h(UButton, {
110
+ "icon": "i-lucide-table",
111
+ "color": "neutral",
112
+ "variant": "ghost",
113
+ "size": "sm",
114
+ "aria-label": "Collection data",
115
+ "to": `${props.dataPath ?? "/secured/admin/collections"}/${row.original.name}/data`
116
+ }))
117
+ ];
118
+ if (isBucketCollection(row.original.name)) {
119
+ const bucketName = bucketNameFor(row.original.name);
120
+ buttons.push(
121
+ withTooltip("Clear bucket", h(UButton, {
122
+ "icon": "i-lucide-eraser",
123
+ "color": "warning",
124
+ "variant": "ghost",
125
+ "size": "sm",
126
+ "aria-label": "Clear bucket",
127
+ "loading": bucketActionInFlight.value.has(bucketName),
128
+ "onClick": () => confirmClearBucket(bucketName)
129
+ })),
130
+ withTooltip("Delete bucket", h(UButton, {
131
+ "icon": "i-lucide-trash-2",
132
+ "color": "error",
133
+ "variant": "ghost",
134
+ "size": "sm",
135
+ "aria-label": "Delete bucket",
136
+ "loading": bucketActionInFlight.value.has(bucketName),
137
+ "onClick": () => confirmDeleteBucket(bucketName)
138
+ }))
139
+ );
140
+ }
141
+ return h("div", { class: "flex gap-1 justify-end" }, buttons);
142
+ }
143
+ }
144
+ ];
145
+ fetchCollections();
146
+ </script>
147
+
148
+ <template>
149
+ <div class="flex flex-col gap-4">
150
+ <div class="flex items-center justify-between gap-4">
151
+ <h2 class="text-xl font-semibold">
152
+ Collections
153
+ </h2>
154
+ <div class="flex flex-1 items-center gap-2">
155
+ <UInput
156
+ v-model="globalFilter"
157
+ icon="i-lucide-search"
158
+ placeholder="Filter collections..."
159
+ size="sm"
160
+ class="flex-1 max-w-xs"
161
+ />
162
+ <UTooltip text="Refresh">
163
+ <UButton
164
+ icon="i-lucide-refresh-cw"
165
+ color="neutral"
166
+ variant="ghost"
167
+ :loading="loading"
168
+ aria-label="Refresh"
169
+ @click="fetchCollections"
170
+ />
171
+ </UTooltip>
172
+ </div>
173
+ </div>
174
+
175
+ <UTable
176
+ v-model:global-filter="globalFilter"
177
+ v-model:sorting="sorting"
178
+ :data="collections"
179
+ :columns="columns"
180
+ :loading="loading"
181
+ />
182
+
183
+ <UModal
184
+ v-model:open="isClearBucketModalOpen"
185
+ title="Clear Bucket"
186
+ :ui="{ footer: 'justify-end' }"
187
+ >
188
+ <template #body>
189
+ <p>Are you sure you want to delete every file in the "{{ bucketToClear }}" bucket? The bucket itself is kept, but all files in it are permanently removed. This action cannot be undone.</p>
190
+ </template>
191
+ <template #footer>
192
+ <UButton
193
+ label="Cancel"
194
+ color="neutral"
195
+ variant="ghost"
196
+ @click="isClearBucketModalOpen = false"
197
+ />
198
+ <UButton
199
+ label="Clear Bucket"
200
+ color="warning"
201
+ icon="i-lucide-eraser"
202
+ @click="handleClearBucket"
203
+ />
204
+ </template>
205
+ </UModal>
206
+
207
+ <UModal
208
+ v-model:open="isDeleteBucketModalOpen"
209
+ title="Delete Bucket"
210
+ :ui="{ footer: 'justify-end' }"
211
+ >
212
+ <template #body>
213
+ <p>Are you sure you want to delete the "{{ bucketToDelete }}" bucket? This permanently removes both the "{{ bucketToDelete }}.files" and "{{ bucketToDelete }}.chunks" collections. This action cannot be undone.</p>
214
+ </template>
215
+ <template #footer>
216
+ <UButton
217
+ label="Cancel"
218
+ color="neutral"
219
+ variant="ghost"
220
+ @click="isDeleteBucketModalOpen = false"
221
+ />
222
+ <UButton
223
+ label="Delete Bucket"
224
+ color="error"
225
+ icon="i-lucide-trash-2"
226
+ @click="handleDeleteBucket"
227
+ />
228
+ </template>
229
+ </UModal>
230
+ </div>
231
+ </template>
@@ -0,0 +1,7 @@
1
+ type __VLS_Props = {
2
+ infoPath?: string;
3
+ dataPath?: string;
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<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
6
+ declare const _default: typeof __VLS_export;
7
+ export default _default;
@@ -0,0 +1,3 @@
1
+ declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
2
+ declare const _default: typeof __VLS_export;
3
+ export default _default;