You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@skywalking.apache.org by ha...@apache.org on 2023/09/12 12:46:05 UTC
[skywalking-banyandb] branch main updated: Add Property CRUD Function (#328)
This is an automated email from the ASF dual-hosted git repository.
hanahmily pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/skywalking-banyandb.git
The following commit(s) were added to refs/heads/main by this push:
new b034d20e Add Property CRUD Function (#328)
b034d20e is described below
commit b034d20e00e77a26116a8852cd847f2a771cdbf1
Author: Wu ChuSheng <a1...@gmail.com>
AuthorDate: Tue Sep 12 20:45:59 2023 +0800
Add Property CRUD Function (#328)
* Add Property Aside
* Add Property Table
* Add Property CRUD
---------
Co-authored-by: Gao Hongtao <ha...@gmail.com>
---
ui/src/api/index.js | 36 ++++
ui/src/components/Aside/index.vue | 79 ++++++++-
ui/src/components/Editor/index.vue | 6 +-
ui/src/components/IndexRule/Editor.vue | 4 +-
ui/src/components/IndexRule/index.vue | 4 +-
ui/src/components/IndexRuleBinding/Editor.vue | 4 +-
ui/src/components/IndexRuleBinding/index.vue | 4 +-
ui/src/components/MeasureEditor/index.vue | 6 +-
ui/src/components/Property/PropertyEditror.vue | 230 +++++++++++++++++++++++++
ui/src/components/Property/PropertyRead.vue | 189 ++++++++++++++++++++
ui/src/components/Property/TagEditor.vue | 95 ++++++++++
ui/src/components/Read/index.vue | 6 +-
ui/src/components/TopNav/index.vue | 50 +++---
ui/src/router/index.js | 18 +-
ui/src/views/Property/index.vue | 40 ++++-
15 files changed, 711 insertions(+), 60 deletions(-)
diff --git a/ui/src/api/index.js b/ui/src/api/index.js
index 059782c9..c491405c 100644
--- a/ui/src/api/index.js
+++ b/ui/src/api/index.js
@@ -139,4 +139,40 @@ export function deleteIndexRuleOrIndexRuleBinding(type, group, name) {
url: `/api/v1/${type}/schema/${group}/${name}`,
method: 'delete'
})
+}
+
+export function getPropertyByGroup(group) {
+ return request({
+ url: `/api/v1/property/lists/${group}`,
+ method: 'get'
+ })
+}
+
+export function getPropertyList(group, name, ids, tags) {
+ return request({
+ url: `/api/v1/property/lists/${group}/${name}/${ids}/${tags}`,
+ method: 'get'
+ })
+}
+
+export function getPropertyDetail(group, name, id, tags) {
+ return request({
+ url: `/api/v1/property/${group}/${name}/${id}/${tags}`,
+ method: 'get'
+ })
+}
+
+export function deleteProperty(group, name, id, tags) {
+ return request({
+ url: `/api/v1/property/${group}/${name}/${id}/${tags}`,
+ method: 'delete'
+ })
+}
+
+export function applyProperty(group, name, id, data) {
+ return request({
+ url: `/api/v1/property/${group}/${name}/${id}`,
+ method: 'put',
+ data: data
+ })
}
\ No newline at end of file
diff --git a/ui/src/components/Aside/index.vue b/ui/src/components/Aside/index.vue
index 581e1c44..afd3c9e8 100644
--- a/ui/src/components/Aside/index.vue
+++ b/ui/src/components/Aside/index.vue
@@ -19,12 +19,13 @@
<script setup>
import RigheMenu from '@/components/RightMenu/index.vue'
-import { deleteIndexRuleOrIndexRuleBinding, getindexRuleList, getindexRuleBindingList, getGroupList, getStreamOrMeasureList, deleteStreamOrMeasure, deleteGroup, createGroup, editGroup, createResources } from '@/api/index'
+import { deleteIndexRuleOrIndexRuleBinding, getindexRuleList, getindexRuleBindingList, getGroupList, getStreamOrMeasureList, deleteStreamOrMeasure, deleteGroup, createGroup, editGroup } from '@/api/index'
import { ElMessage, ElMessageBox } from 'element-plus'
import { watch, getCurrentInstance } from "@vue/runtime-core"
import { useRouter, useRoute } from 'vue-router'
import { ref, reactive } from 'vue'
import { Search } from '@element-plus/icons-vue'
+
const router = useRouter()
const route = useRoute()
const { proxy } = getCurrentInstance()
@@ -269,6 +270,11 @@ function getGroupLists() {
let group = res.data.group
data.groupLists = group
deleteOtherGroup()
+ if (props.type == 'property') {
+ data.showSearch = true
+ $loadingClose()
+ return
+ }
let promise = data.groupLists.map((item) => {
let type = props.type
let name = item.metadata.name
@@ -335,8 +341,13 @@ function getGroupLists() {
})
}
function deleteOtherGroup() {
+ let flag = {
+ 'CATALOG_MEASURE': 'measure',
+ 'CATALOG_STREAM': 'stream',
+ 'CATALOG_UNSPECIFIED': 'property'
+ }
for (let i = 0; i < data.groupLists.length; i++) {
- let type = data.groupLists[i].catalog == 'CATALOG_MEASURE' ? 'measure' : 'stream'
+ let type = flag[data.groupLists[i].catalog]
if (type !== props.type) {
data.groupLists.splice(i, 1)
i--
@@ -345,6 +356,25 @@ function deleteOtherGroup() {
}
// to resources
function openResources(index, childIndex) {
+ if (props.type == 'property') {
+ const group = data.groupLists[index].metadata.name
+ const route = {
+ name: 'property',
+ params: {
+ group: group,
+ operator: 'read',
+ type: props.type
+ }
+ }
+ router.push(route)
+ const add = {
+ label: group,
+ type: 'Read',
+ route
+ }
+ data.active = `${group}`
+ return $bus.emit('AddTabs', add)
+ }
const group = data.groupLists[index].children[childIndex].metadata.group
const name = data.groupLists[index].children[childIndex].metadata.name
const route = {
@@ -403,11 +433,13 @@ function shrinkDown(e) {
function rightClickGroup(e, index) {
data.rightMenuList = groupMenu
if (props.type == 'measure') {
- data.rightMenuList.push({
+ const rightMenuList = JSON.parse(JSON.stringify(groupMenu))
+ rightMenuList.push({
icon: "el-icon-document",
name: "new resources",
id: "create resources"
})
+ data.rightMenuList = rightMenuList
}
data.clickIndex = index
data.rightClickType = 'group'
@@ -815,7 +847,10 @@ $bus.on('changeIsCollapse', (obj) => {
emit('setWidth', obj.width)
})
$bus.on('changeAside', (obj) => {
- data.activeMenu = `${obj.group}-${obj.name}`
+ if (obj.group && obj.name)
+ data.activeMenu = `${obj.group}-${obj.name}`
+ else
+ data.activeMenu = `${obj.group}`
})
$bus.on('resetAside', () => {
data.activeMenu = ''
@@ -826,7 +861,6 @@ $bus.on('resetAside', () => {
$bus.on('refreshAside', () => {
getGroupLists()
})
-
getGroupLists()
initActiveMenu()
@@ -836,12 +870,23 @@ initActiveMenu()
<template>
<div style="display: flex; flex-direction: column; width: 100%;">
<div class="size flex" style="display: flex; flex-direction: column; width: 100%;">
- <el-input v-if="data.showSearch && props.type == 'measure'" class="aside-search" v-model="data.search"
+ <el-input v-if="data.showSearch && props.type != 'stream'" class="aside-search" v-model="data.search"
placeholder="Search" :prefix-icon="Search" clearable />
<el-menu v-if="data.groupLists.length > 0" :collapse="data.isCollapse" :default-active="data.activeMenu">
<div v-for="(item, index) in data.groupLists" :key="item.metadata.name"
@contextmenu.prevent="rightClickGroup($event, index)">
- <el-sub-menu :index="`${item.metadata.name}-${index}`">
+ <el-menu-item v-if="props.type == 'property'" @click="openResources(index)"
+ :index="`${item.metadata.name}`">
+ <template #title>
+ <el-icon>
+ <Document />
+ </el-icon>
+ <span slot="title" :title="item.metadata.name" style="width: 70%" class="text-overflow-hidden">
+ {{ item.metadata.name }}
+ </span>
+ </template>
+ </el-menu-item>
+ <el-sub-menu v-else :index="`${item.metadata.name}-${index}`">
<template #title>
<el-icon>
<Folder />
@@ -952,6 +997,25 @@ initActiveMenu()
</div>
</div>
</div>
+ <!-- <div v-if="props.type == 'property'">
+ <div v-for="(child, childIndex) in item.children" :key="child.metadata.id">
+ <div @contextmenu.prevent="rightClickResources($event, index, childIndex)">
+ <el-menu-item
+ :index="`${child.metadata.container.group}-${child.metadata.container.name}`"
+ @click="openResources(index, childIndex)">
+ <template #title>
+ <el-icon>
+ <Document />
+ </el-icon>
+ <span slot="title" :title="child.metadata.container.name" style="width: 90%"
+ class="text-overflow-hidden">
+ {{ child.metadata.container.name }}
+ </span>
+ </template>
+ </el-menu-item>
+ </div>
+ </div>
+ </div> -->
</el-sub-menu>
</div>
</el-menu>
@@ -974,6 +1038,7 @@ initActiveMenu()
<el-select v-model="data.groupForm.catalog" placeholder="please select" style="width: 100%">
<el-option label="CATALOG_STREAM" value="CATALOG_STREAM"></el-option>
<el-option label="CATALOG_MEASURE" value="CATALOG_MEASURE"></el-option>
+ <el-option label="CATALOG_UNSPECIFIED" value="CATALOG_UNSPECIFIED"></el-option>
</el-select>
</el-form-item>
<el-form-item label="shard num" :label-width="data.formLabelWidth" prop="shardNum">
diff --git a/ui/src/components/Editor/index.vue b/ui/src/components/Editor/index.vue
index ef91ed46..5c4851a3 100644
--- a/ui/src/components/Editor/index.vue
+++ b/ui/src/components/Editor/index.vue
@@ -321,9 +321,7 @@ function initData() {
</template>
<style lang="scss" scoped>
-::v-deep {
- .el-card {
- margin: 15px;
- }
+:deep(.el-card) {
+ margin: 15px;
}
</style>
\ No newline at end of file
diff --git a/ui/src/components/IndexRule/Editor.vue b/ui/src/components/IndexRule/Editor.vue
index 4e4cef35..5e0a575f 100644
--- a/ui/src/components/IndexRule/Editor.vue
+++ b/ui/src/components/IndexRule/Editor.vue
@@ -324,9 +324,7 @@ function initData() {
</template>
<style lang="scss" scoped>
-::v-deep {
- .el-card {
+:deep(.el-card) {
margin: 15px;
- }
}
</style>
\ No newline at end of file
diff --git a/ui/src/components/IndexRule/index.vue b/ui/src/components/IndexRule/index.vue
index ce597f32..ae66b8c1 100644
--- a/ui/src/components/IndexRule/index.vue
+++ b/ui/src/components/IndexRule/index.vue
@@ -103,9 +103,7 @@ function initData() {
</template>
<style lang="scss" scoped>
-::v-deep {
- .el-card {
+:deep(.el-card) {
margin: 15px;
- }
}
</style>
\ No newline at end of file
diff --git a/ui/src/components/IndexRuleBinding/Editor.vue b/ui/src/components/IndexRuleBinding/Editor.vue
index cb6b2029..b8ce515d 100644
--- a/ui/src/components/IndexRuleBinding/Editor.vue
+++ b/ui/src/components/IndexRuleBinding/Editor.vue
@@ -256,9 +256,7 @@ function initData() {
</template>
<style lang="scss" scoped>
-::v-deep {
- .el-card {
+:deep(.el-card) {
margin: 15px;
- }
}
</style>
\ No newline at end of file
diff --git a/ui/src/components/IndexRuleBinding/index.vue b/ui/src/components/IndexRuleBinding/index.vue
index af0e1922..366aa52d 100644
--- a/ui/src/components/IndexRuleBinding/index.vue
+++ b/ui/src/components/IndexRuleBinding/index.vue
@@ -101,9 +101,7 @@ function initData() {
</template>
<style lang="scss" scoped>
-::v-deep {
- .el-card {
+:deep(.el-card) {
margin: 15px;
- }
}
</style>
\ No newline at end of file
diff --git a/ui/src/components/MeasureEditor/index.vue b/ui/src/components/MeasureEditor/index.vue
index 3e9173c1..d1efae64 100644
--- a/ui/src/components/MeasureEditor/index.vue
+++ b/ui/src/components/MeasureEditor/index.vue
@@ -30,9 +30,7 @@
</template>
<style lang="scss" scoped>
-::v-deep {
- .el-card {
- margin: 15px;
- }
+:deep(.el-card) {
+ margin: 15px;
}
</style>
\ No newline at end of file
diff --git a/ui/src/components/Property/PropertyEditror.vue b/ui/src/components/Property/PropertyEditror.vue
new file mode 100644
index 00000000..72382096
--- /dev/null
+++ b/ui/src/components/Property/PropertyEditror.vue
@@ -0,0 +1,230 @@
+<!--
+ ~ Licensed to Apache Software Foundation (ASF) under one or more contributor
+ ~ license agreements. See the NOTICE file distributed with
+ ~ this work for additional information regarding copyright
+ ~ ownership. Apache Software Foundation (ASF) licenses this file to you under
+ ~ the Apache License, Version 2.0 (the "License"); you may
+ ~ not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing,
+ ~ software distributed under the License is distributed on an
+ ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ ~ KIND, either express or implied. See the License for the
+ ~ specific language governing permissions and limitations
+ ~ under the License.
+-->
+
+<script setup>
+import { applyProperty } from '@/api';
+import { reactive, ref } from 'vue';
+import { getCurrentInstance } from '@vue/runtime-core'
+import TagEditor from './TagEditor.vue';
+import { ElMessage } from 'element-plus';
+const $loadingCreate = getCurrentInstance().appContext.config.globalProperties.$loadingCreate
+const $loadingClose = getCurrentInstance().appContext.config.globalProperties.$loadingClose
+const showDialog = ref(false)
+const title = ref('')
+const tagEditorRef = ref()
+const ruleForm = ref()
+const formData = reactive({
+ strategy: 'STRATEGY_UNSPECIFIED',
+ group: '',
+ name: '',
+ containerID: '',
+ modRevision: 0,
+ createRevision: 0,
+ id: '',
+ tags: []
+})
+const validateTags = (rule, value, callback) => {
+ if (value.length <= 0) {
+ callback(new Error('Please add the tags'))
+ } else {
+ callback()
+ }
+}
+const rules = {
+ strategy: [{ required: true, message: 'please select the apply method.', trigger: 'blur' }],
+ group: [{ required: true, message: 'Please enter the group.', trigger: 'blur' }],
+ name: [{ required: true, message: 'Please enter the name.', trigger: 'blur' }],
+ containerID: [{ required: true, message: 'Please enter the container id.', trigger: 'blur' }],
+ modRevision: [{ required: true, message: 'Please enter the container Mod Revision.', trigger: 'blur' }],
+ createRevision: [{ required: true, message: 'Please enter the container Create Revision.', trigger: 'blur' }],
+ id: [{ required: true, message: 'Please enter the ID.', trigger: 'blur' }],
+ tags: [{ required: true, validator: validateTags, trigger: 'blur' }],
+}
+const strategyGroup = [
+ { label: 'STRATEGY_UNSPECIFIED', value: 'STRATEGY_UNSPECIFIED' },
+ { label: 'STRATEGY_MERGE', value: 'STRATEGY_MERGE' },
+ { label: 'STRATEGY_REPLACE', value: 'STRATEGY_REPLACE' }
+]
+const formConfig = [
+ { label: 'Strategy', prop: 'strategy', type: 'select', selectGroup: strategyGroup },
+ { label: 'Container Group', prop: 'group', type: 'input', disabled: true },
+ { label: 'Container Name', prop: 'name', type: 'input' },
+ { label: 'Container ID', prop: 'containerID', type: 'input' },
+ { label: 'Container ModRevision', prop: 'modRevision', type: 'number' },
+ { label: 'Container CreateRevision', prop: 'createRevision', type: 'number' },
+ { label: 'ID', prop: 'id', type: 'input' },
+]
+let promiseResolve
+
+const initData = () => {
+ formData.strategy = 'STRATEGY_UNSPECIFIED'
+ formData.group = ''
+ formData.name = ''
+ formData.containerID = ''
+ formData.modRevision = 0
+ formData.createRevision = 0
+ formData.id = ''
+ formData.tags = []
+}
+const closeDialog = () => {
+ showDialog.value = false
+ initData()
+}
+const openEditTag = (index) => {
+ tagEditorRef.value.openDialog(formData.tags[index])
+ .then(res => {
+ formData.tags[index].key = res.key
+ formData.tags[index].value = res.value
+ })
+}
+const deleteTag = (index) => {
+ formData.tags.splice(index, 1)
+}
+const openAddTag = () => {
+ tagEditorRef.value.openDialog()
+ .then(res => {
+ formData.tags.push(res)
+ })
+}
+const confirmApply = async () => {
+ if (!ruleForm.value) return
+ await ruleForm.value.validate((valid) => {
+ if (valid) {
+ $loadingCreate()
+ const param = {
+ strategy: formData.strategy,
+ property: {
+ metadata: {
+ container: {
+ createRevision: formData.createRevision,
+ group: formData.group,
+ id: formData.containerID,
+ modRevision: formData.modRevision,
+ name: formData.name
+ },
+ id: formData.id
+ },
+ tags: formData.tags.map(item => {
+ return {
+ key: item.key,
+ value: JSON.parse(item.value)
+ }
+ })
+ }
+ }
+ applyProperty(formData.group, formData.name, formData.id, param)
+ .then(res => {
+ if (res.status == 200) {
+ ElMessage({
+ message: 'successed',
+ type: "success",
+ duration: 5000
+ })
+ showDialog.value = false
+ promiseResolve()
+ }
+ })
+ .catch(err => {
+ ElMessage({
+ message: 'Please refresh and try again. Error: ' + err,
+ type: "error",
+ duration: 3000
+ })
+ })
+ .finally(() => {
+ $loadingClose()
+ })
+ }
+ })
+}
+const openDialog = (edit, data) => {
+ showDialog.value = true
+ if (edit === true) {
+ title.value = 'Edit Property'
+ } else {
+ title.value = 'Apply Property'
+ }
+ formData.group = data?.group || ''
+ formData.name = data?.name || ''
+ formData.containerID = data?.containerID || ''
+ formData.modRevision = data?.modRevision || 0
+ formData.createRevision = data?.createRevision || 0
+ formData.id = data?.id || ''
+ formData.tags = JSON.parse(JSON.stringify(data?.tags || []))
+ return new Promise((resolve) => {
+ promiseResolve = resolve
+ })
+}
+defineExpose({
+ openDialog
+})
+</script>
+
+<template>
+ <el-dialog v-model="showDialog" :title="title" width="50%">
+ <el-form ref="ruleForm" :rules="rules" :model="formData" label-position="left">
+ <el-form-item v-for="item in formConfig" :key="item.prop" :label="item.label" :prop="item.prop"
+ label-width="200">
+ <el-select v-if="item.type === 'select'" v-model="formData[item.prop]" placeholder="please select"
+ style="width: 100%;">
+ <el-option v-for="option in item.selectGroup" :key="option.value" :label="option.label"
+ :value="option.value"></el-option>
+ </el-select>
+ <el-input v-if="item.type === 'input'" v-model="formData[item.prop]" :disabled="item.disabled"
+ autocomplete="off"></el-input>
+ <el-input-number v-if="item.type === 'number'" v-model="formData[item.prop]" :min="0"></el-input-number>
+ </el-form-item>
+ <el-form-item label="Tags" prop="tags" label-width="200">
+ <el-button size="small" type="primary" color="#6E38F7" @click="openAddTag">Add Tag</el-button>
+ <el-table style="margin-top: 10px;" :data="formData.tags" border>
+ <el-table-column label="Key" prop="key"></el-table-column>
+ <el-table-column label="Value" prop="value"></el-table-column>
+ <el-table-column label="Operator" width="150">
+ <template #default="scope">
+ <el-button link type="primary" @click.prevent="openEditTag(scope.$index)"
+ style="color: var(--color-main); font-weight: bold;">Edit</el-button>
+ <el-popconfirm @confirm="deleteTag(scope.$index)" title="Are you sure to delete this?">
+ <template #reference>
+ <el-button link type="danger" style="color: red;font-weight: bold;">Delete</el-button>
+ </template>
+ </el-popconfirm>
+ </template>
+ </el-table-column>
+ </el-table>
+ </el-form-item>
+ </el-form>
+ <template #footer>
+ <span class="dialog-footer footer">
+ <el-button @click="closeDialog">Cancel</el-button>
+ <el-button type="primary" @click="confirmApply">
+ Confirm
+ </el-button>
+ </span>
+ </template>
+ </el-dialog>
+ <TagEditor ref="tagEditorRef"></TagEditor>
+</template>
+
+<style lang="scss" scoped>
+.footer {
+ width: 100%;
+ display: flex;
+ justify-content: center;
+}
+</style>
\ No newline at end of file
diff --git a/ui/src/components/Property/PropertyRead.vue b/ui/src/components/Property/PropertyRead.vue
new file mode 100644
index 00000000..6142b10b
--- /dev/null
+++ b/ui/src/components/Property/PropertyRead.vue
@@ -0,0 +1,189 @@
+<!--
+ ~ Licensed to Apache Software Foundation (ASF) under one or more contributor
+ ~ license agreements. See the NOTICE file distributed with
+ ~ this work for additional information regarding copyright
+ ~ ownership. Apache Software Foundation (ASF) licenses this file to you under
+ ~ the Apache License, Version 2.0 (the "License"); you may
+ ~ not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing,
+ ~ software distributed under the License is distributed on an
+ ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ ~ KIND, either express or implied. See the License for the
+ ~ specific language governing permissions and limitations
+ ~ under the License.
+-->
+
+<script setup>
+import { getPropertyByGroup, deleteProperty } from '@/api/index';
+import { watch, getCurrentInstance } from "@vue/runtime-core";
+import { useRoute } from 'vue-router';
+import { ElMessage } from 'element-plus';
+import { onMounted, reactive, ref } from 'vue';
+import { RefreshRight } from '@element-plus/icons-vue';
+import PropertyEditror from './PropertyEditror.vue';
+
+const { proxy } = getCurrentInstance()
+// Loading
+const route = useRoute()
+const $bus = getCurrentInstance().appContext.config.globalProperties.mittBus
+const $loadingCreate = getCurrentInstance().appContext.config.globalProperties.$loadingCreate
+const $loadingClose = proxy.$loadingClose
+const propertyEditorRef = ref()
+const data = reactive({
+ group: "",
+ tableData: []
+})
+const getProperty = () => {
+ $loadingCreate()
+ const group = route.params.group
+ getPropertyByGroup(group)
+ .then(res => {
+ if (res.status == 200 && group == route.params.group) {
+ data.tableData = res.data.property.map(item => {
+ item.tags.forEach(tag => {
+ tag.value = JSON.stringify(tag.value)
+ })
+ return item
+ })
+ }
+ })
+ .catch((err) => {
+ ElMessage({
+ message: 'An error occurred while obtaining group data. Please refresh and try again. Error: ' + err,
+ type: "error",
+ duration: 3000
+ })
+ })
+ .finally(() => {
+ $loadingClose()
+ })
+}
+const openEditField = (index) => {
+ const item = data.tableData[index]
+ const param = {
+ group: item.metadata.container.group,
+ name: item.metadata.container.name,
+ containerID: item.metadata.container.id,
+ modRevision: item.metadata.container.modRevision,
+ createRevision: item.metadata.container.createRevision,
+ id: item.metadata.id,
+ tags: JSON.parse(JSON.stringify(item.tags))
+ }
+ propertyEditorRef?.value.openDialog(true, param)
+ .then(() => {
+ getProperty()
+ })
+}
+const openAddProperty = () => {
+ let dataForm = {
+ group: data.group
+ }
+ propertyEditorRef?.value.openDialog(false, dataForm)
+ .then(() => {
+ getProperty()
+ })
+}
+const deleteTableData = (index) => {
+ const item = data.tableData[index]
+ $loadingCreate()
+ deleteProperty(item.metadata.container.group, item.metadata.container.name, item.metadata.id, item.tags)
+ .then((res) => {
+ if (res.status == 200) {
+ ElMessage({
+ message: 'successed',
+ type: "success",
+ duration: 5000
+ })
+ getProperty()
+ }
+ })
+ .catch(err => {
+ ElMessage({
+ message: 'Please refresh and try again. Error: ' + err,
+ type: "error",
+ duration: 3000
+ })
+ })
+ .finally(() => {
+ $loadingClose()
+ })
+}
+watch(() => route, () => {
+ data.group = route.params.group
+ data.tableData = []
+ getProperty()
+}, {
+ deep: true,
+ immediate: true
+})
+onMounted(() => {
+ getProperty()
+})
+</script>
+<template>
+ <div>
+ <el-card shadow="always">
+ <template #header>
+ <div class="flex">
+ <span class="text-bold">Group:</span>
+ <span style="margin-right: 20px;">{{ data.group }}</span>
+ <span class="text-bold">Operation:</span>
+ <span>Read</span>
+ </div>
+ </template>
+ <div class="button-group-operator">
+ <el-button size="small" type="primary" color="#6E38F7" @click="openAddProperty">Apply
+ Property</el-button>
+ <el-button size="small" :icon="RefreshRight" @click="getProperty" plain></el-button>
+ </div>
+
+ <el-table :data="data.tableData" style="width: 100%; margin-top: 20px;" border>
+ <el-table-column label="Container">
+ <el-table-column label="Group" prop="metadata.container.group" width="100"></el-table-column>
+ <el-table-column label="Name" prop="metadata.container.name" width="120"></el-table-column>
+ <el-table-column label="ID" prop="metadata.container.id" width="100"></el-table-column>
+ <el-table-column label="ModRevision" prop="metadata.container.modRevision"
+ width="120"></el-table-column>
+ <el-table-column label="CreateRevision" prop="metadata.container.createRevision"
+ width="140"></el-table-column>
+ </el-table-column>
+ <el-table-column label="ID" prop="metadata.id" width="150"></el-table-column>
+ <el-table-column label="Tags">
+ <template #default="scope">
+ <el-table :data="scope.row.tags">
+ <el-table-column label="Key" prop="key" width="150"></el-table-column>
+ <el-table-column label="Value" prop="value"></el-table-column>
+ </el-table>
+ </template>
+ </el-table-column>
+ <el-table-column label="Operator" width="150">
+ <template #default="scope">
+ <el-button link type="primary" @click.prevent="openEditField(scope.$index)"
+ style="color: var(--color-main); font-weight: bold;">Edit</el-button>
+ <el-popconfirm @confirm="deleteTableData(scope.$index)" title="Are you sure to delete this?">
+ <template #reference>
+ <el-button link type="danger" style="color: red;font-weight: bold;">Delete</el-button>
+ </template>
+ </el-popconfirm>
+ </template>
+ </el-table-column>
+ </el-table>
+ </el-card>
+ <PropertyEditror ref="propertyEditorRef"></PropertyEditror>
+ </div>
+</template>
+<style lang="scss" scoped>
+:deep(.el-card) {
+ margin: 15px;
+}
+
+.button-group-operator {
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+}
+</style>
\ No newline at end of file
diff --git a/ui/src/components/Property/TagEditor.vue b/ui/src/components/Property/TagEditor.vue
new file mode 100644
index 00000000..66601711
--- /dev/null
+++ b/ui/src/components/Property/TagEditor.vue
@@ -0,0 +1,95 @@
+<!--
+ ~ Licensed to Apache Software Foundation (ASF) under one or more contributor
+ ~ license agreements. See the NOTICE file distributed with
+ ~ this work for additional information regarding copyright
+ ~ ownership. Apache Software Foundation (ASF) licenses this file to you under
+ ~ the Apache License, Version 2.0 (the "License"); you may
+ ~ not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing,
+ ~ software distributed under the License is distributed on an
+ ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ ~ KIND, either express or implied. See the License for the
+ ~ specific language governing permissions and limitations
+ ~ under the License.
+-->
+
+<script setup>
+import { reactive, ref } from 'vue'
+
+const showDialog = ref(false)
+//const editValue = ref(false)
+const ruleForm = ref()
+const title = ref('')
+const formData = reactive({
+ key: '',
+ value: ''
+})
+let promiseResolve
+const initData = () => {
+ formData.key = ''
+ formData.value = ''
+}
+const closeDialog = () => {
+ initData()
+ showDialog.value = false
+}
+const confirmApply = async () => {
+ if (!ruleForm.value) return
+ await ruleForm.value.validate((valid) => {
+ if (valid) {
+ promiseResolve(JSON.parse(JSON.stringify(formData)))
+ initData()
+ showDialog.value = false
+ }
+ })
+}
+const openDialog = (data) => {
+ if (data) {
+ formData.key = data.key
+ formData.value = data.value
+ title.value = 'Edit Tag'
+ } else {
+ title.value = 'Add Tag'
+ }
+ showDialog.value = true
+ return new Promise((resolve) => {
+ promiseResolve = resolve
+ })
+}
+defineExpose({
+ openDialog
+})
+</script>
+
+<template>
+ <el-dialog v-model="showDialog" :title="title" width="30%">
+ <el-form ref="ruleForm" :model="formData" label-position="left">
+ <el-form-item label="Key" prop="key" required label-width="150">
+ <el-input v-model="formData.key" autocomplete="off"></el-input>
+ </el-form-item>
+ <el-form-item label="Value" prop="value" required label-width="150">
+ <el-input v-model="formData.value" type="textarea" autocomplete="off"></el-input>
+ </el-form-item>
+ </el-form>
+ <template #footer>
+ <span class="dialog-footer footer">
+ <el-button @click="closeDialog">Cancel</el-button>
+ <el-button type="primary" @click="confirmApply">
+ Confirm
+ </el-button>
+ </span>
+ </template>
+ </el-dialog>
+</template>
+
+<style scoped lang="scss">
+.footer {
+ width: 100%;
+ display: flex;
+ justify-content: center;
+}
+</style>
\ No newline at end of file
diff --git a/ui/src/components/Read/index.vue b/ui/src/components/Read/index.vue
index 8c0376c4..a21f6d7a 100644
--- a/ui/src/components/Read/index.vue
+++ b/ui/src/components/Read/index.vue
@@ -437,9 +437,7 @@ function changeFields() {
</template>
<style lang="scss" scoped>
-::v-deep {
- .el-card {
- margin: 15px;
- }
+:deep(.el-card) {
+ margin: 15px;
}
</style>
\ No newline at end of file
diff --git a/ui/src/components/TopNav/index.vue b/ui/src/components/TopNav/index.vue
index 4ed5db9c..c24d2208 100644
--- a/ui/src/components/TopNav/index.vue
+++ b/ui/src/components/TopNav/index.vue
@@ -33,9 +33,8 @@ const data = reactive({
function tabClick() {
router.push(data.tabsList[data.activeTabIndex].route)
const group = data.tabsList[data.activeTabIndex].route.params.group
- const name = data.tabsList[data.activeTabIndex].route.params.name
-
- if (group && name) {
+ const name = data.tabsList[data.activeTabIndex].route.params.name ? data.tabsList[data.activeTabIndex].route.params.name : null
+ if (group) {
$bus.emit('changeAside', { group, name })
}
}
@@ -67,7 +66,7 @@ function initData() {
const add = { route: routeData }
if (operator == 'read') {
routeData.name = type
- add.label = name
+ add.label = type == 'property' ? group : name
add.type = type == 'index-rule' || type == 'index-rule-binding' ? `Read-${type}` : 'Read'
} else if (operator == 'edit') {
routeData.name = `edit-${type}`
@@ -79,6 +78,7 @@ function initData() {
add.type = type == 'index-rule' || type == 'index-rule-binding' ? `Create-${type}` : 'Create'
}
data.tabsList.push(add)
+ $bus.emit('changeAside', { group, name })
}
$bus.on('AddTabs', (tab) => {
const index = data.tabsList.findIndex(item => {
@@ -133,30 +133,28 @@ initData()
box-shadow: 0 0 10px 0 var(--color-placeholder-font);
}
-::v-deep {
- .el-tabs--card>.el-tabs__header .el-tabs__nav {
- border-top: none !important;
- padding: 0 !important;
- }
+:deep(.el-tabs--card>.el-tabs__header .el-tabs__nav) {
+ border-top: none !important;
+ padding: 0 !important;
+}
- .el-tabs__header.is-top {
- padding: 0 !important;
- margin: 0 !important;
- }
+:deep(.el-tabs__header.is-top) {
+ padding: 0 !important;
+ margin: 0 !important;
+}
- .el-tabs__item.is-active {
- color: var(--color-main) !important;
- background-color: var(--color-main-background) !important;
- border: 1px solid var(--color-main) !important;
- border-bottom: none !important;
- border-top-left-radius: 4px;
- border-top-right-radius: 4px;
- }
+:deep(.el-tabs__item.is-active) {
+ color: var(--color-main) !important;
+ background-color: var(--color-main-background) !important;
+ border: 1px solid var(--color-main) !important;
+ border-bottom: none !important;
+ border-top-left-radius: 4px;
+ border-top-right-radius: 4px;
+}
- .el-tabs__content {
- padding: 0 !important;
- margin: 0 !important;
- height: 0 !important;
- }
+:deep(.el-tabs__content) {
+ padding: 0 !important;
+ margin: 0 !important;
+ height: 0 !important;
}
</style>
\ No newline at end of file
diff --git a/ui/src/router/index.js b/ui/src/router/index.js
index 79e417c7..0b9f104b 100644
--- a/ui/src/router/index.js
+++ b/ui/src/router/index.js
@@ -137,7 +137,23 @@ const router = createRouter({
{
path: '/banyandb/property',
name: 'Property',
- component: () => import('@/views/Property/index.vue')
+ redirect: '/banyandb/property/start',
+ component: () => import('@/views/Property/index.vue'),
+ children: [
+ {
+ path: '/banyandb/property/start',
+ name: 'propertyStart',
+ component: () => import('@/components/Start/index.vue'),
+ meta: {
+ type: 'property'
+ }
+ },
+ {
+ path: '/banyandb/property/operator-read/:type/:operator/:group',
+ name: 'property',
+ component: () => import('@/components/Property/PropertyRead.vue')
+ }
+ ]
}
]
},
diff --git a/ui/src/views/Property/index.vue b/ui/src/views/Property/index.vue
index 4c303543..4f66dcb8 100644
--- a/ui/src/views/Property/index.vue
+++ b/ui/src/views/Property/index.vue
@@ -18,13 +18,49 @@
-->
<script setup>
+import Aside from '@/components/Aside/index.vue'
+import TopNav from '@/components/TopNav/index.vue'
+import { reactive } from 'vue';
+
+const data = reactive({
+ width: '200px'
+})
+
+function setWidth(width) {
+ data.width = width
+}
</script>
<template>
- <div class="about">
- <h1>This is the property page</h1>
+ <div class="size">
+ <el-container>
+ <el-aside :width="data.width" class="bd-top flex" style="position:relative; background-color: white;">
+ <Aside type="property" @setWidth="setWidth"></Aside>
+ </el-aside>
+ <el-main>
+ <TopNav></TopNav>
+ <RouterView></RouterView>
+ </el-main>
+ </el-container>
</div>
</template>
<style lang="scss" scoped>
+.el-container {
+ height: 100%;
+ padding: 0;
+ margin: 0;
+
+ .el-header {
+ background-color: #ffffff;
+ padding: 0;
+ margin: 0;
+ }
+
+ .el-main {
+ background-color: #eaedf1;
+ padding: 0;
+ margin: 0;
+ }
+}
</style>
\ No newline at end of file