@antelopejs/dms-database 0.0.14 → 0.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 (173) hide show
  1. package/README.md +30 -5
  2. package/dist/db/models/query_history.model.d.ts +1 -0
  3. package/dist/db/models/query_history.model.js +6 -0
  4. package/dist/db/models/query_history.model.js.map +1 -1
  5. package/dist/db/tables/query_history.table.d.ts +3 -0
  6. package/dist/db/tables/query_history.table.js +12 -0
  7. package/dist/db/tables/query_history.table.js.map +1 -1
  8. package/dist/db/tables/saved_queries.table.d.ts +1 -0
  9. package/dist/db/tables/saved_queries.table.js +4 -0
  10. package/dist/db/tables/saved_queries.table.js.map +1 -1
  11. package/dist/i18n/messages.d.ts +39 -0
  12. package/dist/i18n/messages.js +64 -0
  13. package/dist/i18n/messages.js.map +1 -0
  14. package/dist/index.d.ts +1 -0
  15. package/dist/index.js +1 -7
  16. package/dist/index.js.map +1 -1
  17. package/dist/module.d.ts +11 -0
  18. package/dist/module.js +55 -0
  19. package/dist/module.js.map +1 -0
  20. package/dist/pages/data.d.ts +1 -1
  21. package/dist/pages/data.js +9 -6
  22. package/dist/pages/data.js.map +1 -1
  23. package/dist/pages/diagram.d.ts +9 -0
  24. package/dist/pages/diagram.js +45 -0
  25. package/dist/pages/diagram.js.map +1 -0
  26. package/dist/pages/index.d.ts +1 -0
  27. package/dist/pages/index.js +1 -0
  28. package/dist/pages/index.js.map +1 -1
  29. package/dist/pages/overview.d.ts +4 -1
  30. package/dist/pages/overview.js +113 -6
  31. package/dist/pages/overview.js.map +1 -1
  32. package/dist/pages/query.d.ts +1 -1
  33. package/dist/pages/query.js +22 -6
  34. package/dist/pages/query.js.map +1 -1
  35. package/dist/pages/schemas.d.ts +2 -1
  36. package/dist/pages/schemas.js +143 -6
  37. package/dist/pages/schemas.js.map +1 -1
  38. package/dist/routes/data.d.ts +8 -0
  39. package/dist/routes/data.js +84 -21
  40. package/dist/routes/data.js.map +1 -1
  41. package/dist/routes/index.d.ts +1 -0
  42. package/dist/routes/index.js +1 -0
  43. package/dist/routes/index.js.map +1 -1
  44. package/dist/routes/overview.d.ts +12 -0
  45. package/dist/routes/overview.js +55 -0
  46. package/dist/routes/overview.js.map +1 -0
  47. package/dist/routes/query.d.ts +5 -1
  48. package/dist/routes/query.js +162 -27
  49. package/dist/routes/query.js.map +1 -1
  50. package/dist/routes/tables.d.ts +15 -12
  51. package/dist/routes/tables.js +95 -55
  52. package/dist/routes/tables.js.map +1 -1
  53. package/dist/service/catalog.d.ts +3 -0
  54. package/dist/service/catalog.js +26 -0
  55. package/dist/service/catalog.js.map +1 -0
  56. package/dist/service/health.d.ts +3 -3
  57. package/dist/service/health.js +17 -14
  58. package/dist/service/health.js.map +1 -1
  59. package/dist/service/introspect.d.ts +3 -1
  60. package/dist/service/introspect.js +131 -43
  61. package/dist/service/introspect.js.map +1 -1
  62. package/dist/service/overview.d.ts +9 -0
  63. package/dist/service/overview.js +113 -0
  64. package/dist/service/overview.js.map +1 -0
  65. package/dist/service/queryInspection.d.ts +15 -0
  66. package/dist/service/queryInspection.js +107 -0
  67. package/dist/service/queryInspection.js.map +1 -0
  68. package/dist/service/references.d.ts +9 -0
  69. package/dist/service/references.js +52 -0
  70. package/dist/service/references.js.map +1 -0
  71. package/dist/service/schemaFilters.d.ts +26 -0
  72. package/dist/service/schemaFilters.js +42 -0
  73. package/dist/service/schemaFilters.js.map +1 -0
  74. package/dist/service/stagedSerialization.d.ts +2 -0
  75. package/dist/service/stagedSerialization.js +38 -10
  76. package/dist/service/stagedSerialization.js.map +1 -1
  77. package/dist/service/types.d.ts +11 -1
  78. package/dist/types/constants.d.ts +2 -0
  79. package/dist/types/constants.js +3 -1
  80. package/dist/types/constants.js.map +1 -1
  81. package/dist/utils/query.d.ts +0 -2
  82. package/dist/utils/query.js +0 -20
  83. package/dist/utils/query.js.map +1 -1
  84. package/frontend-vue/app/{composables → build/composables}/useDataBrowserGrid.ts +65 -50
  85. package/frontend-vue/app/build/composables/useDataBrowserTabs.ts +366 -0
  86. package/frontend-vue/app/build/composables/useDatabaseHealth.ts +50 -0
  87. package/frontend-vue/app/build/composables/useDatabaseSchemas.ts +140 -0
  88. package/frontend-vue/app/{composables → build/composables}/useDiagramPersistence.ts +47 -39
  89. package/frontend-vue/app/build/composables/useQueryRuntime.ts +217 -0
  90. package/frontend-vue/app/build/composables/useQueryStore.ts +162 -0
  91. package/frontend-vue/app/build/data/cellValues.ts +132 -0
  92. package/frontend-vue/app/build/data/instanceOptions.ts +36 -0
  93. package/frontend-vue/app/build/data/pickerMenus.ts +175 -0
  94. package/frontend-vue/app/build/data/recentTables.ts +48 -0
  95. package/frontend-vue/app/build/data/stagedEdits.ts +117 -0
  96. package/frontend-vue/app/build/data/stagedWrites.ts +64 -0
  97. package/frontend-vue/app/build/data/useStagedSave.ts +298 -0
  98. package/frontend-vue/app/build/diagram/autosave.ts +72 -0
  99. package/frontend-vue/app/build/diagram/graph.ts +585 -0
  100. package/frontend-vue/app/build/diagram/history.ts +178 -0
  101. package/frontend-vue/app/build/diagram/shortcuts.ts +62 -0
  102. package/frontend-vue/app/build/diagram/sync.ts +137 -0
  103. package/frontend-vue/app/build/query/completion.ts +100 -0
  104. package/frontend-vue/app/build/query/csv.ts +32 -0
  105. package/frontend-vue/app/build/query/examples.ts +38 -0
  106. package/frontend-vue/app/build/query/runs.ts +168 -0
  107. package/frontend-vue/app/build/query/tableColumns.ts +35 -0
  108. package/frontend-vue/app/build/query/target.ts +143 -0
  109. package/frontend-vue/app/build/schemas/filterState.ts +128 -0
  110. package/frontend-vue/app/build/schemas/useSchemasListing.ts +62 -0
  111. package/frontend-vue/app/build/utils/databaseLinks.ts +98 -0
  112. package/frontend-vue/app/build/utils/fieldTypes.ts +198 -0
  113. package/frontend-vue/app/build/utils/relativeTime.ts +19 -0
  114. package/frontend-vue/app/{utils → build/utils}/stagedSerialization.ts +7 -1
  115. package/frontend-vue/app/components/ConnectionStatus.vue +122 -0
  116. package/frontend-vue/app/components/DataBrowser.vue +126 -0
  117. package/frontend-vue/app/components/DataBrowserSidebar.vue +293 -210
  118. package/frontend-vue/app/components/DataBrowserTabBar.vue +185 -93
  119. package/frontend-vue/app/components/DataFilterBar.vue +163 -0
  120. package/frontend-vue/app/components/DataGrid.vue +737 -499
  121. package/frontend-vue/app/components/DataGridCell.vue +125 -77
  122. package/frontend-vue/app/components/DataGridHeaderCell.vue +119 -81
  123. package/frontend-vue/app/components/DiagramNoteNode.vue +81 -63
  124. package/frontend-vue/app/components/DiagramRelationEdge.vue +57 -27
  125. package/frontend-vue/app/components/DiagramStubNode.vue +16 -17
  126. package/frontend-vue/app/components/DiagramTableNode.vue +106 -68
  127. package/frontend-vue/app/components/InstancePicker.vue +195 -0
  128. package/frontend-vue/app/components/JsonCellPanel.vue +30 -29
  129. package/frontend-vue/app/components/QueryBuilderPanel.vue +290 -238
  130. package/frontend-vue/app/components/QueryConsole.vue +726 -0
  131. package/frontend-vue/app/components/QueryLibrary.vue +314 -0
  132. package/frontend-vue/app/components/QueryResultPanel.vue +556 -126
  133. package/frontend-vue/app/components/RowDrawer.vue +520 -0
  134. package/frontend-vue/app/components/SchemaDiagram.vue +1016 -1051
  135. package/frontend-vue/app/components/SchemaPicker.vue +86 -0
  136. package/frontend-vue/app/components/SchemasFilterBar.vue +201 -0
  137. package/frontend-vue/app/components/TableInspector.vue +741 -0
  138. package/frontend-vue/app/config/shortcuts-registry.ts +65 -0
  139. package/frontend-vue/dms.frontend.ts +5 -14
  140. package/frontend-vue/i18n/locales/database-en-GB.json +526 -193
  141. package/frontend-vue/i18n/locales/database-fr-FR.json +528 -195
  142. package/frontend-vue/package.json +2 -1
  143. package/frontend-vue/pnpm-lock.yaml +2601 -858
  144. package/frontend-vue/pnpm-workspace.yaml +1 -1
  145. package/frontend-vue/tests/dataBrowser.test.ts +206 -0
  146. package/frontend-vue/tests/diagram.test.ts +516 -0
  147. package/frontend-vue/tests/helpers.test.ts +205 -0
  148. package/frontend-vue/tests/instanceOptions.test.ts +53 -0
  149. package/frontend-vue/tests/locales.test.ts +56 -0
  150. package/frontend-vue/tests/persistence.test.ts +106 -5
  151. package/frontend-vue/tests/pickerMenus.test.ts +254 -0
  152. package/frontend-vue/tests/queryConsole.test.ts +282 -0
  153. package/frontend-vue/tests/queryRuntime.test.ts +2 -2
  154. package/frontend-vue/tests/registration.test.ts +36 -25
  155. package/frontend-vue/tests/schemasFilters.test.ts +115 -0
  156. package/package.json +7 -7
  157. package/frontend-vue/app/components/QueryFavoritesPanel.vue +0 -127
  158. package/frontend-vue/app/components/QueryHistoryPanel.vue +0 -134
  159. package/frontend-vue/app/components/SchemaTableInspector.vue +0 -278
  160. package/frontend-vue/app/composables/useDataBrowserTabs.ts +0 -255
  161. package/frontend-vue/app/composables/useDatabaseHealth.ts +0 -50
  162. package/frontend-vue/app/composables/useDatabaseSchemas.ts +0 -92
  163. package/frontend-vue/app/composables/useQueryRuntime.ts +0 -118
  164. package/frontend-vue/app/composables/useQueryStore.ts +0 -117
  165. package/frontend-vue/app/composables/useTableViewRowClick.ts +0 -22
  166. package/frontend-vue/app/custom-pages/database/data.vue +0 -41
  167. package/frontend-vue/app/custom-pages/database/overview.vue +0 -367
  168. package/frontend-vue/app/custom-pages/database/query.vue +0 -436
  169. package/frontend-vue/app/custom-pages/database/schemas.vue +0 -218
  170. package/frontend-vue/app/utils/databaseFieldModifiers.ts +0 -33
  171. package/frontend-vue/app/utils/databaseFieldTypeIcons.ts +0 -224
  172. package/frontend-vue/app/utils/dmsTableView.ts +0 -83
  173. package/frontend-vue/app/utils/relativeTime.ts +0 -18
@@ -1,436 +0,0 @@
1
- <script setup lang="ts">
2
- import { prepareQueryPayload } from "../../composables/useQueryRuntime";
3
- import type {
4
- ExecuteResult,
5
- HistoryEntry,
6
- QueryLanguage,
7
- QueryScope,
8
- SavedQuery,
9
- } from "../../composables/useQueryStore";
10
-
11
- const HISTORY_PAGE_SIZE = 5;
12
- const MUTATION_SKIP_KEY = "dms-db-query.skipMutationWarning";
13
-
14
- const route = useDmsRoute();
15
- const router = useDmsRouter();
16
-
17
- const {
18
- savedQueries,
19
- historyItems,
20
- historyTotal,
21
- fetchSaved,
22
- fetchHistory,
23
- executeQuery,
24
- saveQuery,
25
- updateSaved,
26
- deleteSaved,
27
- } = useQueryStore();
28
-
29
- const { schemas: dbSchemas, refresh: refreshSchemas } = useDatabaseSchemas();
30
-
31
- const DATA_PATH = "/modules/database/data";
32
-
33
- const queryText = ref<string>("");
34
- const language = ref<QueryLanguage>("aql");
35
- const lastResult = ref<ExecuteResult | null>(null);
36
- const resultMode = ref<"table" | "json" | "chart">("table");
37
- const executing = ref(false);
38
-
39
- const savedScope = ref<QueryScope>("me");
40
- const historyPage = ref(0);
41
-
42
- const saveModalOpen = ref(false);
43
- const saveName = ref("");
44
- const saveDescription = ref("");
45
- const saveShared = ref(false);
46
- const saving = ref(false);
47
- // Non-null while the save modal edits an existing saved query instead of
48
- // creating a new one. The modal works on its own snapshot of the source and
49
- // language, so it never clobbers (or depends on) the main editor.
50
- const editingSaved = ref<SavedQuery | null>(null);
51
- const saveSource = ref("");
52
- const saveLanguage = ref<QueryLanguage>("aql");
53
- const saveError = ref<string | null>(null);
54
-
55
- const mutationModalOpen = ref(false);
56
- const mutationDontRemind = ref(false);
57
- let pendingPayload: { query: Record<string, unknown>; source: string } | null =
58
- null;
59
-
60
- async function refreshSaved() {
61
- try {
62
- await fetchSaved(savedScope.value);
63
- } catch {
64
- savedQueries.value = [];
65
- }
66
- }
67
-
68
- async function refreshHistory() {
69
- try {
70
- await fetchHistory(
71
- HISTORY_PAGE_SIZE,
72
- historyPage.value * HISTORY_PAGE_SIZE,
73
- );
74
- } catch {
75
- historyItems.value = [];
76
- historyTotal.value = 0;
77
- }
78
- }
79
-
80
- watch(savedScope, refreshSaved);
81
- watch(historyPage, refreshHistory);
82
-
83
- function loadFromSaved(saved: SavedQuery) {
84
- queryText.value = saved.source;
85
- language.value = saved.language;
86
- }
87
-
88
- function loadFromHistory(entry: HistoryEntry) {
89
- queryText.value = entry.source;
90
- language.value = entry.language;
91
- }
92
-
93
- function saveFromHistory(entry: HistoryEntry) {
94
- loadFromHistory(entry);
95
- openSaveModal();
96
- }
97
-
98
- function setError(message: string) {
99
- lastResult.value = {
100
- rows: [],
101
- executedAt: new Date().toISOString(),
102
- durationMs: 0,
103
- error: message,
104
- };
105
- }
106
-
107
- function extractFetchError(err: unknown): string {
108
- const { data, message } = (err ?? {}) as { data?: unknown; message?: unknown };
109
- if (typeof data === "string" && data.length > 0) return data;
110
- if (data && typeof data === "object") {
111
- const inner = (data as { message?: unknown; error?: unknown }).message
112
- ?? (data as { message?: unknown; error?: unknown }).error;
113
- if (typeof inner === "string" && inner.length > 0) return inner;
114
- }
115
- if (typeof message === "string" && message.length > 0) return message;
116
- return "Query execution failed";
117
- }
118
-
119
- async function runPrepared(query: Record<string, unknown>, source: string) {
120
- executing.value = true;
121
- try {
122
- const result = await executeQuery(query, source, language.value);
123
- lastResult.value = result;
124
- await refreshHistory();
125
- } catch (err) {
126
- setError(extractFetchError(err));
127
- } finally {
128
- executing.value = false;
129
- }
130
- }
131
-
132
- async function onExecute() {
133
- if (!queryText.value.trim() || executing.value) return;
134
-
135
- let prepared: ReturnType<typeof prepareQueryPayload>;
136
- try {
137
- prepared = prepareQueryPayload(queryText.value, dbSchemas.value);
138
- } catch (err) {
139
- setError((err as Error).message);
140
- return;
141
- }
142
-
143
- const skip =
144
- typeof window !== "undefined" &&
145
- window.localStorage?.getItem(MUTATION_SKIP_KEY) === "true";
146
- if (prepared.hasMutations && !skip) {
147
- pendingPayload = { query: prepared.query, source: prepared.source };
148
- mutationDontRemind.value = false;
149
- mutationModalOpen.value = true;
150
- return;
151
- }
152
-
153
- await runPrepared(prepared.query, prepared.source);
154
- }
155
-
156
- async function confirmMutation() {
157
- const payload = pendingPayload;
158
- pendingPayload = null;
159
- mutationModalOpen.value = false;
160
- if (mutationDontRemind.value && typeof window !== "undefined") {
161
- window.localStorage?.setItem(MUTATION_SKIP_KEY, "true");
162
- }
163
- if (payload) {
164
- await runPrepared(payload.query, payload.source);
165
- }
166
- }
167
-
168
- function cancelMutation() {
169
- pendingPayload = null;
170
- mutationModalOpen.value = false;
171
- }
172
-
173
- function openSaveModal() {
174
- editingSaved.value = null;
175
- saveName.value = "";
176
- saveDescription.value = "";
177
- saveShared.value = false;
178
- // Snapshot the editor state; confirmSave only reads modal refs.
179
- saveSource.value = queryText.value;
180
- saveLanguage.value = language.value;
181
- saveError.value = null;
182
- saveModalOpen.value = true;
183
- }
184
-
185
- // Editing reuses the save modal: open it blank, then overlay the item.
186
- function openEditModal(item: SavedQuery) {
187
- openSaveModal();
188
- editingSaved.value = item;
189
- saveName.value = item.name;
190
- saveDescription.value = item.description;
191
- saveShared.value = item.shared;
192
- saveSource.value = item.source;
193
- saveLanguage.value = item.language;
194
- }
195
-
196
- async function confirmSave() {
197
- const name = saveName.value.trim();
198
- if (!name) return;
199
-
200
- // A metadata-only edit reuses the stored payload — re-preparing would
201
- // needlessly re-evaluate the source and fail when the query's schema is
202
- // no longer registered.
203
- const editing = editingSaved.value;
204
- let payload: { query: Record<string, unknown>; source: string };
205
- if (editing && saveSource.value === editing.source) {
206
- payload = { query: editing.query, source: editing.source };
207
- } else {
208
- try {
209
- payload = prepareQueryPayload(saveSource.value, dbSchemas.value);
210
- } catch (err) {
211
- saveError.value = (err as Error).message;
212
- return;
213
- }
214
- }
215
-
216
- saving.value = true;
217
- saveError.value = null;
218
- try {
219
- const input = {
220
- name,
221
- description: saveDescription.value.trim(),
222
- query: payload.query,
223
- source: payload.source,
224
- language: saveLanguage.value,
225
- shared: saveShared.value,
226
- };
227
- if (editing) {
228
- await updateSaved(editing.id, input);
229
- } else {
230
- await saveQuery(input);
231
- }
232
- saveModalOpen.value = false;
233
- await refreshSaved();
234
- } catch (err) {
235
- saveError.value = extractFetchError(err);
236
- } finally {
237
- saving.value = false;
238
- }
239
- }
240
-
241
- async function onDeleteSaved(id: string) {
242
- try {
243
- await deleteSaved(id);
244
- await refreshSaved();
245
- } catch {
246
- // swallow — re-fetch ensures the UI is consistent
247
- await refreshSaved();
248
- }
249
- }
250
-
251
- onMounted(async () => {
252
- await Promise.all([refreshSaved(), refreshHistory(), refreshSchemas()]);
253
- const queryParam = route.query.q;
254
- const targetId = typeof queryParam === "string" ? queryParam : null;
255
- if (targetId) {
256
- const match = savedQueries.value.find((q) => q.id === targetId);
257
- if (match) loadFromSaved(match);
258
- }
259
- // A raw DSL snippet forwarded from the data browser ("Open in query console")
260
- // prefills the editor directly.
261
- const sourceParam = route.query.source;
262
- const prefill = typeof sourceParam === "string" ? sourceParam : null;
263
- if (prefill) queryText.value = prefill;
264
- if (targetId || prefill) {
265
- router.replace({ query: { ...route.query, q: undefined, source: undefined } });
266
- }
267
- });
268
- </script>
269
-
270
- <template>
271
- <!-- The page fills the panel (`fillHeight` in src/pages/query.ts): each
272
- column scrolls on its own, and the grid scrolls when they stack. -->
273
- <div class="flex min-h-0 flex-1 flex-col gap-6">
274
- <section class="flex flex-wrap items-center gap-4 pt-6 pb-2">
275
- <div
276
- class="rounded-lg bg-primary/10 shadow-sm shrink-0 ring ring-primary/20 flex items-center justify-center size-12"
277
- >
278
- <UIcon name="i-ph-code" class="text-primary" size="1.75rem" />
279
- </div>
280
- <div class="flex-1 min-w-0">
281
- <h1 class="text-highlighted font-semibold">
282
- {{ $t("dms_database.query.title") }}
283
- </h1>
284
- <p class="text-muted text-sm">
285
- {{ $t("dms_database.query.description") }}
286
- </p>
287
- </div>
288
- <UButton
289
- :to="DATA_PATH"
290
- color="neutral"
291
- variant="ghost"
292
- icon="i-ph-table"
293
- :label="$t('dms_database.query.backToData')"
294
- class="ml-auto"
295
- />
296
- </section>
297
-
298
- <div
299
- class="grid min-h-0 flex-1 grid-cols-1 gap-5 pb-6 max-lg:overflow-y-auto lg:grid-cols-[18rem_1fr]"
300
- >
301
- <aside class="flex min-h-0 flex-col gap-5 lg:overflow-y-auto">
302
- <DmsDatabaseQueryHistoryPanel
303
- v-model:page="historyPage"
304
- :items="historyItems"
305
- :total="historyTotal"
306
- :page-size="HISTORY_PAGE_SIZE"
307
- class="flex-1 min-h-0 max-h-96"
308
- @load="loadFromHistory"
309
- @save="saveFromHistory"
310
- />
311
- <DmsDatabaseQueryFavoritesPanel
312
- v-model:scope="savedScope"
313
- :items="savedQueries"
314
- class="flex-1 min-h-0 max-h-80"
315
- @load="loadFromSaved"
316
- @edit="openEditModal"
317
- @delete="onDeleteSaved"
318
- />
319
- </aside>
320
-
321
- <div class="flex min-h-0 min-w-0 flex-col gap-5 lg:overflow-y-auto">
322
- <DmsDatabaseQueryBuilderPanel
323
- v-model="queryText"
324
- v-model:language="language"
325
- :executing="executing"
326
- :schemas="dbSchemas"
327
- @execute="onExecute"
328
- @save="openSaveModal"
329
- />
330
- <DmsDatabaseQueryResultPanel
331
- v-model:mode="resultMode"
332
- :result="lastResult"
333
- />
334
- </div>
335
- </div>
336
-
337
- <UModal
338
- v-model:open="saveModalOpen"
339
- :title="
340
- editingSaved
341
- ? $t('dms_database.query.editModal.title')
342
- : $t('dms_database.query.saveModal.title')
343
- "
344
- >
345
- <template #body>
346
- <div class="space-y-4">
347
- <UFormField :label="$t('dms_database.query.saveModal.name')" required>
348
- <UInput
349
- v-model="saveName"
350
- autofocus
351
- class="w-full"
352
- :placeholder="$t('dms_database.query.saveModal.namePlaceholder')"
353
- />
354
- </UFormField>
355
- <UFormField :label="$t('dms_database.query.saveModal.description')">
356
- <UTextarea
357
- v-model="saveDescription"
358
- class="w-full"
359
- :placeholder="$t('dms_database.query.saveModal.descriptionPlaceholder')"
360
- :rows="3"
361
- />
362
- </UFormField>
363
- <UFormField
364
- v-if="editingSaved"
365
- :label="$t('dms_database.query.editModal.query')"
366
- >
367
- <UTextarea
368
- v-model="saveSource"
369
- class="w-full font-mono"
370
- :rows="4"
371
- />
372
- </UFormField>
373
- <UCheckbox
374
- v-model="saveShared"
375
- :label="$t('dms_database.query.saveModal.share')"
376
- :description="$t('dms_database.query.saveModal.shareDescription')"
377
- />
378
- <UAlert
379
- v-if="saveError"
380
- color="error"
381
- variant="subtle"
382
- :title="$t('dms_database.query.errorTitle')"
383
- :description="saveError"
384
- />
385
- </div>
386
- </template>
387
- <template #footer>
388
- <div class="flex w-full justify-end gap-2">
389
- <UButton
390
- variant="outline"
391
- color="neutral"
392
- :disabled="saving"
393
- @click="saveModalOpen = false"
394
- >
395
- {{ $t("dms_database.query.saveModal.cancel") }}
396
- </UButton>
397
- <UButton
398
- color="primary"
399
- :loading="saving"
400
- :disabled="!saveName.trim() || saving"
401
- @click="confirmSave"
402
- >
403
- {{ $t("dms_database.query.saveModal.save") }}
404
- </UButton>
405
- </div>
406
- </template>
407
- </UModal>
408
-
409
- <UModal
410
- v-model:open="mutationModalOpen"
411
- :title="$t('dms_database.query.mutationModal.title')"
412
- >
413
- <template #body>
414
- <div class="space-y-4">
415
- <p class="text-sm text-muted">
416
- {{ $t("dms_database.query.mutationModal.body") }}
417
- </p>
418
- <UCheckbox
419
- v-model="mutationDontRemind"
420
- :label="$t('dms_database.query.mutationModal.dontRemind')"
421
- />
422
- </div>
423
- </template>
424
- <template #footer>
425
- <div class="flex w-full justify-end gap-2">
426
- <UButton variant="outline" color="neutral" @click="cancelMutation">
427
- {{ $t("dms_database.query.mutationModal.cancel") }}
428
- </UButton>
429
- <UButton color="warning" @click="confirmMutation">
430
- {{ $t("dms_database.query.mutationModal.confirm") }}
431
- </UButton>
432
- </div>
433
- </template>
434
- </UModal>
435
- </div>
436
- </template>
@@ -1,218 +0,0 @@
1
- <script setup lang="ts">
2
- import type { TableSummary } from "../../composables/useDatabaseSchemas";
3
- import SchemaTableInspector from "../../components/SchemaTableInspector.vue";
4
- import { buildReadonlyColumns } from "../../utils/dmsTableView";
5
-
6
- const TABLES_LOCATION = "/api/database/tables";
7
- const COMPONENT_ID = "database-schemas-tables";
8
- const PAGE_ID = "database.schemas";
9
-
10
- // One table summary row, as returned by /api/database/tables/list.
11
- interface TableListRow {
12
- id: string;
13
- schema: string;
14
- name: string;
15
- columnCount: number;
16
- indexCount: number;
17
- relationCount: number;
18
- elementCount: number;
19
- }
20
-
21
- const route = useDmsRoute();
22
- const { t } = useI18n();
23
- const { schemas } = useDatabaseSchemas();
24
-
25
- const fullFormatter = new Intl.NumberFormat("en-US");
26
-
27
- // --- schema selection (pill) ---
28
- // Local state (not URL-bound) to avoid re-triggering the global module-routing
29
- // guard on every selection, which can loop. Deep links via ?schema are honoured
30
- // once on load; the diagram link carries the schema explicitly.
31
- const initialSchema = route.query.schema;
32
- const selectedSchemaId = ref<string | null>(
33
- typeof initialSchema === "string" && initialSchema.length > 0
34
- ? initialSchema
35
- : null,
36
- );
37
-
38
- const currentSchema = computed(
39
- () => schemas.value.find((s) => s.id === selectedSchemaId.value) ?? null,
40
- );
41
-
42
- const schemaOptions = computed(() =>
43
- schemas.value.map((s) => ({ label: s.id, value: s.id })),
44
- );
45
-
46
- watch(
47
- schemas,
48
- (list) => {
49
- if (selectedSchemaId.value) return;
50
- if (list[0]) selectedSchemaId.value = list[0].id;
51
- },
52
- { immediate: true },
53
- );
54
-
55
- // --- list / diagram view toggle (inline alternate view, no navigation) ---
56
- // View is local state, diagram by default; ?view=list deep links are honoured
57
- // once on load without writing back to the URL.
58
- type SchemaView = "list" | "diagram";
59
- const view = ref<SchemaView>(route.query.view === "list" ? "list" : "diagram");
60
- const viewItems = computed(() => [
61
- { label: t("dms_database.schemas.view.diagram"), value: "diagram" as const, icon: "i-ph-graph" },
62
- { label: t("dms_database.schemas.view.list"), value: "list" as const, icon: "i-ph-table" },
63
- ]);
64
-
65
- // --- tables list (shared DmsTableView, read-only) ---
66
- const columns = computed(() =>
67
- buildReadonlyColumns([
68
- { key: "name", header: t("dms_database.overview.tables.cols.table") },
69
- { key: "elementCount", header: t("dms_database.overview.tables.cols.rows") },
70
- { key: "columnCount", header: t("dms_database.overview.tables.cols.columns") },
71
- { key: "indexCount", header: t("dms_database.overview.tables.cols.indexes") },
72
- { key: "relationCount", header: t("dms_database.schemas.cols.relations") },
73
- ]),
74
- );
75
-
76
- // Pin the table list to the selected schema. The table is remounted (`:key`) when
77
- // the schema changes so the filter is rebuilt cleanly.
78
- const defaultFilters = computed(() =>
79
- selectedSchemaId.value
80
- ? [{ accessorKey: "schema", value: selectedSchemaId.value, mode: "is" }]
81
- : [],
82
- );
83
-
84
- const collectionsLabel = computed(() =>
85
- t(
86
- "dms_database.schemas.collections",
87
- { count: currentSchema.value?.tables.length ?? 0 },
88
- currentSchema.value?.tables.length ?? 0,
89
- ),
90
- );
91
-
92
- // --- inspector (shared dms drawer) ---
93
- // Opened imperatively through the dms-ui `useDrawer` composable: the shared
94
- // DynamicDrawer owns the themed surface, the header (table name + subtitle) and
95
- // the close button; SchemaTableInspector only renders the body (tabs + content).
96
- const { open: openDrawer } = useDrawer();
97
-
98
- function inspectorSubtitle(table: TableSummary, rows: number | null): string {
99
- const parts: string[] = [];
100
- if (rows != null) {
101
- parts.push(
102
- `${fullFormatter.format(rows)} ${t("dms_database.overview.storage.rowsUnit")}`,
103
- );
104
- }
105
- parts.push(
106
- t("dms_database.data.colCount", {
107
- count: Object.keys(table.fields ?? {}).length,
108
- }),
109
- );
110
- return parts.join(" · ");
111
- }
112
-
113
- function openInspectorForTable(table: TableSummary, rows: number | null) {
114
- // `useDrawer` (dms-ui ≥ 0.1.3) defers the actual open until any closing
115
- // row-actions menu has left the DOM, so opening from the "View details"
116
- // dropdown no longer flashes the drawer in and out — no local wait needed.
117
- // The schema id is captured here (at call time) into componentOptions rather
118
- // than read lazily: the open may still be deferred while the menu closes, and
119
- // the schema pill could change in that window.
120
- openDrawer({
121
- title: table.name,
122
- description: inspectorSubtitle(table, rows),
123
- direction: "right",
124
- component: SchemaTableInspector,
125
- componentOptions: {
126
- schemaId: selectedSchemaId.value,
127
- table,
128
- },
129
- });
130
- }
131
-
132
- function openInspector(row: TableListRow) {
133
- // The list row carries display counts; the full descriptor (fields/indexes/
134
- // relations) comes from the schema introspection the inspector needs.
135
- const table = currentSchema.value?.tables.find((tbl) => tbl.name === row.name);
136
- if (!table) return;
137
- openInspectorForTable(table, row.elementCount ?? null);
138
- }
139
-
140
- // Double-clicking a table node in the diagram opens the same inspector drawer.
141
- function openInspectorByName(tableName: string) {
142
- const table = currentSchema.value?.tables.find((tbl) => tbl.name === tableName);
143
- if (!table) return;
144
- openInspectorForTable(table, null);
145
- }
146
-
147
- // A row (double-)click opens the table inspector drawer.
148
- useTableViewRowClick<TableListRow>(COMPONENT_ID, openInspector);
149
- </script>
150
-
151
- <template>
152
- <!-- The page fills the panel (`fillHeight` in src/pages/schemas.ts): the
153
- list scrolls under the header, the diagram takes the height left. -->
154
- <div class="flex min-h-0 flex-1 flex-col gap-5">
155
- <section class="flex flex-wrap items-center gap-4 pt-6 pb-2">
156
- <div
157
- class="rounded-lg bg-primary/10 shadow-sm shrink-0 ring ring-primary/20 flex items-center justify-center size-12"
158
- >
159
- <UIcon name="i-ph-tree-structure" class="text-primary" size="1.75rem" />
160
- </div>
161
- <div class="flex-1 min-w-0">
162
- <h1 class="text-highlighted font-semibold">
163
- {{ $t("dms_database.schemas.title") }}
164
- </h1>
165
- <p class="text-muted text-sm">
166
- {{ $t("dms_database.schemas.description") }}
167
- </p>
168
- </div>
169
- <div class="flex items-center gap-3 ml-auto">
170
- <USelect
171
- v-model="selectedSchemaId"
172
- :items="schemaOptions"
173
- icon="i-ph-database"
174
- :placeholder="$t('dms_database.data.schema')"
175
- class="min-w-40"
176
- />
177
- <DmsSegmented
178
- v-model="view"
179
- :items="viewItems"
180
- :aria-label="$t('dms_database.schemas.view.list')"
181
- />
182
- </div>
183
- </section>
184
-
185
- <div v-show="view === 'list'" class="min-h-0 flex-1 space-y-2 overflow-y-auto">
186
- <p class="text-dimmed px-1 text-xs">
187
- <span class="font-mono">{{ selectedSchemaId }}</span>
188
- · {{ collectionsLabel }}
189
- </p>
190
- <DmsTableView
191
- v-if="selectedSchemaId"
192
- :key="selectedSchemaId"
193
- :location="TABLES_LOCATION"
194
- :columns="columns"
195
- :form-components="{}"
196
- :form-container="{ type: 'drawer' }"
197
- :row-actions="{ details: true }"
198
- :default-filters="defaultFilters"
199
- :default-sort="{ field: 'elementCount', desc: true }"
200
- :component-id="COMPONENT_ID"
201
- :page-id="PAGE_ID"
202
- row-id-key="id"
203
- />
204
- </div>
205
-
206
- <!-- DmsClientOnly: vue-flow is not SSR-safe. Kept mounted (v-show) so toggling
207
- List/Diagram preserves un-applied diagram edits and avoids a blocking
208
- confirm dialog on every switch. -->
209
- <DmsClientOnly>
210
- <DmsDatabaseSchemaDiagram
211
- v-show="view === 'diagram'"
212
- :schema-id="selectedSchemaId"
213
- :active="view === 'diagram'"
214
- @inspect-table="openInspectorByName"
215
- />
216
- </DmsClientOnly>
217
- </div>
218
- </template>
@@ -1,33 +0,0 @@
1
- export interface ModifierIcon {
2
- icon: string;
3
- color: string;
4
- label: string;
5
- }
6
-
7
- const MODIFIER_ICONS: Record<string, ModifierIcon> = {
8
- HashModifier: {
9
- icon: "i-ph-fingerprint",
10
- color: "text-rose-500",
11
- label: "Hashed",
12
- },
13
- LocalizationModifier: {
14
- icon: "i-ph-translate",
15
- color: "text-cyan-500",
16
- label: "Localized",
17
- },
18
- EncryptionModifier: {
19
- icon: "i-ph-lock-key",
20
- color: "text-indigo-500",
21
- label: "Encrypted",
22
- },
23
- };
24
-
25
- const fallbackModifierIcon = (id: string): ModifierIcon => ({
26
- icon: "i-ph-question",
27
- color: "text-muted",
28
- label: id,
29
- });
30
-
31
- export function resolveModifierIcon(modifierId: string): ModifierIcon {
32
- return MODIFIER_ICONS[modifierId] ?? fallbackModifierIcon(modifierId);
33
- }