@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.
- package/LICENSE +21 -0
- package/e2e/codecept.conf.js +60 -0
- package/e2e/connectEmailCode.test.js +61 -0
- package/e2e/connectEmailLink.test.js +60 -0
- package/e2e/delete.test.js +44 -0
- package/e2e/disconnectEmail.test.js +42 -0
- package/e2e/resetPasswordWithEmailCode.test.js +62 -0
- package/e2e/resetPasswordWithEmailLink.test.js +62 -0
- package/e2e/setPassword.test.js +70 -0
- package/e2e/signInEmailCode.test.js +52 -0
- package/e2e/signInEmailLink.test.js +52 -0
- package/e2e/signInEmailPassword.test.js +47 -0
- package/e2e/signOut.test.js +41 -0
- package/e2e/signUpEmailCode.test.js +41 -0
- package/e2e/signUpEmailLink.test.js +41 -0
- package/e2e/steps.d.ts +12 -0
- package/e2e/steps_file.js +85 -0
- package/front/assets/images/empty-photo.svg +38 -0
- package/front/assets/images/empty-user-photo.svg +33 -0
- package/front/assets/images/logo.svg +34 -0
- package/front/index.html +11 -0
- package/front/public/favicon.ico +0 -0
- package/front/public/images/empty-photo.svg +38 -0
- package/front/public/images/empty-user-photo.svg +33 -0
- package/front/public/images/logo.svg +34 -0
- package/front/public/images/logo128.png +0 -0
- package/front/src/App.vue +93 -0
- package/front/src/CodeEditor.vue +92 -0
- package/front/src/Data.vue +74 -0
- package/front/src/DataRangeView.vue +58 -0
- package/front/src/DataView.vue +51 -0
- package/front/src/Database.vue +364 -0
- package/front/src/DatabaseAdmin.vue +135 -0
- package/front/src/Databases.vue +110 -0
- package/front/src/NavBar.vue +105 -0
- package/front/src/ObjectEditor.vue +143 -0
- package/front/src/Page.vue +39 -0
- package/front/src/PathEditor.vue +210 -0
- package/front/src/dbSugar.js +75 -0
- package/front/src/entry-client.js +24 -0
- package/front/src/entry-server.js +59 -0
- package/front/src/isClientSide.js +3 -0
- package/front/src/main.js +61 -0
- package/front/src/path.js +68 -0
- package/front/src/router.js +42 -0
- package/front/src/routes.js +31 -0
- package/front/vite.config.js +107 -0
- package/package.json +71 -0
- package/server/init.js +16 -0
- 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>
|