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 36256a14 Add CRUD function for Index rule (#285)
36256a14 is described below

commit 36256a1418e4bdae2e5d37b4330e1b6bedcd5060
Author: Wu ChuSheng <[email protected]>
AuthorDate: Mon Jun 26 17:03:57 2023 +0800

    Add CRUD function for Index rule (#285)
    
    * Init IndexRule
    
    * Add router
    
    * Add IndexRule Reader and Editor
    
    * Add Delete Index-rule
---
 ui/src/api/index.js                           |  44 ++++
 ui/src/components/Aside/index.vue             | 336 ++++++++++++++++++++++++--
 ui/src/components/IndexRule/Editor.vue        | 332 +++++++++++++++++++++++++
 ui/src/components/IndexRule/index.vue         | 109 +++++++++
 ui/src/components/IndexRuleBinding/Editor.vue |  26 ++
 ui/src/components/IndexRuleBinding/index.vue  |  26 ++
 ui/src/components/Read/index.vue              |   2 +-
 ui/src/components/TopNav/index.vue            |   7 +-
 ui/src/router/index.js                        |  30 +++
 9 files changed, 883 insertions(+), 29 deletions(-)

diff --git a/ui/src/api/index.js b/ui/src/api/index.js
index 50cf5010..059782c9 100644
--- a/ui/src/api/index.js
+++ b/ui/src/api/index.js
@@ -95,4 +95,48 @@ export function editResources(type, group, name, data) {
         method: 'put',
         data: data
     })
+}
+
+export function getindexRuleList(name) {
+    return request({
+        url: `/api/v1/index-rule/schema/lists/${name}`,
+        method: 'get'
+    })
+}
+
+export function getindexRuleBindingList(name) {
+    return request({
+        url: `/api/v1/index-rule-binding/schema/lists/${name}`,
+        method: 'get'
+    })
+}
+
+export function getIndexRuleOrIndexRuleBinding(type, group, name) {
+    return request({
+        url: `/api/v1/${type}/schema/${group}/${name}`,
+        method: 'get'
+    })
+}
+
+export function createIndexRuleOrIndexRuleBinding(type, data) {
+    return request({
+        url: `/api/v1/${type}/schema`,
+        method: 'post',
+        data: data
+    })
+}
+
+export function updatendexRuleOrIndexRuleBinding(type, group, name, data) {
+    return request({
+        url: `/api/v1/${type}/schema/${group}/${name}`,
+        method: 'put',
+        data: data
+    })
+}
+
+export function deleteIndexRuleOrIndexRuleBinding(type, group, name) {
+    return request({
+        url: `/api/v1/${type}/schema/${group}/${name}`,
+        method: 'delete'
+    })
 }
\ No newline at end of file
diff --git a/ui/src/components/Aside/index.vue 
b/ui/src/components/Aside/index.vue
index 4096bf28..bb5a4a9c 100644
--- a/ui/src/components/Aside/index.vue
+++ b/ui/src/components/Aside/index.vue
@@ -19,7 +19,7 @@
 
 <script setup>
 import RigheMenu from '@/components/RightMenu/index.vue'
-import { getGroupList, getStreamOrMeasureList, deleteStreamOrMeasure, 
deleteGroup, createGroup, editGroup, createResources } from '@/api/index'
+import { deleteIndexRuleOrIndexRuleBinding, getindexRuleList, 
getindexRuleBindingList, getGroupList, getStreamOrMeasureList, 
deleteStreamOrMeasure, deleteGroup, createGroup, editGroup, createResources } 
from '@/api/index'
 import { ElMessage, ElMessageBox } from 'element-plus'
 import { watch, getCurrentInstance } from "@vue/runtime-core"
 import { useRouter, useRoute } from 'vue-router'
@@ -77,10 +77,6 @@ const groupMenu = [
         icon: "el-icon-folder",
         name: "edit group",
         id: "edit Group"
-    }, {
-        icon: "el-icon-document",
-        name: "new resources",
-        id: "create resources"
     }, {
         icon: "el-icon-refresh-right",
         name: "refresh",
@@ -103,13 +99,62 @@ const resourceMenu = [
         id: "delete resources"
     }
 ]
+const StreamMenu = [
+    {
+        icon: "el-icon-document",
+        name: "new resources",
+        id: "create resources"
+    },
+]
+const indexRuleMenu = [
+    {
+        icon: "el-icon-document",
+        name: "new index-rule",
+        id: "create index-rule"
+    }
+]
+const indexRuleBindMenu = [
+    {
+        icon: "el-icon-document",
+        name: "new index-rule-binding",
+        id: "create index-rule-binding"
+    }
+]
+const indexRuleItemMenu = [
+    {
+        icon: "el-icon-document",
+        name: "edit index-rule",
+        id: "edit index-rule"
+    },
+    {
+        icon: "el-icon-delete",
+        name: "delete",
+        id: "delete index-rule"
+    }
+]
+const indexRuleBindingItemMenu = [
+    {
+        icon: "el-icon-document",
+        name: "edit index-rule-binding",
+        id: "edit index-rule-binding"
+    },
+    {
+        icon: "el-icon-delete",
+        name: "delete",
+        id: "delete index-rule-binding"
+    }
+]
 const menuItemFunction = {
     "new group": openCreateGroup,
     "edit group": openEditGroup,
     "new resources": openCreateResource,
-    "refresh": getGroupList,
+    "refresh": getGroupLists,
     "delete": openDeletaDialog,
-    "edit resources": openEditResource
+    "edit resources": openEditResource,
+    'new index-rule': openCreateIndexRuleOrIndexRuleBinding,
+    'edit index-rule': openEditIndexRuleOrIndexRuleBinding,
+    'new index-rule-binding': openCreateIndexRuleOrIndexRuleBinding,
+    'edit index-rule-binding': openEditIndexRuleOrIndexRuleBinding
 }
 
 // rules
@@ -199,6 +244,40 @@ function getGroupLists() {
                             })
                     })
                 })
+                if (props.type == 'stream') {
+                    let promiseIndexRule = data.groupLists.map((item) => {
+                        let name = item.metadata.name
+                        return new Promise((resolve, reject) => {
+                            getindexRuleList(name)
+                                .then(res => {
+                                    if (res.status == 200) {
+                                        item.indexRule = res.data.indexRule
+                                        resolve()
+                                    }
+                                })
+                                .catch((err) => {
+                                    reject(err)
+                                })
+                        })
+                    })
+                    let promiseIndexRuleBinding = data.groupLists.map((item) 
=> {
+                        let name = item.metadata.name
+                        return new Promise((resolve, reject) => {
+                            getindexRuleBindingList(name)
+                                .then(res => {
+                                    if (res.status == 200) {
+                                        item.indexRuleBinding = 
res.data.indexRuleBinding
+                                        resolve()
+                                    }
+                                })
+                                .catch((err) => {
+                                    reject(err)
+                                })
+                        })
+                    })
+                    promise = promise.concat(promiseIndexRule)
+                    promise = promise.concat(promiseIndexRuleBinding)
+                }
                 Promise.all(promise).then(() => {
                     data.showSearch = true
                     data.groupListsCopy = 
JSON.parse(JSON.stringify(data.groupLists))
@@ -294,6 +373,38 @@ function rightClickResources(e, index, childIndex) {
     data.rightClickType = 'resources'
     openRightMenu(e)
 }
+function rightClickStream(e, index) {
+    data.rightMenuList = StreamMenu
+    data.clickIndex = index
+    data.rightClickType = 'group'
+    openRightMenu(e)
+}
+function rightClickIndexRule(e, index) {
+    data.rightMenuList = indexRuleMenu
+    data.clickIndex = index
+    data.rightClickType = 'index-rule'
+    openRightMenu(e)
+}
+function rightClickIndexRuleBinding(e, index) {
+    data.rightMenuList = indexRuleBindMenu
+    data.clickIndex = index
+    data.rightClickType = 'index-rule-binding'
+    openRightMenu(e)
+}
+function rightClickIndexRuleItem(e, index, childIndex) {
+    data.rightMenuList = indexRuleItemMenu
+    data.clickIndex = index
+    data.clickChildIndex = childIndex
+    data.rightClickType = 'index-rule'
+    openRightMenu(e)
+}
+function rightClickIndexRuleBindingItem(e, index, childIndex) {
+    data.rightMenuList = indexRuleBindingItemMenu
+    data.clickIndex = index
+    data.clickChildIndex = childIndex
+    data.rightClickType = 'index-rule-binding'
+    openRightMenu(e)
+}
 function openRightMenu(e) {
     data.showRightMenu = true
     data.top = e.pageY
@@ -320,6 +431,73 @@ function stopPropagation(e) {
 }
 
 // CRUD operator
+function openCreateIndexRuleOrIndexRuleBinding() {
+    const route = {
+        name: `create-${data.rightClickType}`,
+        params: {
+            operator: 'create',
+            group: data.groupLists[data.clickIndex].metadata.name,
+            name: '',
+            type: data.rightClickType
+        }
+    }
+    router.push(route)
+    const add = {
+        label: data.groupLists[data.clickIndex].metadata.name,
+        type: `Create-${data.rightClickType}`,
+        route
+    }
+    data.activeMenu = ''
+    $bus.emit('AddTabs', add)
+}
+
+function openEditIndexRuleOrIndexRuleBinding() {
+    const typeFlag = {
+        'index-rule': 'indexRule',
+        'index-rule-binding': 'indexRuleBinding'
+    }
+    const route = {
+        name: `edit-${data.rightClickType}`,
+        params: {
+            operator: 'edit',
+            group: data.groupLists[data.clickIndex].metadata.name,
+            name: 
data.groupLists[data.clickIndex][typeFlag[data.rightClickType]][data.clickChildIndex].metadata.name,
+            type: data.rightClickType
+        }
+    }
+    router.push(route)
+    const add = {
+        label: 
data.groupLists[data.clickIndex][typeFlag[data.rightClickType]][data.clickChildIndex].metadata.name,
+        type: `Edit-${data.rightClickType}`,
+        route
+    }
+    $bus.emit('AddTabs', add)
+}
+function openIndexRuleOrIndexRuleBinding(index, childIndex, type) {
+    const typeFlag = {
+        'indexRule': 'index-rule',
+        'indexRuleBinding': 'index-rule-binding'
+    }
+    const group = data.groupLists[index][type][childIndex].metadata.group
+    const name = data.groupLists[index][type][childIndex].metadata.name
+    const route = {
+        name: `${typeFlag[type]}`,
+        params: {
+            group: group,
+            name: name,
+            operator: 'read',
+            type: typeFlag[type]
+        }
+    }
+    router.push(route)
+    const add = {
+        label: name,
+        type: `Read-${typeFlag[type]}`,
+        route
+    }
+    data.activeMenu = `${group}-${name}`
+    $bus.emit('AddTabs', add)
+}
 function openCreateGroup() {
     data.setGroup = 'create'
     data.dialogGroupVisible = true
@@ -368,11 +546,13 @@ function openEditResource() {
     $bus.emit('AddTabs', add)
 }
 function openDeletaDialog() {
-    ElMessageBox.confirm('Are you sure to delete this resource?')
+    ElMessageBox.confirm('Are you sure to delete?')
         .then(() => {
             let group = data.groupLists[data.clickIndex].metadata.name
             if (data.rightClickType == 'group') {
                 return deleteGroupFunction(group)
+            } else if (data.rightClickType == 'index-rule') {
+                return deleteIndexRuleFunction()
             }
             return deleteResource(group)
         })
@@ -380,6 +560,28 @@ function openDeletaDialog() {
             // catch error
         })
 }
+function deleteIndexRuleFunction() {
+    $loadingCreate()
+    let group = data.groupLists[data.clickIndex].metadata.name
+    let name = 
data.groupLists[data.clickIndex].indexRule[data.clickChildIndex].metadata.name
+    deleteIndexRuleOrIndexRuleBinding("index-rule", group, name)
+        .then((res) => {
+            if (res.status == 200) {
+                if (res.data.deleted) {
+                    ElMessage({
+                        message: 'Delete succeeded',
+                        type: "success",
+                        duration: 5000
+                    })
+                    getGroupLists()
+                    $bus.emit('deleteResource', name)
+                }
+            }
+        })
+        .finally(() => {
+            $loadingClose()
+        })
+}
 function deleteGroupFunction(group) {
     // delete group
     $loadingCreate()
@@ -537,8 +739,8 @@ 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" class="aside-search" 
v-model="data.search" placeholder="Search"
-                :prefix-icon="Search" clearable />
+            <el-input v-if="data.showSearch && props.type == 'measure'" 
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)">
@@ -551,20 +753,106 @@ initActiveMenu()
                                 {{ item.metadata.name }}
                             </span>
                         </template>
-                        <div v-for="(child, childIndex) in item.children" 
:key="child.metadata.name">
-                            <div 
@contextmenu.prevent="rightClickResources($event, index, childIndex)">
-                                <el-menu-item 
:index="`${child.metadata.group}-${child.metadata.name}`"
-                                    @click="openResources(index, childIndex)">
-                                    <template #title>
-                                        <el-icon>
-                                            <Document />
-                                        </el-icon>
-                                        <span slot="title" 
:title="child.metadata.name" style="width: 90%"
-                                            class="text-overflow-hidden">
-                                            {{ child.metadata.name }}
-                                        </span>
-                                    </template>
-                                </el-menu-item>
+                        <el-sub-menu v-if="props.type == 'stream'" 
:index="`${item.metadata.name}-${index}-index-rule`"
+                            @contextmenu.prevent="rightClickIndexRule($event, 
index)">
+                            <template #title>
+                                <el-icon>
+                                    <Folder />
+                                </el-icon>
+                                <span slot="title" title="Index-Rule" 
style="width: 70%" class="text-overflow-hidden">
+                                    Index-Rule
+                                </span>
+                            </template>
+                            <div v-for="(child, childIndex) in item.indexRule" 
:key="child.metadata.name">
+                                <div 
@contextmenu.prevent="rightClickIndexRuleItem($event, index, childIndex)">
+                                    <el-menu-item 
@click="openIndexRuleOrIndexRuleBinding(index, childIndex, 'indexRule')"
+                                        
:index="`${child.metadata.group}-${child.metadata.name}`">
+                                        <template #title>
+                                            <el-icon>
+                                                <Document />
+                                            </el-icon>
+                                            <span slot="title" 
:title="child.metadata.name" style="width: 90%"
+                                                class="text-overflow-hidden">
+                                                {{ child.metadata.name }}
+                                            </span>
+                                        </template>
+                                    </el-menu-item>
+                                </div>
+                            </div>
+                        </el-sub-menu>
+                        <el-sub-menu v-if="props.type == 'stream'"
+                            
:index="`${item.metadata.name}-${index}-index-rule-binding`"
+                            
@contextmenu.prevent="rightClickIndexRuleBinding($event, index)">
+                            <template #title>
+                                <el-icon>
+                                    <Folder />
+                                </el-icon>
+                                <span slot="title" title="Index-Rule-Binding" 
style="width: 70%"
+                                    class="text-overflow-hidden">
+                                    Index-Rule-Binding
+                                </span>
+                            </template>
+                            <div v-for="(child, childIndex) in 
item.indexRuleBinding" :key="child.metadata.name">
+                                <div 
@contextmenu.prevent="rightClickIndexRuleBindingItem($event, index, 
childIndex)">
+                                    <el-menu-item
+                                        
@click="openIndexRuleOrIndexRuleBinding(index, childIndex, 'indexRuleBinding')"
+                                        
:index="`${child.metadata.group}-${child.metadata.name}`">
+                                        <template #title>
+                                            <el-icon>
+                                                <Document />
+                                            </el-icon>
+                                            <span slot="title" 
:title="child.metadata.name" style="width: 90%"
+                                                class="text-overflow-hidden">
+                                                {{ child.metadata.name }}
+                                            </span>
+                                        </template>
+                                    </el-menu-item>
+                                </div>
+                            </div>
+                        </el-sub-menu>
+                        <el-sub-menu v-if="props.type == 'stream'" 
@contextmenu.prevent="rightClickStream($event, index)"
+                            :index="`${item.metadata.name}-${index}-stream`">
+                            <template #title>
+                                <el-icon>
+                                    <Folder />
+                                </el-icon>
+                                <span slot="title" title="Stream" 
style="width: 70%" class="text-overflow-hidden">
+                                    Stream
+                                </span>
+                            </template>
+                            <div v-for="(child, childIndex) in item.children" 
:key="child.metadata.name">
+                                <div 
@contextmenu.prevent="rightClickResources($event, index, childIndex)">
+                                    <el-menu-item 
:index="`${child.metadata.group}-${child.metadata.name}`"
+                                        @click="openResources(index, 
childIndex)">
+                                        <template #title>
+                                            <el-icon>
+                                                <Document />
+                                            </el-icon>
+                                            <span slot="title" 
:title="child.metadata.name" style="width: 90%"
+                                                class="text-overflow-hidden">
+                                                {{ child.metadata.name }}
+                                            </span>
+                                        </template>
+                                    </el-menu-item>
+                                </div>
+                            </div>
+                        </el-sub-menu>
+                        <div v-if="props.type == 'measure'">
+                            <div v-for="(child, childIndex) in item.children" 
:key="child.metadata.name">
+                                <div 
@contextmenu.prevent="rightClickResources($event, index, childIndex)">
+                                    <el-menu-item 
:index="`${child.metadata.group}-${child.metadata.name}`"
+                                        @click="openResources(index, 
childIndex)">
+                                        <template #title>
+                                            <el-icon>
+                                                <Document />
+                                            </el-icon>
+                                            <span slot="title" 
:title="child.metadata.name" style="width: 90%"
+                                                class="text-overflow-hidden">
+                                                {{ child.metadata.name }}
+                                            </span>
+                                        </template>
+                                    </el-menu-item>
+                                </div>
                             </div>
                         </div>
                     </el-sub-menu>
diff --git a/ui/src/components/IndexRule/Editor.vue 
b/ui/src/components/IndexRule/Editor.vue
new file mode 100644
index 00000000..4e4cef35
--- /dev/null
+++ b/ui/src/components/IndexRule/Editor.vue
@@ -0,0 +1,332 @@
+<!--
+  ~ 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 lang="ts" setup>
+import { reactive, ref } from 'vue'
+import { watch, getCurrentInstance } from '@vue/runtime-core'
+import { useRoute, useRouter } from 'vue-router'
+import type { FormInstance } from 'element-plus'
+import { createIndexRuleOrIndexRuleBinding, getIndexRuleOrIndexRuleBinding, 
updatendexRuleOrIndexRuleBinding } from '@/api/index'
+import { ElMessage } from 'element-plus'
+const $loadingCreate = 
getCurrentInstance().appContext.config.globalProperties.$loadingCreate
+const $loadingClose = 
getCurrentInstance().appContext.config.globalProperties.$loadingClose
+const $bus = getCurrentInstance().appContext.config.globalProperties.mittBus
+const ruleFormRef = ref<FormInstance>()
+
+const route = useRoute()
+const router = useRouter()
+const rules = {
+  group: [
+    {
+      required: true, message: 'Please enter the group', trigger: 'blur'
+    }
+  ],
+  name: [
+    {
+      required: true, message: 'Please enter the name', trigger: 'blur'
+    }
+  ],
+  analyzer: [
+    {
+      required: true, message: 'Please select the analyzer', trigger: 'blur'
+    }
+  ],
+  location: [
+    {
+      required: true, message: 'Please select the location', trigger: 'blur'
+    }
+  ],
+  tags: [
+    {
+      required: true, message: 'Please select the tags', trigger: 'blur'
+    }
+  ],
+  type: [
+    {
+      required: true, message: 'Please select the type', trigger: 'blur'
+    }
+  ]
+}
+const typeList = [
+  {
+    label: "TYPE_UNSPECIFIED",
+    value: "TYPE_UNSPECIFIED"
+  },
+  {
+    label: "TYPE_TREE",
+    value: "TYPE_TREE"
+  },
+  {
+    label: "TYPE_INVERTED",
+    value: "TYPE_INVERTED"
+  }
+]
+const locationList = [
+  {
+    label: "LOCATION_UNSPECIFIED",
+    value: "LOCATION_UNSPECIFIED"
+  },
+  {
+    label: "LOCATION_SERIES",
+    value: "LOCATION_SERIES"
+  },
+  {
+    label: "LOCATION_GLOBAL",
+    value: "LOCATION_GLOBAL"
+  }
+]
+const analyzerList = [
+  {
+    label: "ANALYZER_UNSPECIFIED",
+    value: "ANALYZER_UNSPECIFIED"
+  },
+  {
+    label: "ANALYZER_KEYWORD",
+    value: "ANALYZER_KEYWORD"
+  },
+  {
+    label: "ANALYZER_STANDARD",
+    value: "ANALYZER_STANDARD"
+  },
+  {
+    label: "ANALYZER_SIMPLE",
+    value: "ANALYZER_SIMPLE"
+  }
+]
+const data = reactive({
+  group: route.params.group,
+  name: route.params.name,
+  type: route.params.type,
+  operator: route.params.operator,
+  form: {
+    group: route.params.group,
+    name: route.params.name || '',
+    analyzer: '',
+    location: '',
+    tags: [],
+    type: ''
+  }
+})
+
+
+watch(() => route, () => {
+  data.form = {
+    group: route.params.group,
+    name: route.params.name || '',
+    analyzer: '',
+    location: '',
+    tags: [],
+    type: ''
+  }
+  data.group = route.params.group
+  data.name = route.params.name
+  data.type = route.params.type
+  data.operator = route.params.operator
+  initData()
+}, {
+  immediate: true,
+  deep: true
+})
+const submit = async (formEl: FormInstance | undefined) => {
+  if (!formEl) return
+  await formEl.validate((valid) => {
+    if (valid) {
+      const param = {
+        indexRule: {
+          metadata: {
+            group: data.form.group,
+            name: data.form.name
+          },
+          tags: data.form.tags,
+          type: data.form.type,
+          location: data.form.location,
+          analyzer: data.form.analyzer
+        }
+      }
+      $loadingCreate()
+      if (data.operator == 'create') {
+        return createIndexRuleOrIndexRuleBinding("index-rule", param)
+          .then(res => {
+            if (res.status == 200) {
+              ElMessage({
+                message: 'Create successed',
+                type: "success",
+                duration: 5000
+              })
+              $bus.emit('refreshAside')
+              $bus.emit('deleteGroup', data.form.group)
+              openIndexRule()
+            }
+          })
+          .catch(err => {
+            ElMessage({
+              message: 'Please refresh and try again. Error: ' + err,
+              type: "error",
+              duration: 3000
+            })
+          })
+          .finally(() => {
+            $loadingClose()
+          })
+      } else {
+        return updatendexRuleOrIndexRuleBinding("index-rule", data.form.group, 
data.form.name, param)
+          .then(res => {
+            if (res.status == 200) {
+              ElMessage({
+                message: 'Edit successed',
+                type: "success",
+                duration: 5000
+              })
+              $bus.emit('refreshAside')
+              $bus.emit('deleteResource', data.form.group)
+              openIndexRule()
+            }
+          })
+          .catch(err => {
+            ElMessage({
+              message: 'Please refresh and try again. Error: ' + err,
+              type: "error",
+              duration: 3000
+            })
+          })
+          .finally(() => {
+            $loadingClose()
+          })
+      }
+    }
+  })
+}
+
+function openIndexRule() {
+  const route = {
+    name: data.type + '',
+    params: {
+      group: data.form.group,
+      name: data.form.name,
+      operator: 'read',
+      type: data.type + ''
+    }
+  }
+  router.push(route)
+  const add = {
+    label: data.form.name,
+    type: `Read-${data.type}`,
+    route
+  }
+  $bus.emit('changeAside', data.form)
+  $bus.emit('AddTabs', add)
+}
+
+function initData() {
+  if (data.operator == 'edit' && data.form.group && data.form.name) {
+    $loadingCreate()
+    getIndexRuleOrIndexRuleBinding("index-rule", data.form.group, 
data.form.name)
+      .then(res => {
+        if (res.status == 200) {
+          const indexRule = res.data.indexRule
+          data.form = {
+            group: indexRule.metadata.group,
+            name: indexRule.metadata.name,
+            analyzer: indexRule.analyzer,
+            location: indexRule.location,
+            tags: indexRule.tags,
+            type: indexRule.type
+          }
+        }
+      })
+      .catch(err => {
+        ElMessage({
+          message: 'Please refresh and try again. Error: ' + err,
+          type: "error",
+          duration: 3000
+        })
+      })
+      .finally(() => {
+        $loadingClose()
+      })
+  }
+}
+</script>
+
+<template>
+  <div>
+    <el-card>
+      <template #header>
+        <el-row>
+          <el-col :span="12">
+            <div class="flex align-item-center" style="height: 30px; width: 
100%;">
+              <div class="flex" style="height: 30px;">
+                <span class="text-bold">Group:</span>
+                <span style="margin-right: 20px;">{{ data.group }}</span>
+                <span class="text-bold" v-if="data.operator == 
'edit'">Name:</span>
+                <span style="margin-right: 20px;">{{ data.name }}</span>
+                <span class="text-bold">Type:</span>
+                <span style="margin-right: 20px;">{{ data.type }}</span>
+                <span class="text-bold">Operation:</span>
+                <span>{{ data.operator }}</span>
+              </div>
+            </div>
+          </el-col>
+          <el-col :span="12">
+            <div class="flex align-item-center justify-end" style="height: 
30px;">
+              <el-button size="small" type="primary" 
@click="submit(ruleFormRef)" color="#6E38F7">submit</el-button>
+            </div>
+          </el-col>
+        </el-row>
+      </template>
+      <el-form ref="ruleFormRef" :rules="rules" label-position="left" 
label-width="100px" :model="data.form"
+        style="width: 50%;">
+        <el-form-item label="Group" prop="group">
+          <el-input v-model="data.form.group" clearable 
:disabled="true"></el-input>
+        </el-form-item>
+        <el-form-item label="Name" prop="name">
+          <el-input v-model="data.form.name" :disabled="data.operator == 
'edit'" clearable
+            placeholder="Input Name"></el-input>
+        </el-form-item>
+        <el-form-item label="Analyzer" prop="analyzer">
+          <el-select v-model="data.form.analyzer" placeholder="Choose 
Analyzer" style="width: 100%;" clearable>
+            <el-option v-for="item in analyzerList" :key="item.value" 
:label="item.label" :value="item.value" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="Location" prop="location">
+          <el-select v-model="data.form.location" placeholder="Choose 
Location" style="width: 100%;" clearable>
+            <el-option v-for="item in locationList" :key="item.value" 
:label="item.label" :value="item.value" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="Tags" prop="tags">
+          <el-select v-model="data.form.tags" allow-create filterable 
default-first-option placeholder="Input Tags"
+            style="width: 100%;" clearable multiple></el-select>
+        </el-form-item>
+        <el-form-item label="Type" prop="type">
+          <el-select v-model="data.form.type" placeholder="Choose Type" 
style="width: 100%;" clearable>
+            <el-option v-for="item in typeList" :key="item.value" 
:label="item.label" :value="item.value" />
+          </el-select>
+        </el-form-item>
+      </el-form>
+    </el-card>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+::v-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
new file mode 100644
index 00000000..c9531e39
--- /dev/null
+++ b/ui/src/components/IndexRule/index.vue
@@ -0,0 +1,109 @@
+<!--
+  ~ 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 } from 'vue';
+import { watch, getCurrentInstance } from '@vue/runtime-core'
+import { useRoute } from 'vue-router'
+import { getIndexRuleOrIndexRuleBinding } from '@/api/index'
+
+const { proxy } = getCurrentInstance()
+const $loadingCreate = 
getCurrentInstance().appContext.config.globalProperties.$loadingCreate
+const $loadingClose = proxy.$loadingClose
+
+const data = reactive({
+  group: '',
+  name: '',
+  type: '',
+  operator: '',
+  indexRule: {}
+})
+
+const route = useRoute()
+
+watch(() => route, () => {
+  data.group = route.params.group
+  data.name = route.params.name
+  data.type = route.params.type
+  data.operator = route.params.operator
+  initData()
+}, {
+  immediate: true,
+  deep: true
+})
+
+function initData() {
+  $loadingCreate()
+  getIndexRuleOrIndexRuleBinding(data.type, data.group, data.name)
+    .then(result => {
+      data.indexRule = result.data.indexRule
+    })
+    .catch(err => {
+      ElMessage({
+        message: 'Please refresh and try again. Error: ' + err,
+        type: "error",
+        duration: 3000
+      })
+    })
+    .finally(() => {
+      $loadingClose()
+    })
+}
+</script>
+
+<template>
+  <div>
+    <el-card>
+      <template #header>
+        <div class="flex">
+          <span class="text-bold">Group:</span>
+          <span style="margin-right: 20px;">{{ data.group }}</span>
+          <span class="text-bold">Name:</span>
+          <span style="margin-right: 20px;">{{ data.name }}</span>
+          <span class="text-bold">Type:</span>
+          <span style="margin-right: 20px;">{{ data.type }}</span>
+          <span class="text-bold">Operation:</span>
+          <span>Read</span>
+        </div>
+      </template>
+      <el-form label-position="left" label-width="100px" 
:model="data.indexRule" style="width: 50%;">
+        <el-form-item label="Analyzer">
+          <el-input v-model="data.indexRule.analyzer" 
:disabled="true"></el-input>
+        </el-form-item>
+        <el-form-item label="Location">
+          <el-input v-model="data.indexRule.location" 
:disabled="true"></el-input>
+        </el-form-item>
+        <el-form-item label="Tags">
+          <el-select v-model="data.indexRule.tags" style="width: 100%;" 
:disabled="true" multiple></el-select>
+        </el-form-item>
+        <el-form-item label="Type">
+          <el-input v-model="data.indexRule.type" :disabled="true"></el-input>
+        </el-form-item>
+      </el-form>
+    </el-card>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+::v-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
new file mode 100644
index 00000000..68570dad
--- /dev/null
+++ b/ui/src/components/IndexRuleBinding/Editor.vue
@@ -0,0 +1,26 @@
+<!--
+  ~ 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></script>
+
+<template>
+    123456Editor
+</template>
+
+<style></style>
\ No newline at end of file
diff --git a/ui/src/components/IndexRuleBinding/index.vue 
b/ui/src/components/IndexRuleBinding/index.vue
new file mode 100644
index 00000000..f8d4369c
--- /dev/null
+++ b/ui/src/components/IndexRuleBinding/index.vue
@@ -0,0 +1,26 @@
+<!--
+  ~ 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></script>
+
+<template>
+    123456
+</template>
+
+<style></style>
\ No newline at end of file
diff --git a/ui/src/components/Read/index.vue b/ui/src/components/Read/index.vue
index 53c34519..945ffc9d 100644
--- a/ui/src/components/Read/index.vue
+++ b/ui/src/components/Read/index.vue
@@ -115,7 +115,7 @@ const data = reactive({
     tableFields: [],
     handleFields: "",
     group: route.params.group,
-    name: route.params.group,
+    name: route.params.name,
     type: route.params.type,
     tagFamily: 0,
     tagFamilies: [],
diff --git a/ui/src/components/TopNav/index.vue 
b/ui/src/components/TopNav/index.vue
index 1feb4b83..c91801b7 100644
--- a/ui/src/components/TopNav/index.vue
+++ b/ui/src/components/TopNav/index.vue
@@ -21,7 +21,6 @@
 import { reactive } from 'vue';
 import { useRouter, useRoute } from 'vue-router';
 import { getCurrentInstance } from '@vue/runtime-core'
-import { ElStep } from 'element-plus';
 
 const router = useRouter()
 const route = useRoute()
@@ -69,15 +68,15 @@ function initData() {
     if (operator == 'read') {
         routeData.name = type
         add.label = name
-        add.type = 'Read'
+        add.type = type == 'index-rule' || type == 'index-rule' ? 
`Read-${type}` : 'Read'
     } else if (operator == 'edit') {
         routeData.name = `edit-${type}`
         add.label = name
-        add.type = 'Edit'
+        add.type = type == 'index-rule' || type == 'index-rule' ? 
`Edit-${type}` : 'Edit'
     } else {
         routeData.name = `create-${type}`
         add.label = group
-        add.type = 'Create'
+        add.type = type == 'index-rule' || type == 'index-rule' ? 
`Create-${type}` : 'Create'
     }
     data.tabsList.push(add)
 }
diff --git a/ui/src/router/index.js b/ui/src/router/index.js
index 3ab1a2a6..e19f41cd 100644
--- a/ui/src/router/index.js
+++ b/ui/src/router/index.js
@@ -70,6 +70,36 @@ const router = createRouter({
               path: 
'/banyandb/stream/operator-edit/:type/:operator/:group/:name',
               name: 'edit-stream',
               component: () => import('@/views/Stream/createEdit.vue')
+            },
+            {
+              path: 
'/banyandb/stream/index-rule/operator/:type/:operator/:group',
+              name: 'create-index-rule',
+              component: () => import('@/components/IndexRule/Editor.vue')
+            },
+            {
+              path: 
'/banyandb/stream/index-rule/operator/:type/:operator/:group/:name',
+              name: 'edit-index-rule',
+              component: () => import('@/components/IndexRule/Editor.vue')
+            },
+            {
+              path: 
'/banyandb/stream/index-rule/operator/:type/:operator/:group/:name',
+              name: 'index-rule',
+              component: () => import('@/components/IndexRule/index.vue')
+            },
+            {
+              path: 
'/banyandb/stream/index-rule-binding/operator/:type/:operator/:group',
+              name: 'create-index-rule-binding',
+              component: () => 
import('@/components/IndexRuleBinding/Editor.vue')
+            },
+            {
+              path: 
'/banyandb/stream/index-rule-binding/operator/:type/:operator/:group/:name',
+              name: 'edit-index-rule-binding',
+              component: () => 
import('@/components/IndexRuleBinding/Editor.vue')
+            },
+            {
+              path: 
'/banyandb/stream/index-rule-binding/operator/:type/:operator/:group/:name',
+              name: 'index-rule-binding',
+              component: () => 
import('@/components/IndexRuleBinding/index.vue')
             }
           ]
         },


Reply via email to