@live-change/db-admin 0.5.6

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 (50) hide show
  1. package/LICENSE +21 -0
  2. package/e2e/codecept.conf.js +60 -0
  3. package/e2e/connectEmailCode.test.js +61 -0
  4. package/e2e/connectEmailLink.test.js +60 -0
  5. package/e2e/delete.test.js +44 -0
  6. package/e2e/disconnectEmail.test.js +42 -0
  7. package/e2e/resetPasswordWithEmailCode.test.js +62 -0
  8. package/e2e/resetPasswordWithEmailLink.test.js +62 -0
  9. package/e2e/setPassword.test.js +70 -0
  10. package/e2e/signInEmailCode.test.js +52 -0
  11. package/e2e/signInEmailLink.test.js +52 -0
  12. package/e2e/signInEmailPassword.test.js +47 -0
  13. package/e2e/signOut.test.js +41 -0
  14. package/e2e/signUpEmailCode.test.js +41 -0
  15. package/e2e/signUpEmailLink.test.js +41 -0
  16. package/e2e/steps.d.ts +12 -0
  17. package/e2e/steps_file.js +85 -0
  18. package/front/assets/images/empty-photo.svg +38 -0
  19. package/front/assets/images/empty-user-photo.svg +33 -0
  20. package/front/assets/images/logo.svg +34 -0
  21. package/front/index.html +11 -0
  22. package/front/public/favicon.ico +0 -0
  23. package/front/public/images/empty-photo.svg +38 -0
  24. package/front/public/images/empty-user-photo.svg +33 -0
  25. package/front/public/images/logo.svg +34 -0
  26. package/front/public/images/logo128.png +0 -0
  27. package/front/src/App.vue +93 -0
  28. package/front/src/CodeEditor.vue +92 -0
  29. package/front/src/Data.vue +74 -0
  30. package/front/src/DataRangeView.vue +58 -0
  31. package/front/src/DataView.vue +51 -0
  32. package/front/src/Database.vue +364 -0
  33. package/front/src/DatabaseAdmin.vue +135 -0
  34. package/front/src/Databases.vue +110 -0
  35. package/front/src/NavBar.vue +105 -0
  36. package/front/src/ObjectEditor.vue +143 -0
  37. package/front/src/Page.vue +39 -0
  38. package/front/src/PathEditor.vue +210 -0
  39. package/front/src/dbSugar.js +75 -0
  40. package/front/src/entry-client.js +24 -0
  41. package/front/src/entry-server.js +59 -0
  42. package/front/src/isClientSide.js +3 -0
  43. package/front/src/main.js +61 -0
  44. package/front/src/path.js +68 -0
  45. package/front/src/router.js +42 -0
  46. package/front/src/routes.js +31 -0
  47. package/front/vite.config.js +107 -0
  48. package/package.json +71 -0
  49. package/server/init.js +16 -0
  50. package/server/services.config.js +4 -0
@@ -0,0 +1,74 @@
1
+ <template>
2
+
3
+ <PathEditor v-model="path"
4
+ @update:read="v => read = v"
5
+ @update:write="v => write = v"
6
+ @update:remove="v => remove = v" />
7
+
8
+ <!-- <p>{{ path }}</p>-->
9
+
10
+ <template v-if="read?.external?.includes('range')">
11
+ <DataRangeView v-if="read && write && remove" :read="read.result" :write="write.result" :remove="remove.result" />
12
+ </template>
13
+ <template v-else>
14
+ <DataView v-if="read && write && remove" :read="read.result" :write="write.result" :remove="remove.result" />
15
+ </template>
16
+
17
+ </template>
18
+
19
+ <script setup>
20
+ import PathEditor from "./PathEditor.vue"
21
+ import DataRangeView from "./DataRangeView.vue"
22
+ import DataView from "./DataView.vue"
23
+
24
+ const props = defineProps({
25
+ position: {
26
+ type: String,
27
+ required: true
28
+ },
29
+ read: {
30
+ type: String,
31
+ required: true
32
+ },
33
+ write: {
34
+ type: String,
35
+ required: true
36
+ },
37
+ remove: {
38
+ type: String,
39
+ required: true
40
+ },
41
+ params: {
42
+ type: Array,
43
+ required: true
44
+ }
45
+ })
46
+
47
+ const pathParams = new Array(props.params.length / 2)
48
+ for(let i = 0; i < props.params.length/2; i++) pathParams[i] = [props.params[i * 2], props.params[i * 2 + 1]]
49
+
50
+ import { ref, watch } from 'vue'
51
+ import { useRouter, useRoute } from 'vue-router'
52
+
53
+ const path = ref({ read: props.read, write: props.write, remove: props.remove, params: pathParams })
54
+
55
+ const router = useRouter()
56
+ const route = useRoute()
57
+
58
+ watch(() => path.value, value => {
59
+ console.log("PATH VALUE UPDATED", value)
60
+ const paramsArray = value.params.flat()
61
+ router.replace({ name: route.name, params: {
62
+ read: value.read,
63
+ write: value.write,
64
+ remove: value.remove,
65
+ params: paramsArray
66
+ } })
67
+ /// TODO: update URL
68
+ })
69
+
70
+ const read = ref()
71
+ const write = ref()
72
+ const remove = ref()
73
+
74
+ </script>
@@ -0,0 +1,58 @@
1
+ <template>
2
+ <div class="rows w-full mt-2">
3
+ <scroll-border placement="top"
4
+ :load="dataBuckets.loadTop"
5
+ :canLoad="dataBuckets.canLoadTop" />
6
+ <div v-for="(bucket, bucketIndex) in dataBuckets.buckets" :key="bucket.id"
7
+ class="w-full">
8
+ <div v-for="(row, index) in bucket.data" :key="row.id" :ref="el => bucket.domElements[index] = el"
9
+ class="surface-0 shadow-1 w-full">
10
+ <!-- {{ JSON.stringify(row) }}-->
11
+ <object-editor :currentData="JSON.stringify(row)"
12
+ :write="write" :remove="remove"
13
+ :dbApi="dbApi" />
14
+ </div>
15
+ </div>
16
+ <scroll-border placement="bottom"
17
+ :load="dataBuckets.loadBottom"
18
+ :canLoad="dataBuckets.canLoadBottom" />
19
+ </div>
20
+ </template>
21
+
22
+ <script setup>
23
+
24
+ import { path, live, actions, api, rangeBuckets, reverseRange } from '@live-change/vue3-ssr'
25
+ import ScrollBorder from 'vue3-scroll-border'
26
+ import ObjectEditor from "./ObjectEditor.vue"
27
+
28
+ import { dbViewSugar } from "./dbSugar.js"
29
+
30
+ const { dbApi, read, write, remove } = defineProps({
31
+ dbApi: {
32
+ type: String,
33
+ default: 'serverDatabase'
34
+ },
35
+ read: {
36
+ type: Function,
37
+ required: true
38
+ },
39
+ write: {
40
+ type: Function,
41
+ default: null
42
+ },
43
+ remove: {
44
+ type: Function,
45
+ default: null
46
+ }
47
+ })
48
+
49
+
50
+ const [ dataBuckets ] = await Promise.all([
51
+ rangeBuckets((range, p) => [dbApi, ...JSON.parse(JSON.stringify(read({ range }, dbViewSugar)))])
52
+ ])
53
+
54
+ </script>
55
+
56
+ <style scoped>
57
+
58
+ </style>
@@ -0,0 +1,51 @@
1
+ <template>
2
+ <div class="rows w-full mt-2">
3
+ <div v-for="(row, index) in dataRows" :key="row.id"
4
+ class="surface-0 shadow-1 w-full">
5
+ <!-- {{ JSON.stringify(row) }}-->
6
+ <object-editor :currentData="JSON.stringify(row)"
7
+ :write="write" :remove="remove"
8
+ :dbApi="dbApi" />
9
+ </div>
10
+ </div>
11
+ </template>
12
+
13
+ <script setup>
14
+
15
+ import { path, live, actions, api, rangeBuckets, reverseRange } from '@live-change/vue3-ssr'
16
+ import ScrollBorder from 'vue3-scroll-border'
17
+ import ObjectEditor from "./ObjectEditor.vue"
18
+
19
+ import { dbViewSugar } from "./dbSugar.js"
20
+
21
+ const { dbApi, read, write, remove } = defineProps({
22
+ dbApi: {
23
+ type: String,
24
+ default: 'serverDatabase'
25
+ },
26
+ read: {
27
+ type: Function,
28
+ required: true
29
+ },
30
+ write: {
31
+ type: Function,
32
+ default: null
33
+ },
34
+ remove: {
35
+ type: Function,
36
+ default: null
37
+ }
38
+ })
39
+
40
+
41
+ const [ dataRows ] = await Promise.all([
42
+ live({
43
+ what: [dbApi, ...JSON.parse(JSON.stringify(read({ }, dbViewSugar)))]
44
+ })
45
+ ])
46
+
47
+ </script>
48
+
49
+ <style scoped>
50
+
51
+ </style>
@@ -0,0 +1,364 @@
1
+ <template>
2
+ <div class="surface-card p-4 shadow-2 border-round w-full">
3
+ <div class="text-center mb-3">
4
+ <div class="text-900 text-3xl font-medium mb-3">Database "{{ dbName }}"</div>
5
+ </div>
6
+ <div class="text-center mb-3">
7
+ <div v-if="tables.length > 0" class="text-900 text-2xl font-medium mb-3">Tables</div>
8
+ <div v-else class="text-600 text-xl font-medium mb-3">
9
+ There are no tables. Create first one.
10
+ </div>
11
+ </div>
12
+
13
+ <DataTable v-if="tables.length > 0" :value="tables" responsiveLayout="scroll">
14
+ <Column field="id" header="Table">
15
+ <template #body="slotProps">
16
+ <form v-if="tableRename == slotProps.data.id" @submit="ev => finishTableRename(ev, slotProps.data.id)">
17
+ <InputText v-model="tableNewName" />
18
+ </form>
19
+ <router-link v-else :to="tableLink(slotProps.data.id)">
20
+ {{ slotProps.data.id }}
21
+ </router-link>
22
+ </template>
23
+ </Column>
24
+ <Column field="rows" header="Rows" :headerStyle="{ 'width': '60px' }"></Column>
25
+ <Column :headerStyle="{ 'width': '120px' }">
26
+ <template #body="slotProps">
27
+ <Button v-if="tableRename == slotProps.data.id"
28
+ @click="ev => finishTableRename(ev, slotProps.data.id)" type="button"
29
+ icon="pi pi-save" class="p-button-rounded p-button-primary mr-2" />
30
+ <Button v-else
31
+ @click="ev => startTableRename(ev, slotProps.data.id)" type="button"
32
+ icon="pi pi-pencil" class="p-button-rounded p-button-warning mr-2" />
33
+ <Button @click="ev => deleteTable(ev, slotProps.data.id)" type="button"
34
+ icon="pi pi-trash" class="p-button-rounded p-button-danger" />
35
+ </template>
36
+ </Column>
37
+ </DataTable>
38
+
39
+ <form class="mt-4 flex flex-row justify-content-center" @submit="handleNewTableSubmit">
40
+ <InputText v-model="newTableName" class="mr-2" placeholder="Table Name" />
41
+ <Button type="submit" class="p-button-primary" icon="pi pi-plus" label="Create Table" />
42
+ </form>
43
+
44
+
45
+ <div class="text-center mb-3 mt-5">
46
+ <div v-if="logs.length > 0" class="text-900 text-2xl font-medium mb-3">Logs</div>
47
+ <div v-else class="text-600 text-xl font-medium mb-3">
48
+ There are no logs. Create first one.
49
+ </div>
50
+ </div>
51
+
52
+ <DataTable v-if="logs.length > 0" :value="logs" responsiveLayout="scroll">
53
+ <Column field="id" header="Log">
54
+ <template #body="slotProps">
55
+ <form v-if="logRename == slotProps.data.id" @submit="ev => finishLogRename(ev, slotProps.data.id)">
56
+ <InputText v-model="logNewName" />
57
+ </form>
58
+ <router-link v-else :to="logLink(slotProps.data.id)">
59
+ {{ slotProps.data.id }}
60
+ </router-link>
61
+ </template>
62
+ </Column>
63
+ <Column field="rows" header="Rows" :headerStyle="{ 'width': '60px' }"></Column>
64
+ <Column :headerStyle="{ 'width': '120px' }">
65
+ <template #body="slotProps">
66
+ <Button v-if="logRename == slotProps.data.id"
67
+ @click="ev => finishLogRename(ev, slotProps.data.id)" type="button"
68
+ icon="pi pi-save" class="p-button-rounded p-button-primary mr-2" />
69
+ <Button v-else
70
+ @click="ev => startLogRename(ev, slotProps.data.id)" type="button"
71
+ icon="pi pi-pencil" class="p-button-rounded p-button-warning mr-2" />
72
+ <Button @click="ev => deleteLog(ev, slotProps.data.id)" type="button"
73
+ icon="pi pi-trash" class="p-button-rounded p-button-danger" />
74
+ </template>
75
+ </Column>
76
+ </DataTable>
77
+
78
+ <form class="mt-4 flex flex-row justify-content-center" @submit="handleNewLogSubmit">
79
+ <InputText v-model="newLogName" class="mr-2" placeholder="Log Name" />
80
+ <Button type="submit" class="p-button-primary" icon="pi pi-plus" label="Create Log" />
81
+ </form>
82
+
83
+
84
+ <div class="text-center mb-3 mt-5">
85
+ <div v-if="indexes.length > 0" class="text-900 text-2xl font-medium mb-3">Indexes</div>
86
+ <div v-else class="text-600 text-xl font-medium mb-3">
87
+ There are no indexes.
88
+ </div>
89
+ </div>
90
+
91
+ <DataTable v-if="indexes.length > 0" :value="indexes" responsiveLayout="scroll">
92
+ <Column field="id" header="Index">
93
+ <template #body="slotProps">
94
+ <form v-if="indexRename == slotProps.data.id" @submit="ev => finishIndexRename(ev, slotProps.data.id)">
95
+ <InputText v-model="indexNewName" />
96
+ </form>
97
+ <router-link v-else :to="indexLink(slotProps.data.id)">
98
+ {{ slotProps.data.id }}
99
+ </router-link>
100
+ </template>
101
+ </Column>
102
+ <Column field="rows" header="Rows" :headerStyle="{ 'width': '60px' }"></Column>
103
+ <Column :headerStyle="{ 'width': '120px' }">
104
+ <template #body="slotProps">
105
+ <Button v-if="indexRename == slotProps.data.id"
106
+ @click="ev => finishIndexRename(ev, slotProps.data.id)" type="button"
107
+ icon="pi pi-save" class="p-button-rounded p-button-primary mr-2" />
108
+ <Button v-else
109
+ @click="ev => startIndexRename(ev, slotProps.data.id)" type="button"
110
+ icon="pi pi-pencil" class="p-button-rounded p-button-warning mr-2" />
111
+ <Button @click="ev => deleteIndex(ev, slotProps.data.id)" type="button"
112
+ icon="pi pi-trash" class="p-button-rounded p-button-danger" />
113
+ </template>
114
+ </Column>
115
+ </DataTable>
116
+
117
+ </div>
118
+ </template>
119
+
120
+ <script setup>
121
+ import DataTable from "primevue/datatable"
122
+ import Column from "primevue/column"
123
+ import Button from "primevue/button"
124
+ import InputText from "primevue/inputtext"
125
+
126
+ import ConfirmPopup from 'primevue/confirmpopup'
127
+ import Toast from 'primevue/toast'
128
+
129
+ const { dbApi, dbName } = defineProps({
130
+ dbApi: {
131
+ type: String,
132
+ default: 'serverDatabase'
133
+ },
134
+ dbName: {
135
+ type: String,
136
+ required: true
137
+ }
138
+ })
139
+
140
+ import { ref, onMounted, onUnmounted, inject } from "vue"
141
+ let isMounted = ref(false)
142
+ onMounted(() => isMounted.value = true)
143
+ onUnmounted(() => isMounted.value = false)
144
+
145
+ import { api } from "@live-change/vue3-ssr"
146
+ const dao = api().source
147
+ import { live, RangeBuckets } from "@live-change/dao-vue3"
148
+
149
+ import { useToast } from 'primevue/usetoast'
150
+ const toast = useToast()
151
+ import { useConfirm } from 'primevue/useconfirm'
152
+ const confirm = useConfirm()
153
+
154
+ const workingZone = inject('workingZone')
155
+
156
+ function deleteTable(event, id) {
157
+ confirm.require({
158
+ target: event.currentTarget,
159
+ message: `Do you really want to delete table ${id}?`,
160
+ icon: 'pi pi-info-circle',
161
+ acceptClass: 'p-button-danger',
162
+ accept: async () => {
163
+ workingZone.addPromise('deleteTable', (async () => {
164
+ await dao.request([dbApi, 'deleteTable'], dbName, id)
165
+ toast.add({ severity:'info', summary: `Table ${id} deleted`, life: 1500 })
166
+ })())
167
+ },
168
+ reject: () => {
169
+ toast.add({ severity:'error', summary: 'Rejected', detail: 'You have rejected', life: 3000 })
170
+ }
171
+ })
172
+ }
173
+
174
+ const tableRename = ref("")
175
+ const tableNewName = ref("")
176
+ function startTableRename(event, id) {
177
+ tableNewName.value = id
178
+ tableRename.value = id
179
+ }
180
+ function finishTableRename(event, id) {
181
+ event.preventDefault()
182
+ const oldTableName = tableRename.value
183
+ const tableName = tableNewName.value
184
+ tableRename.value = null
185
+ if(oldTableName == tableName) return
186
+ workingZone.addPromise('renameTable', (async () => {
187
+ try {
188
+ await dao.request([dbApi, 'renameTable'], dbName, oldTableName, tableName)
189
+ toast.add({severity: 'info', summary: `Table ${oldTableName} renamed to ${tableName}`, life: 1500})
190
+ } catch(error) {
191
+ toast.add({ severity: 'error', summary: `Table ${tableName} not renamed`, detail: error.message, life: 3000 })
192
+ }
193
+ })())
194
+ }
195
+
196
+
197
+ const newTableName = ref("")
198
+ function handleNewTableSubmit(event) {
199
+ event.preventDefault()
200
+ const tableName = newTableName.value
201
+ if(tableName.length == 0) return
202
+ newTableName.value = ""
203
+ workingZone.addPromise('createTable', (async () => {
204
+ try {
205
+ await dao.request([dbApi, 'createTable'], dbName, tableName)
206
+ toast.add({ severity: 'info', summary: `Table ${tableName} created`, life: 1500 })
207
+ } catch(error) {
208
+ toast.add({ severity: 'error', summary: `Table ${tableName} not created`, detail: error.message, life: 3000 })
209
+ }
210
+ })())
211
+ }
212
+
213
+
214
+ function deleteLog(event, id) {
215
+ confirm.require({
216
+ target: event.currentTarget,
217
+ message: `Do you really want to delete log ${id}?`,
218
+ icon: 'pi pi-info-circle',
219
+ acceptClass: 'p-button-danger',
220
+ accept: async () => {
221
+ workingZone.addPromise('deleteLog', (async () => {
222
+ await dao.request([dbApi, 'deleteLog'], dbName, id)
223
+ toast.add({ severity:'info', summary: `Log ${id} deleted`, life: 1500 })
224
+ })())
225
+ },
226
+ reject: () => {
227
+ toast.add({ severity:'error', summary: 'Rejected', detail: 'You have rejected', life: 3000 })
228
+ }
229
+ })
230
+ }
231
+
232
+ const logRename = ref("")
233
+ const logNewName = ref("")
234
+ function startLogRename(event, id) {
235
+ logNewName.value = id
236
+ logRename.value = id
237
+ }
238
+ function finishLogRename(event, id) {
239
+ event.preventDefault()
240
+ const oldLogName = logRename.value
241
+ const logName = logNewName.value
242
+ logRename.value = null
243
+ if(oldLogName == logName) return
244
+ workingZone.addPromise('renameLog', (async () => {
245
+ try {
246
+ await dao.request([dbApi, 'renameLog'], dbName, oldLogName, logName)
247
+ toast.add({severity: 'info', summary: `Log ${oldLogName} renamed to ${logName}`, life: 1500})
248
+ } catch(error) {
249
+ toast.add({ severity: 'error', summary: `Log ${logName} not renamed`, detail: error.message, life: 3000 })
250
+ }
251
+ })())
252
+ }
253
+
254
+ const newLogName = ref("")
255
+ function handleNewLogSubmit(event) {
256
+ event.preventDefault()
257
+ const logName = newLogName.value
258
+ newLogName.value = ""
259
+ workingZone.addPromise('createLog', (async () => {
260
+ try {
261
+ await dao.request([dbApi, 'createLog'], dbName, logName)
262
+ toast.add({severity: 'info', summary: `Log ${logName} created`, life: 1500})
263
+ } catch(error) {
264
+ toast.add({ severity: 'error', summary: `Log ${logName} not created`, detail: error.message, life: 3000 })
265
+ }
266
+ })())
267
+ }
268
+
269
+
270
+ function deleteIndex(event, id) {
271
+ confirm.require({
272
+ target: event.currentTarget,
273
+ message: `Do you really want to delete index ${id}?`,
274
+ icon: 'pi pi-info-circle',
275
+ acceptClass: 'p-button-danger',
276
+ accept: async () => {
277
+ workingZone.addPromise('deleteIndex', (async () => {
278
+ await dao.request([dbApi, 'deleteIndex'], dbName, id)
279
+ toast.add({ severity:'info', summary: `Index ${id} deleted`, life: 1500 })
280
+ })())
281
+ },
282
+ reject: () => {
283
+ toast.add({ severity:'error', summary: 'Rejected', detail: 'You have rejected', life: 3000 })
284
+ }
285
+ })
286
+ }
287
+
288
+ const indexRename = ref("")
289
+ const indexNewName = ref("")
290
+ function startIndexRename(event, id) {
291
+ indexNewName.value = id
292
+ indexRename.value = id
293
+ }
294
+ function finishIndexRename(event, id) {
295
+ event.preventDefault()
296
+ const oldIndexName = indexRename.value
297
+ const indexName = indexNewName.value
298
+ indexRename.value = null
299
+ if(oldIndexName == indexName) return
300
+ workingZone.addPromise('renameIndex', (async () => {
301
+ try {
302
+ await dao.request([dbApi, 'renameIndex'], dbName, oldIndexName, indexName)
303
+ toast.add({severity: 'info', summary: `Index ${oldIndexName} renamed to ${indexName}`, life: 1500})
304
+ } catch(error) {
305
+ toast.add({ severity: 'error', summary: `Index ${indexName} not renamed`, detail: error.message, life: 3000 })
306
+ }
307
+ })())
308
+ }
309
+
310
+ function tableLink(table) {
311
+ return { name: 'db:data', params: {
312
+ position: " ",
313
+ read: `db.tableRange($.db,$.table,$.range)`,
314
+ write: `db.put($.db,$.table,$.object)`,
315
+ remove: `db.delete($.db,$.table,$.object.id)`,
316
+ params: [
317
+ 'db', JSON.stringify(dbName),
318
+ 'table', JSON.stringify(table)
319
+ ]
320
+ } }
321
+ }
322
+
323
+ function logLink(table) {
324
+ return { name: 'db:data', params: {
325
+ position: " ",
326
+ read: `db.logRange($.db,$.table,$.range)`,
327
+ write: false,
328
+ remove: false,
329
+ params: [
330
+ 'db', JSON.stringify(dbName),
331
+ 'table', JSON.stringify(table)
332
+ ]
333
+ } }
334
+ }
335
+
336
+ function indexLink(table) {
337
+ return { name: 'db:data', params: {
338
+ position: " ",
339
+ read: `db.indexRange($.db,$.table,$.range)`,
340
+ write: false,
341
+ remove: false,
342
+ params: [
343
+ 'db', JSON.stringify(dbName),
344
+ 'table', JSON.stringify(table)
345
+ ]
346
+ } }
347
+ }
348
+
349
+ const [ tables, indexes, logs ] = await Promise.all([
350
+ live(dao, {
351
+ what: [dbApi, 'tables', dbName],
352
+ more: [{ to: 'rows', schema: [[dbApi, 'tableCount', dbName, { property: 'id' }, { static: { limit: 999 }} ]] }]
353
+ }),
354
+ live(dao, {
355
+ what: [dbApi, 'indexes', dbName],
356
+ more: [{ to: 'rows', schema: [[dbApi, 'indexCount', dbName, { property: 'id' }, { static: { limit: 999 }} ]] }]
357
+ }),
358
+ live(dao, {
359
+ what: [dbApi, 'logs', dbName],
360
+ more: [{ to: 'rows', schema: [[dbApi, 'logCount', dbName, { property: 'id' }, { static: { limit: 999 }} ]] }]
361
+ })
362
+ ])
363
+
364
+ </script>
@@ -0,0 +1,135 @@
1
+ <template>
2
+ <div class="min-h-screen flex relative lg:static surface-ground w-full">
3
+ <div id="app-sidebar-1" class="surface-section h-screen hidden lg:block flex-shrink-0 absolute lg:static left-0
4
+ top-0 z-1 border-right-1 surface-border select-none" style="width:280px">
5
+ <div class="flex flex-column h-full">
6
+ <div class="flex align-items-center px-5 flex-shrink-0" style="height:60px">
7
+ <img src="/images/logo.svg" alt="Image" height="30">
8
+ </div>
9
+ <div class="overflow-y-auto">
10
+ <ul class="list-none p-2 m-0">
11
+ <li v-for="database in databases">
12
+ <div class="p-3 flex align-items-center justify-content-between text-600 cursor-pointer p-ripple">
13
+ <router-link :to="{ name: 'db:database', params: { dbName: database.id } }"
14
+ class="font-medium no-underline text-600">
15
+ {{ database.id }}
16
+ </router-link>
17
+ <i class="pi pi-chevron-down"
18
+ v-styleclass="{ selector: `#db-menu-${database.id}`, enterClass: 'hidden',
19
+ enterActiveClass: 'slidedown', leaveToClass: 'hidden',
20
+ leaveActiveClass: 'slideup' }" v-ripple/>
21
+ </div>
22
+ <ul class="list-none p-0 m-0 ml-3 overflow-hidden hidden" :id="`db-menu-${database.id}`">
23
+ <li>
24
+ <a v-ripple class="flex align-items-center cursor-pointer p-3 border-round text-700 hover:surface-100
25
+ transition-duration-150 transition-colors p-ripple"
26
+ v-styleclass="{ selector: '@next', enterClass: 'hidden', enterActiveClass: 'slidedown',
27
+ leaveToClass: 'hidden', leaveActiveClass: 'slideup' }">
28
+ <i class="pi pi-table mr-2"></i>
29
+ <span class="font-medium">{{ database?.tables?.length }} TABLES</span>
30
+ <i class="pi pi-chevron-down ml-auto"></i>
31
+ </a>
32
+ <ul class="list-none py-0 pl-3 pr-0 m-0 hidden overflow-y-hidden transition-all
33
+ transition-duration-400 transition-ease-in-out">
34
+ <li v-for="table in database?.tables">
35
+ <a v-ripple class="flex align-items-center cursor-pointer p-3 border-round text-700
36
+ hover:surface-100 transition-duration-150 transition-colors p-ripple">
37
+ <span class="font-medium">{{ table }}</span>
38
+ </a>
39
+ </li>
40
+ </ul>
41
+ </li>
42
+ <li>
43
+ <a v-ripple class="flex align-items-center cursor-pointer p-3 border-round text-700 hover:surface-100
44
+ transition-duration-150 transition-colors p-ripple"
45
+ v-styleclass="{ selector: '@next', enterClass: 'hidden', enterActiveClass: 'slidedown',
46
+ leaveToClass: 'hidden', leaveActiveClass: 'slideup' }">
47
+ <i class="pi pi-external-link mr-2"></i>
48
+ <span class="font-medium">{{ database?.indexes?.length }} INDEXES</span>
49
+ <i class="pi pi-chevron-down ml-auto"></i>
50
+ </a>
51
+ <ul class="list-none py-0 pl-3 pr-0 m-0 hidden overflow-y-hidden transition-all
52
+ transition-duration-400 transition-ease-in-out">
53
+ <li v-for="index in database?.indexes">
54
+ <a v-ripple class="flex align-items-center cursor-pointer p-3 border-round text-700
55
+ hover:surface-100 transition-duration-150 transition-colors p-ripple">
56
+ <span class="font-medium">{{ index }}</span>
57
+ </a>
58
+ </li>
59
+ </ul>
60
+ </li>
61
+ <li>
62
+ <a v-ripple class="flex align-items-center cursor-pointer p-3 border-round text-700 hover:surface-100
63
+ transition-duration-150 transition-colors p-ripple"
64
+ v-styleclass="{ selector: '@next', enterClass: 'hidden', enterActiveClass: 'slidedown',
65
+ leaveToClass: 'hidden', leaveActiveClass: 'slideup' }">
66
+ <i class="pi pi-list mr-2"></i>
67
+ <span class="font-medium">{{ database?.logs?.length }} LOGS</span>
68
+ <i class="pi pi-chevron-down ml-auto"></i>
69
+ </a>
70
+ <ul class="list-none py-0 pl-3 pr-0 m-0 hidden overflow-y-hidden transition-all
71
+ transition-duration-400 transition-ease-in-out">
72
+ <li v-for="log in database?.logs">
73
+ <a v-ripple class="flex align-items-center cursor-pointer p-3 border-round text-700
74
+ hover:surface-100 transition-duration-150 transition-colors p-ripple">
75
+ <span class="font-medium">{{ log }}</span>
76
+ </a>
77
+ </li>
78
+ </ul>
79
+ </li>
80
+ </ul>
81
+ </li>
82
+ </ul>
83
+ </div>
84
+ </div>
85
+ </div>
86
+ <div class="min-h-screen flex flex-column relative flex-auto">
87
+ <div class="flex justify-content-between align-items-center px-5 surface-0 border-bottom-1 surface-border
88
+ relative lg:hidden" style="height:60px">
89
+ <div class="flex">
90
+ <a v-ripple class="cursor-pointer block text-700 mr-3 p-ripple"
91
+ v-styleclass="{ selector: '#app-sidebar-1', enterClass: 'hidden', enterActiveClass: 'fadeinleft', leaveToClass: 'hidden', leaveActiveClass: 'fadeoutleft', hideOnOutsideClick: true }">
92
+ <i class="pi pi-bars text-4xl"></i>
93
+ </a>
94
+ </div>
95
+ </div>
96
+ <div v-if="viewType == 'simple'" class="p-5 flex flex-column flex-auto align-items-center">
97
+ <router-view></router-view>
98
+ </div>
99
+ <template v-if="viewType == 'wide'">
100
+ <router-view></router-view>
101
+ </template>
102
+ </div>
103
+ </div>
104
+ </template>
105
+
106
+ <script setup>
107
+
108
+ const { dbApi } = defineProps({
109
+ dbApi: {
110
+ type: String,
111
+ default: 'serverDatabase'
112
+ }
113
+ })
114
+
115
+ import { computed } from 'vue'
116
+
117
+ import { useRoute } from 'vue-router'
118
+ const route = useRoute()
119
+
120
+ const viewType = computed(() => route.meta.viewType ?? 'simple' )
121
+
122
+ import { api } from "@live-change/vue3-ssr"
123
+ const dao = api().source
124
+ import { live, RangeBuckets } from "@live-change/dao-vue3"
125
+
126
+ const databases = await live(dao, {
127
+ what: [ dbApi, 'databases' ],
128
+ more: [
129
+ { to: 'tables', schema: [[ dbApi, 'tablesList', { property: 'id' } ]] },
130
+ { to: 'indexes', schema: [[ dbApi, 'indexesList', { property: 'id' } ]] },
131
+ { to: 'logs', schema: [[ dbApi, 'logsList', { property: 'id' } ]] }
132
+ ]
133
+ })
134
+
135
+ </script>