@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,110 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="surface-card p-4 shadow-2 border-round w-full">
|
|
3
|
+
<ConfirmPopup v-if="isMounted"></ConfirmPopup>
|
|
4
|
+
<Toast v-if="isMounted"></Toast>
|
|
5
|
+
|
|
6
|
+
<div class="text-center mb-5">
|
|
7
|
+
<div class="text-900 text-3xl font-medium mb-3">Databases</div>
|
|
8
|
+
</div>
|
|
9
|
+
|
|
10
|
+
<DataTable :value="databases" responsiveLayout="scroll">
|
|
11
|
+
<Column field="id" header="Database">
|
|
12
|
+
<template #body="slotProps">
|
|
13
|
+
<router-link :to="{ name: 'db:database', params: { dbName: slotProps.data.id } }">
|
|
14
|
+
{{ slotProps.data.id }}
|
|
15
|
+
</router-link>
|
|
16
|
+
</template>
|
|
17
|
+
</Column>
|
|
18
|
+
<Column field="tables" header="Tables" :headerStyle="{ 'width': '60px' }"></Column>
|
|
19
|
+
<Column field="indexes" header="Indexes" :headerStyle="{ 'width': '60px' }"></Column>
|
|
20
|
+
<Column field="logs" header="Logs" :headerStyle="{ 'width': '60px' }"></Column>
|
|
21
|
+
<Column :headerStyle="{ 'width': '30px' }">
|
|
22
|
+
<template #body="slotProps">
|
|
23
|
+
<Button @click="ev => deleteDatabase(ev, slotProps.data.id)" type="button"
|
|
24
|
+
icon="pi pi-trash" class="p-button-rounded p-button-danger" />
|
|
25
|
+
</template>
|
|
26
|
+
</Column>
|
|
27
|
+
</DataTable>
|
|
28
|
+
|
|
29
|
+
<!-- <pre>{{ JSON.stringify(databases, null, ' ') }}</pre>-->
|
|
30
|
+
|
|
31
|
+
<form class="mt-4 flex flex-row" @submit="handleNewDatabaseSubmit">
|
|
32
|
+
<InputText v-model="newDatabaseName" class="mr-2" placeholder="Database Name" />
|
|
33
|
+
<Button type="submit" class="p-button-primary" icon="pi pi-plus" label="Create Database" />
|
|
34
|
+
</form>
|
|
35
|
+
|
|
36
|
+
</div>
|
|
37
|
+
</template>
|
|
38
|
+
|
|
39
|
+
<script setup>
|
|
40
|
+
import DataTable from "primevue/datatable"
|
|
41
|
+
import Column from "primevue/column"
|
|
42
|
+
import Button from "primevue/button"
|
|
43
|
+
import InputText from "primevue/inputtext"
|
|
44
|
+
|
|
45
|
+
import ConfirmPopup from 'primevue/confirmpopup'
|
|
46
|
+
import Toast from 'primevue/toast'
|
|
47
|
+
|
|
48
|
+
const { dbApi } = defineProps({
|
|
49
|
+
dbApi: {
|
|
50
|
+
type: String,
|
|
51
|
+
default: 'serverDatabase'
|
|
52
|
+
}
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
import { ref, onMounted, onUnmounted, inject } from "vue"
|
|
56
|
+
let isMounted = ref(false)
|
|
57
|
+
onMounted(() => isMounted.value = true)
|
|
58
|
+
onUnmounted(() => isMounted.value = false)
|
|
59
|
+
|
|
60
|
+
import { api } from "@live-change/vue3-ssr"
|
|
61
|
+
const dao = api().source
|
|
62
|
+
import { live, RangeBuckets } from "@live-change/dao-vue3"
|
|
63
|
+
|
|
64
|
+
import { useToast } from 'primevue/usetoast'
|
|
65
|
+
const toast = useToast()
|
|
66
|
+
import { useConfirm } from 'primevue/useconfirm'
|
|
67
|
+
const confirm = useConfirm()
|
|
68
|
+
|
|
69
|
+
const workingZone = inject('workingZone')
|
|
70
|
+
|
|
71
|
+
function deleteDatabase(event, id) {
|
|
72
|
+
confirm.require({
|
|
73
|
+
target: event.currentTarget,
|
|
74
|
+
message: `Do you really want to delete database ${id}?`,
|
|
75
|
+
icon: 'pi pi-info-circle',
|
|
76
|
+
acceptClass: 'p-button-danger',
|
|
77
|
+
accept: async () => {
|
|
78
|
+
workingZone.addPromise('deleteDatabase', (async () => {
|
|
79
|
+
await dao.request([dbApi, 'deleteDatabase'], id)
|
|
80
|
+
})())
|
|
81
|
+
toast.add({ severity:'info', summary: `Database ${id} deleted`, life: 1500 })
|
|
82
|
+
},
|
|
83
|
+
reject: () => {
|
|
84
|
+
toast.add({ severity:'error', summary: 'Rejected', detail: 'You have rejected', life: 3000 })
|
|
85
|
+
}
|
|
86
|
+
})
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const newDatabaseName = ref("")
|
|
90
|
+
function handleNewDatabaseSubmit(event) {
|
|
91
|
+
event.preventDefault()
|
|
92
|
+
const dbName = newDatabaseName.value
|
|
93
|
+
newDatabaseName.value = ""
|
|
94
|
+
workingZone.addPromise('createDatabase', (async () => {
|
|
95
|
+
await dao.request([dbApi, 'createDatabase'], dbName)
|
|
96
|
+
toast.add({ severity:'info', summary: `Database ${dbName} created`, life: 1500 })
|
|
97
|
+
})())
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const databases = await live(dao, {
|
|
101
|
+
what: [dbApi, 'databases'],
|
|
102
|
+
more: [
|
|
103
|
+
{ to: 'tables', schema: [[ dbApi, 'tablesCount', { property: 'id' } ]] },
|
|
104
|
+
{ to: 'indexes', schema: [[ dbApi, 'indexesCount', { property: 'id' } ]] },
|
|
105
|
+
{ to: 'logs', schema: [[ dbApi, 'logsCount', { property: 'id' } ]] }
|
|
106
|
+
]
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
</script>
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="surface-overlay py-3 px-6 shadow-2 flex align-items-center justify-content-between
|
|
3
|
+
relative md:sticky top-0 z-5"
|
|
4
|
+
style="min-height: 80px" key="navbar">
|
|
5
|
+
<img src="/images/logo.svg" alt="Image" height="40" class="mr-0 lg:mr-6">
|
|
6
|
+
<a v-ripple class="cursor-pointer block lg:hidden text-700 p-ripple"
|
|
7
|
+
v-styleclass="{ selector: '@next', enterClass: 'hidden', leaveToClass: 'hidden', hideOnOutsideClick: true }">
|
|
8
|
+
<i class="pi pi-bars text-4xl"></i>
|
|
9
|
+
</a>
|
|
10
|
+
<div class="align-items-center flex-grow-1 justify-content-between hidden lg:flex absolute lg:static w-full surface-overlay left-0 top-100 z-1 shadow-2 lg:shadow-none">
|
|
11
|
+
<ul class="list-none p-0 m-0 flex lg:align-items-center select-none flex-column lg:flex-row">
|
|
12
|
+
<li>
|
|
13
|
+
<a v-ripple class="flex px-6 p-3 lg:px-3 lg:py-2 align-items-center text-600 hover:text-900 hover:surface-100 font-medium border-round cursor-pointer transition-colors transition-duration-150 p-ripple">
|
|
14
|
+
<i class="pi pi-home mr-2"></i>
|
|
15
|
+
<span>Home</span>
|
|
16
|
+
</a>
|
|
17
|
+
</li>
|
|
18
|
+
<li class="lg:relative">
|
|
19
|
+
<a v-ripple class="flex px-6 p-3 lg:px-3 lg:py-2 align-items-center text-600 hover:text-900 hover:surface-100 font-medium border-round cursor-pointer transition-colors transition-duration-150 p-ripple"
|
|
20
|
+
v-styleclass="{ selector: '@next', enterClass: 'hidden', enterActiveClass: 'scalein', leaveToClass: 'hidden', leaveActiveClass: 'fadeout', hideOnOutsideClick: true }">
|
|
21
|
+
<i class="pi pi-users mr-2"></i>
|
|
22
|
+
<span>Customers</span>
|
|
23
|
+
<i class="pi pi-angle-down ml-auto lg:ml-3"></i>
|
|
24
|
+
</a>
|
|
25
|
+
<ul class="list-none py-3 px-6 m-0 lg:px-0 lg:py-0 border-round shadow-0 lg:shadow-2 lg:border-1 border-50 lg:absolute surface-overlay hidden origin-top w-full lg:w-15rem cursor-pointer">
|
|
26
|
+
<li>
|
|
27
|
+
<a v-ripple class="flex p-3 align-items-center text-600 hover:text-900 hover:surface-100 transition-colors transition-duration-150 p-ripple">
|
|
28
|
+
<i class="pi pi-user-plus mr-2"></i>
|
|
29
|
+
<span class="font-medium">Add New</span>
|
|
30
|
+
</a>
|
|
31
|
+
</li>
|
|
32
|
+
<li class="relative">
|
|
33
|
+
<a v-ripple class="flex p-3 align-items-center text-600 hover:text-900 hover:surface-100 transition-colors transition-duration-150 p-ripple"
|
|
34
|
+
v-styleclass="{ selector: '@next', enterClass: 'hidden', enterActiveClass: 'scalein', leaveToClass: 'hidden', leaveActiveClass: 'fadeout', hideOnOutsideClick: true }">
|
|
35
|
+
<i class="pi pi-search mr-2"></i>
|
|
36
|
+
<span class="font-medium">Search</span>
|
|
37
|
+
<i class="pi pi-angle-down ml-auto lg:-rotate-90"></i>
|
|
38
|
+
</a>
|
|
39
|
+
<ul class="list-none py-3 pl-3 m-0 lg:px-0 lg:py-0 border-round shadow-0 lg:shadow-2 lg:border-1 border-50 lg:absolute surface-overlay hidden origin-top w-full lg:w-15rem cursor-pointer left-100 top-0">
|
|
40
|
+
<li>
|
|
41
|
+
<a v-ripple class="flex p-3 align-items-center text-600 hover:text-900 hover:surface-100 transition-colors transition-duration-150 p-ripple">
|
|
42
|
+
<i class="pi pi-shopping-cart mr-2"></i>
|
|
43
|
+
<span class="font-medium">Purchases</span>
|
|
44
|
+
</a>
|
|
45
|
+
</li>
|
|
46
|
+
<li class="relative">
|
|
47
|
+
<a v-ripple class="flex p-3 align-items-center text-600 hover:text-900 hover:surface-100 transition-colors transition-duration-150 p-ripple">
|
|
48
|
+
<i class="pi pi-comments mr-2"></i>
|
|
49
|
+
<span class="font-medium">Messages</span>
|
|
50
|
+
</a>
|
|
51
|
+
</li>
|
|
52
|
+
</ul>
|
|
53
|
+
</li>
|
|
54
|
+
</ul>
|
|
55
|
+
</li>
|
|
56
|
+
<li>
|
|
57
|
+
<a v-ripple class="flex px-6 p-3 lg:px-3 lg:py-2 align-items-center text-600 hover:text-900 hover:surface-100 font-medium border-round cursor-pointer transition-colors transition-duration-150 p-ripple">
|
|
58
|
+
<i class="pi pi-calendar mr-2"></i>
|
|
59
|
+
<span>Calendar</span>
|
|
60
|
+
</a>
|
|
61
|
+
</li>
|
|
62
|
+
<li>
|
|
63
|
+
<a v-ripple class="flex px-6 p-3 lg:px-3 lg:py-2 align-items-center text-600 hover:text-900 hover:surface-100 font-medium border-round cursor-pointer transition-colors transition-duration-150 p-ripple">
|
|
64
|
+
<i class="pi pi-chart-line mr-2"></i>
|
|
65
|
+
<span>Stats</span>
|
|
66
|
+
</a>
|
|
67
|
+
</li>
|
|
68
|
+
</ul>
|
|
69
|
+
<ul class="list-none p-0 m-0 flex lg:align-items-center select-none flex-column lg:flex-row border-top-1 surface-border lg:border-top-none">
|
|
70
|
+
<li>
|
|
71
|
+
<a v-ripple class="flex px-6 p-3 lg:px-3 lg:py-2 align-items-center text-600 hover:text-900 hover:surface-100 font-medium border-round cursor-pointer transition-colors transition-duration-150 p-ripple">
|
|
72
|
+
<i class="pi pi-inbox text-base lg:text-2xl mr-2 lg:mr-0"></i>
|
|
73
|
+
<span class="block lg:hidden font-medium">Inbox</span>
|
|
74
|
+
</a>
|
|
75
|
+
</li>
|
|
76
|
+
<li>
|
|
77
|
+
<a v-ripple class="flex px-6 p-3 lg:px-3 lg:py-3 align-items-center text-600 hover:text-900 hover:surface-100
|
|
78
|
+
font-medium border-round cursor-pointer transition-colors transition-duration-150 p-ripple">
|
|
79
|
+
<i class="pi pi-bell text-base lg:text-2xl mr-2 lg:mr-0 p-overlay-badge">
|
|
80
|
+
<Badge value="2"></Badge>
|
|
81
|
+
</i>
|
|
82
|
+
<span class="block lg:hidden font-medium">Notifications</span>
|
|
83
|
+
</a>
|
|
84
|
+
</li>
|
|
85
|
+
<li class="border-top-1 surface-border lg:border-top-none">
|
|
86
|
+
<a v-ripple class="flex px-6 p-3 lg:px-3 lg:py-2 align-items-center hover:surface-100 font-medium border-round cursor-pointer transition-colors transition-duration-150 p-ripple">
|
|
87
|
+
<img src="/images/empty-user-photo.svg" class="mr-3 lg:mr-0 border-circle" style="width: 28px; height: 28px"/>
|
|
88
|
+
<div class="block lg:hidden">
|
|
89
|
+
<div class="text-900 font-medium">Josephine Lillard</div>
|
|
90
|
+
<span class="text-600 font-medium text-sm">Marketing Specialist</span>
|
|
91
|
+
</div>
|
|
92
|
+
</a>
|
|
93
|
+
</li>
|
|
94
|
+
</ul>
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
97
|
+
</template>
|
|
98
|
+
|
|
99
|
+
<script setup>
|
|
100
|
+
import Badge from "primevue/badge"
|
|
101
|
+
</script>
|
|
102
|
+
|
|
103
|
+
<style scoped>
|
|
104
|
+
|
|
105
|
+
</style>
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="flex flex-row">
|
|
3
|
+
<div class="flex-grow-1 pt-1 pb-1">
|
|
4
|
+
<CodeEditor :readOnly="readOnly" :initialData="initialData" @result="result => handleEditResult(result)"
|
|
5
|
+
:ref="el => editorElementFound(el)" />
|
|
6
|
+
</div>
|
|
7
|
+
<div class="flex flex-column justify-content-end align-items-center">
|
|
8
|
+
<Button v-if="edited" @click="ev => resetObject(ev)"
|
|
9
|
+
icon="pi pi-refresh" class="p-button-rounded p-button-warning m-1 mr-2 mt-2" />
|
|
10
|
+
<Button v-if="edited" @click="ev => updateObject(ev)"
|
|
11
|
+
icon="pi pi-save" class="p-button-rounded p-button-primary m-1 mr-2" />
|
|
12
|
+
<Button @click="ev => deleteObject(ev)"
|
|
13
|
+
icon="pi pi-trash" class="p-button-rounded p-button-danger m-1 mr-2 mb-2" />
|
|
14
|
+
</div>
|
|
15
|
+
</div>
|
|
16
|
+
</template>
|
|
17
|
+
|
|
18
|
+
<script setup>
|
|
19
|
+
|
|
20
|
+
import Button from "primevue/button"
|
|
21
|
+
import CodeEditor from "./CodeEditor.vue";
|
|
22
|
+
|
|
23
|
+
import { useToast } from 'primevue/usetoast'
|
|
24
|
+
const toast = useToast()
|
|
25
|
+
import { useConfirm } from 'primevue/useconfirm'
|
|
26
|
+
const confirm = useConfirm()
|
|
27
|
+
|
|
28
|
+
import { ref, onMounted, onUnmounted, inject, computed, watch } from "vue"
|
|
29
|
+
let isMounted = ref(false)
|
|
30
|
+
onMounted(() => isMounted.value = true)
|
|
31
|
+
onUnmounted(() => isMounted.value = false)
|
|
32
|
+
|
|
33
|
+
const workingZone = inject('workingZone')
|
|
34
|
+
|
|
35
|
+
const props = defineProps({
|
|
36
|
+
dbApi: {
|
|
37
|
+
type: String
|
|
38
|
+
},
|
|
39
|
+
currentData: {
|
|
40
|
+
type: String
|
|
41
|
+
},
|
|
42
|
+
readOnly: {
|
|
43
|
+
type: Boolean,
|
|
44
|
+
default: false
|
|
45
|
+
},
|
|
46
|
+
write: {
|
|
47
|
+
type: Function,
|
|
48
|
+
default: null
|
|
49
|
+
},
|
|
50
|
+
remove: {
|
|
51
|
+
type: Function,
|
|
52
|
+
default: null
|
|
53
|
+
}
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
const { dbApi, readOnly, write, remove } = props
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
const initialData = ref(JSON.parse(props.currentData))
|
|
60
|
+
const editedData = ref(JSON.parse(props.currentData))
|
|
61
|
+
|
|
62
|
+
const edited = computed(() => JSON.stringify(initialData.value) != JSON.stringify(editedData.value))
|
|
63
|
+
|
|
64
|
+
watch(() => props.currentData, currentData => {
|
|
65
|
+
const refresh = !edited.value
|
|
66
|
+
initialData.value = JSON.parse(currentData)
|
|
67
|
+
if(refresh) {
|
|
68
|
+
editedData.value = JSON.parse(currentData)
|
|
69
|
+
setTimeout(() => {
|
|
70
|
+
codeEditor.value.reset()
|
|
71
|
+
})
|
|
72
|
+
}
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
function handleEditResult(result) {
|
|
76
|
+
editedData.value = result.data
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
import { api } from "@live-change/vue3-ssr"
|
|
80
|
+
const dao = api().source
|
|
81
|
+
|
|
82
|
+
const codeEditor = ref()
|
|
83
|
+
function editorElementFound(element) {
|
|
84
|
+
codeEditor.value = element
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
import { dbRequestSugar } from "./dbSugar.js";
|
|
88
|
+
|
|
89
|
+
function resetObject(event) {
|
|
90
|
+
codeEditor.value.reset()
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function deleteObject(event) {
|
|
94
|
+
const removeRequest = remove({ object: initialData.value }, dbRequestSugar)
|
|
95
|
+
const requestInfo =
|
|
96
|
+
`${removeRequest[0].join('.')}(${removeRequest.slice(1).map(v => JSON.stringify(v)).join(', ')})`
|
|
97
|
+
const id = initialData.value.id
|
|
98
|
+
confirm.require({
|
|
99
|
+
target: event.currentTarget,
|
|
100
|
+
message: `Do you want to delete object ${id}\n`,
|
|
101
|
+
//message: `${requestInfo} ?`,
|
|
102
|
+
icon: 'pi pi-trash',
|
|
103
|
+
acceptClass: 'p-button-danger',
|
|
104
|
+
accept: async () => {
|
|
105
|
+
workingZone.addPromise('deleteObject', (async () => {
|
|
106
|
+
await dao.request([ dbApi, ...removeRequest[0] ], ...(removeRequest.slice(1)))
|
|
107
|
+
toast.add({ severity:'info', summary: `Object ${id} deleted`, life: 1500 })
|
|
108
|
+
})())
|
|
109
|
+
},
|
|
110
|
+
reject: () => {
|
|
111
|
+
toast.add({ severity:'error', summary: 'Rejected', detail: 'You have rejected', life: 3000 })
|
|
112
|
+
}
|
|
113
|
+
})
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function updateObject(event) {
|
|
117
|
+
const updateRequest = write({ object: editedData.value }, dbRequestSugar)
|
|
118
|
+
const requestInfo =
|
|
119
|
+
`${updateRequest[0].join('.')}(${updateRequest.slice(1).map(v => JSON.stringify(v)).join(', ')})`
|
|
120
|
+
const id = editedData.value.id
|
|
121
|
+
confirm.require({
|
|
122
|
+
target: event.currentTarget,
|
|
123
|
+
message: `Do you want to update object ${id}\n`,
|
|
124
|
+
//message: `\n${requestInfo} ?`,
|
|
125
|
+
icon: 'pi pi-trash',
|
|
126
|
+
acceptClass: 'p-button-danger',
|
|
127
|
+
accept: async () => {
|
|
128
|
+
workingZone.addPromise('updateObject', (async () => {
|
|
129
|
+
await dao.request([ dbApi, ...updateRequest[0] ], ...(updateRequest.slice(1)))
|
|
130
|
+
toast.add({ severity:'info', summary: `Object ${id} updated`, life: 1500 })
|
|
131
|
+
})())
|
|
132
|
+
},
|
|
133
|
+
reject: () => {
|
|
134
|
+
toast.add({ severity:'error', summary: 'Rejected', detail: 'You have rejected', life: 3000 })
|
|
135
|
+
}
|
|
136
|
+
})
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
</script>
|
|
140
|
+
|
|
141
|
+
<style scoped>
|
|
142
|
+
|
|
143
|
+
</style>
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="min-h-screen flex flex-column surface-ground">
|
|
3
|
+
<NavBar v-if="!navBarHidden"></NavBar>
|
|
4
|
+
|
|
5
|
+
<div class="relative h-0 w-full">
|
|
6
|
+
<ProgressBar v-if="loading || working" mode="indeterminate" class="absolute top-0 w-full" style="height: .2em" />
|
|
7
|
+
</div>
|
|
8
|
+
<div v-if="pageType == 'simple'" class="p-5 flex flex-column flex-auto align-items-center">
|
|
9
|
+
<slot></slot>
|
|
10
|
+
</div>
|
|
11
|
+
<div v-if="pageType == 'wide'">
|
|
12
|
+
<slot></slot>
|
|
13
|
+
</div>
|
|
14
|
+
</div>
|
|
15
|
+
</template>
|
|
16
|
+
|
|
17
|
+
<script setup>
|
|
18
|
+
import ProgressBar from "primevue/progressbar"
|
|
19
|
+
import NavBar from "./NavBar.vue"
|
|
20
|
+
|
|
21
|
+
const { working, loading } = defineProps({
|
|
22
|
+
working: {
|
|
23
|
+
type: Boolean
|
|
24
|
+
},
|
|
25
|
+
loading: {
|
|
26
|
+
type: Boolean
|
|
27
|
+
}
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
console.log("SETUP PAGE!!!")
|
|
31
|
+
|
|
32
|
+
import { computed } from 'vue'
|
|
33
|
+
import { useRoute } from 'vue-router'
|
|
34
|
+
const route = useRoute()
|
|
35
|
+
|
|
36
|
+
const pageType = computed(() => route.meta.pageType ?? 'simple' )
|
|
37
|
+
const navBarHidden = computed(() => route.meta.noNavBar )
|
|
38
|
+
|
|
39
|
+
</script>
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="surface-0 shadow-1 w-full p-2">
|
|
3
|
+
<div class="flex flex-row flex-wrap w-full">
|
|
4
|
+
<div class="col-2 text-right p-1">read:</div>
|
|
5
|
+
<div class="col-10 p-1">
|
|
6
|
+
<CodeEditor :initialCode="path.read"
|
|
7
|
+
@result="result => handleReadChange(result)" :env="env" :dbSugar="dbViewSugar" />
|
|
8
|
+
</div>
|
|
9
|
+
</div>
|
|
10
|
+
<div class="flex flex-row flex-wrap w-full">
|
|
11
|
+
<div class="col-2 text-right p-1">write:</div>
|
|
12
|
+
<div class="col-10 p-1">
|
|
13
|
+
<CodeEditor :initialCode="path.write"
|
|
14
|
+
@result="result => handleWriteChange(result)" :env="env" :dbSugar="dbRequestSugar" />
|
|
15
|
+
</div>
|
|
16
|
+
</div>
|
|
17
|
+
<div class="flex flex-row flex-wrap w-full">
|
|
18
|
+
<div class="col-2 text-right p-1">remove:</div>
|
|
19
|
+
<div class="col-10 p-1">
|
|
20
|
+
<CodeEditor :initialCode="path.remove"
|
|
21
|
+
@result="result => handleRemoveChange(result)" :env="env" :dbSugar="dbRequestSugar" />
|
|
22
|
+
</div>
|
|
23
|
+
</div>
|
|
24
|
+
<div v-for="field in path.params" class="flex flex-row flex-wrap w-full">
|
|
25
|
+
<div class="col-2 text-right p-1">{{ field[0] }} = </div>
|
|
26
|
+
<div class="col-10 p-1">
|
|
27
|
+
<CodeEditor :initialCode="field[1]"
|
|
28
|
+
@result="result => handleParamChange(field[0], result)" />
|
|
29
|
+
</div>
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
<div v-if="false" class="surface-0 shadow-1 w-full">
|
|
33
|
+
<div class="flex flex-row flex-wrap w-full" >
|
|
34
|
+
<div class="col-2 text-right">Compiled:</div>
|
|
35
|
+
<div class="col-10" v-if="!readCompiled.error" v-html="highlightedObject(readCompiled.example)" />
|
|
36
|
+
<div class="col-10 p-error" v-else>{{ readCompiled.error }}</div>
|
|
37
|
+
</div>
|
|
38
|
+
<div class="flex flex-row flex-wrap w-full">
|
|
39
|
+
<div class="col-2 text-right"></div>
|
|
40
|
+
<div class="col-10" v-if="!writeCompiled.error" v-html="highlightedObject(writeCompiled.example)" />
|
|
41
|
+
<div class="col-10 p-error" v-else>{{ writeCompiled.error }}</div>
|
|
42
|
+
</div>
|
|
43
|
+
<div class="flex flex-row flex-wrap w-full">
|
|
44
|
+
<div class="col-2 text-right"></div>
|
|
45
|
+
<div class="col-10" v-if="!removeCompiled.error" v-html="highlightedObject(removeCompiled.example)" />
|
|
46
|
+
<div class="col-10 p-error" v-else>{{ removeCompiled.error }}</div>
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
</template>
|
|
50
|
+
|
|
51
|
+
<script setup>
|
|
52
|
+
|
|
53
|
+
import CodeEditor from "./CodeEditor.vue"
|
|
54
|
+
|
|
55
|
+
import { stringify } from "javascript-stringify"
|
|
56
|
+
import * as Prism from 'prismjs/components/prism-core'
|
|
57
|
+
|
|
58
|
+
function highlightedObject(obj) {
|
|
59
|
+
const code = stringify(obj)
|
|
60
|
+
return Prism.highlight(code, Prism.languages.js, "js")
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
import { dbRequestSugar, dbViewSugar } from "./dbSugar.js";
|
|
64
|
+
|
|
65
|
+
import { ref, reactive, computed, watch } from "vue"
|
|
66
|
+
|
|
67
|
+
const { modelValue } = defineProps({
|
|
68
|
+
modelValue: {
|
|
69
|
+
type: Object,
|
|
70
|
+
required: true
|
|
71
|
+
}
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
const emit = defineEmits(['update:modelValue', 'update:read', 'update:write', 'update:remove'])
|
|
75
|
+
|
|
76
|
+
const path = reactive(modelValue)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
function handleReadChange(result) {
|
|
80
|
+
if(result.error) {
|
|
81
|
+
} else {
|
|
82
|
+
path.read = result.code
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function handleWriteChange(result) {
|
|
87
|
+
if(result.error) {
|
|
88
|
+
} else {
|
|
89
|
+
path.write = result.code
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function handleRemoveChange(result) {
|
|
94
|
+
if(result.error) {
|
|
95
|
+
} else {
|
|
96
|
+
path.remove = result.code
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function handleParamChange(param, result) {
|
|
101
|
+
if(result.error) {
|
|
102
|
+
} else {
|
|
103
|
+
path.params.find(p => p[0] == param)[1] = result.code
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
import { extractParams, compilePath } from "./path.js"
|
|
108
|
+
|
|
109
|
+
const codeParams = computed( () => {
|
|
110
|
+
let allParams = []
|
|
111
|
+
allParams.push(...extractParams(path.read))
|
|
112
|
+
allParams.push(...extractParams(path.write))
|
|
113
|
+
allParams.push(...extractParams(path.remove))
|
|
114
|
+
for(const field of path.params) {
|
|
115
|
+
console.log("FD", field, "=>", extractParams(field[1]))
|
|
116
|
+
allParams.push(...extractParams(field[1]))
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const paramsSet = new Set(allParams)
|
|
120
|
+
paramsSet.delete('range')
|
|
121
|
+
paramsSet.delete('object')
|
|
122
|
+
return Array.from(paramsSet)
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
const output = computed( () => {
|
|
126
|
+
return {
|
|
127
|
+
read: path.read,
|
|
128
|
+
write: path.write,
|
|
129
|
+
remove: path.remove,
|
|
130
|
+
params: path.params.map(([k, v]) => [k, v])
|
|
131
|
+
}
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
watch(() => codeParams.value, params => {
|
|
135
|
+
for(const newParam of params) {
|
|
136
|
+
if(!path.params.find(p => p[0] == newParam)) {
|
|
137
|
+
path.params.push([newParam, ''])
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
const removedParams = []
|
|
141
|
+
for(const oldParam of path.params) {
|
|
142
|
+
if(!params.find(p => p == oldParam[0]) && !oldParam[1]) {
|
|
143
|
+
removedParams.push(oldParam[0])
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
if(removedParams.length > 0) {
|
|
147
|
+
path.params = path.params.filter(p => !removedParams.includes(p[0]))
|
|
148
|
+
}
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
watch(() => output.value, value => {
|
|
152
|
+
console.log("EMIT OUTPUT!")
|
|
153
|
+
emit('update:modelValue', output.value)
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
const readCompiled = computed(() => {
|
|
157
|
+
try {
|
|
158
|
+
const compiled = compilePath(path.read, path.params, ['range'])
|
|
159
|
+
return { ...compiled, example: compiled.result({ range: { } }, dbViewSugar) }
|
|
160
|
+
} catch (error) {
|
|
161
|
+
console.error("READ CODE ERROR", error)
|
|
162
|
+
return {
|
|
163
|
+
error: error.message
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
const writeCompiled = computed(() => {
|
|
169
|
+
try {
|
|
170
|
+
const compiled = compilePath(path.write, path.params, ['object'])
|
|
171
|
+
return { ...compiled, example: compiled.result({ object: { id: 'object' } }, dbRequestSugar) }
|
|
172
|
+
} catch (error) {
|
|
173
|
+
console.error("WRITE CODE ERROR", error)
|
|
174
|
+
return {
|
|
175
|
+
error: error.message
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
const removeCompiled = computed(() => {
|
|
181
|
+
try {
|
|
182
|
+
const compiled = compilePath(path.remove, path.params, ['object'])
|
|
183
|
+
return { ...compiled, example: compiled.result({ object: { id: 'object' } }, dbRequestSugar) }
|
|
184
|
+
} catch (error) {
|
|
185
|
+
console.error("DELETE CODE ERROR", error)
|
|
186
|
+
return {
|
|
187
|
+
error: error.message
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
watch(() => readCompiled.value, value => {
|
|
193
|
+
if(!value.error) emit('update:read', value)
|
|
194
|
+
})
|
|
195
|
+
watch(() => writeCompiled.value, value => {
|
|
196
|
+
if(!value.error) emit('update:write', value)
|
|
197
|
+
})
|
|
198
|
+
watch(() => removeCompiled.value, value => {
|
|
199
|
+
if(!value.error) emit('update:remove', value)
|
|
200
|
+
})
|
|
201
|
+
emit('update:read', readCompiled.value)
|
|
202
|
+
emit('update:write', writeCompiled.value)
|
|
203
|
+
emit('update:remove', removeCompiled.value)
|
|
204
|
+
|
|
205
|
+
const env = {
|
|
206
|
+
object: {},
|
|
207
|
+
range: {}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
</script>
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
|
|
2
|
+
const dbRequests = [
|
|
3
|
+
'createDatabase',
|
|
4
|
+
'deleteDatabase',
|
|
5
|
+
'clearDatabaseOpLogs',
|
|
6
|
+
'clearTableOpLog',
|
|
7
|
+
'clearIndexOpLog',
|
|
8
|
+
'createTable',
|
|
9
|
+
'deleteTable',
|
|
10
|
+
'renameTable',
|
|
11
|
+
'createIndex',
|
|
12
|
+
'deleteIndex',
|
|
13
|
+
'renameIndex',
|
|
14
|
+
'createLog',
|
|
15
|
+
'deleteLog',
|
|
16
|
+
'renameLog',
|
|
17
|
+
'put',
|
|
18
|
+
'delete',
|
|
19
|
+
'update',
|
|
20
|
+
'putLog',
|
|
21
|
+
'putOldLog',
|
|
22
|
+
'query'
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
const dbViews = [
|
|
26
|
+
'databasesList',
|
|
27
|
+
'databases',
|
|
28
|
+
'databaseConfig',
|
|
29
|
+
'tablesList',
|
|
30
|
+
'indexesList',
|
|
31
|
+
'logsList',
|
|
32
|
+
'tableCount',
|
|
33
|
+
'indexesCount',
|
|
34
|
+
'logsCount',
|
|
35
|
+
'tables',
|
|
36
|
+
'indexes',
|
|
37
|
+
'logs',
|
|
38
|
+
'tableConfig',
|
|
39
|
+
'indexConfig',
|
|
40
|
+
'indexCode',
|
|
41
|
+
'logConfig',
|
|
42
|
+
'tableObject',
|
|
43
|
+
'tableRange',
|
|
44
|
+
'tableCount',
|
|
45
|
+
'tableOpLogObject',
|
|
46
|
+
'tableOpLogRange',
|
|
47
|
+
'tableOpLogCount',
|
|
48
|
+
'indexObject',
|
|
49
|
+
'indexRange',
|
|
50
|
+
'indexOpLogObject',
|
|
51
|
+
'indexOpLogRange',
|
|
52
|
+
'indexOpLogCount',
|
|
53
|
+
'logObject',
|
|
54
|
+
'logRange',
|
|
55
|
+
'logCount',
|
|
56
|
+
'query',
|
|
57
|
+
'queryObject'
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
export { dbViews, dbRequests }
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
const dbRequestSugar = {}
|
|
64
|
+
|
|
65
|
+
for(const request of dbRequests) {
|
|
66
|
+
dbRequestSugar[request] = (...args) => [[request], ...args]
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const dbViewSugar = {}
|
|
70
|
+
|
|
71
|
+
for(const view of dbViews) {
|
|
72
|
+
dbViewSugar[view] = (...args) => [view, ...args]
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export { dbRequestSugar, dbViewSugar }
|