hanahmily commented on code in PR #175:
URL:
https://github.com/apache/skywalking-banyandb/pull/175#discussion_r969040026
##########
ui/src/components/databaseComponents/asideComponents/AsideComponent.vue:
##########
@@ -357,38 +214,301 @@ export default {
e.cancelBubble = true; //IE阻止冒泡方法
Review Comment:
Remove non-English comments.
##########
ui/src/components/databaseComponents/asideComponents/AsideComponent.vue:
##########
@@ -19,335 +19,192 @@
<template>
<div style="width:100%; height:100%">
- <el-menu :default-active="currentMenu ? currentMenu.metadata.group +
currentMenu.metadata.name : ''" active-text-color="#6E38F7"
- style="height: 100%;" :collapse="isCollapse"
:collapse-transition="false">
+ <el-menu :default-active="currentMenu ? currentMenu.metadata.group +
currentMenu.metadata.name : ''"
+ active-text-color="#6E38F7" style="height: 100%;"
:collapse="isCollapse" :collapse-transition="false">
<div v-for="(item, index) in groupLists" :key="item.metadata.name"
@contextmenu.prevent="rightClickGroup($event, index)">
- <el-submenu :index="item.metadata.name + '-' + index">
+ <el-submenu :index="item.metadata.name + '-' + index"
:disabled="item.catalog == 'CATALOG_MEASURE'">
<template slot="title">
<i class="el-icon-folder"></i>
<span slot="title" :title="item.metadata.name"
style="width: 70%"
class="text-overflow-hidden">{{
- item.metadata.name
+ item.metadata.name
}}</span>
</template>
- <div @contextmenu.prevent="rightClickFolder($event, index,
'stream')">
- <el-submenu :index="item.metadata.name + '-' + index +
'-stream'">
- <template slot="title">
- <i class="el-icon-folder"></i>
- <span slot="title">Stream</span>
- </template>
- <div @contextmenu.prevent="rightClick($event,
index)">
- <el-menu-item :index="'streamFile1' + index"
@click="openStreamFile">
- <template slot="title">
- <i class="el-icon-document"></i>
- <span slot="title" title="streamFile"
style="width: 90%"
-
class="text-overflow-hidden">streamFile1</span>
- </template>
- </el-menu-item>
- </div>
- </el-submenu>
- </div>
- <div @contextmenu.prevent="rightClickFolder($event, index,
'measure')">
- <el-submenu :index="item.metadata.name + '-' + index +
'-measure'">
- <template slot="title">
- <i class="el-icon-folder"></i>
- <span slot="title">Measure</span>
- </template>
- <div v-for="(itemMeasure, indexMeasure) in
item.measure" :key="itemMeasure.metadata.name">
- <div @contextmenu.prevent="rightClick($event,
index, indexMeasure)">
- <el-menu-item
:index="itemMeasure.metadata.group + itemMeasure.metadata.name"
- @click="openMeasureFile(index,
indexMeasure)">
- <template slot="title">
- <i class="el-icon-document"></i>
- <span slot="title"
:title="itemMeasure.metadata.name" style="width: 90%"
-
class="text-overflow-hidden">{{ itemMeasure.metadata.name }}</span>
- </template>
- </el-menu-item>
- </div>
- </div>
- </el-submenu>
+ <div v-for="(itemChildren, indexChildren) in
item.children" :key="itemChildren.metadata.name">
+ <div @contextmenu.prevent="rightClick($event, index,
indexChildren)">
+ <el-menu-item :index="itemChildren.metadata.group
+ itemChildren.metadata.name"
+ @click="openFile(index, indexChildren)">
+ <template slot="title">
+ <i class="el-icon-document"></i>
+ <span slot="title"
:title="itemChildren.metadata.name" style="width: 90%"
+ class="text-overflow-hidden">{{
itemChildren.metadata.name }}</span>
+ </template>
+ </el-menu-item>
+ </div>
</div>
</el-submenu>
</div>
</el-menu>
- <div v-if="showRightMenu" class="right-menu border-radius-little
box-shadow"
- :style="{ top: topNumber + 'px', left: leftNumber + 'px' }">
- <right-menu-component
:rightMenuList="rightMenuList"></right-menu-component>
- </div>
+ <el-dialog title="Tips" :visible.sync="dialogVisible" width="25%"
center>
+ <span>Are you sure to delete this {{rightClickType}}?</span>
+ <span slot="footer" class="dialog-footer">
+ <el-button @click="dialogVisible = false">cancel</el-button>
+ <el-button type="primary"
@click="deleteGroupOrFile">delete</el-button>
+ </span>
+ </el-dialog>
+ <el-dialog width="25%" center :title="`${setGroup} group`"
:visible.sync="dialogGroupVisible">
+ <el-form ref="ruleForm" :rules="rules" :model="groupForm"
label-position="left">
+ <el-form-item label="group name" label-width="100px"
prop="name">
+ <el-input :disabled="setGroup == 'edit'"
v-model="groupForm.name" autocomplete="off"
+ style="width: 300px;"></el-input>
+ </el-form-item>
+ <el-form-item label="group type" label-width="100px"
prop="catalog">
+ <el-select v-model="groupForm.catalog" style="width:
300px;" placeholder="please select">
+ <el-option label="CATALOG_STREAM"
value="CATALOG_STREAM"></el-option>
+ <el-option label="CATALOG_MEASURE"
value="CATALOG_MEASURE"></el-option>
+ </el-select>
+ </el-form-item>
+ </el-form>
+ <div slot="footer" class="dialog-footer">
+ <el-button @click="dialogGroupVisible =
false">cancel</el-button>
+ <el-button type="primary" @click="confirmForm">{{setGroup}}
+ </el-button>
+ </div>
+ </el-dialog>
+ <dialog-file-component :visible.sync="dialogFileVisible"
:group="group" :operation="operation" :type="type"
+ @cancel="cancelFileDialog"
@confirm="confirmFileDialog"></dialog-file-component>
</div>
</template>
<script>
import { mapState } from 'vuex'
+import { getGroupList, getStreamOrMeasureList, deleteStreamOrMeasure,
deleteGroup, createGroup, editGroup, createFile } from '@/api/index'
+import { Message } from "element-ui"
import RightMenuComponent from './RightMenuComponent.vue'
+import DialogFileComponent from './DialogFileComponent.vue'
+const list1 = [{
+ icon: "el-icon-folder",
+ name: "new group",
+ id: "create Group"
+}, {
+ icon: "el-icon-folder",
+ name: "edit group",
+ id: "edit Group"
+}, {
+ icon: "el-icon-document",
+ name: "new file",
Review Comment:
The components under a group are the "resources" of the database instead of
"file". The conventional way is to show its dedicated name, "stream" or
"measure".
##########
ui/src/components/databaseComponents/asideComponents/AsideComponent.vue:
##########
@@ -357,38 +214,301 @@ export default {
e.cancelBubble = true; //IE阻止冒泡方法
}
},
+
+ /**
+ * open group or file right menu
+ */
+ rightClick(e, index, indexChild) {
+ this.$store.commit('changeRightMenuList', this.rightMenuListThree)
+ this.rightClickType = 'file'
+ this.rightGroupIndex = index
+ this.rightChildIndex = indexChild
+ this.openRightMenu(e)
+ },
+ rightClickGroup(e, index) {
+ this.$store.commit('changeRightMenuList', this.rightMenuListTwo)
+ this.rightClickType = 'group'
+ this.rightGroupIndex = index
+ this.openRightMenu(e)
+ },
openRightMenu(e) {
this.$store.commit("changeShowRightMenu", true)
- this.topNumber = e.pageY
- this.leftNumber = e.pageX
+ this.$store.commit('changeLeft', e.pageX)
+ this.$store.commit('changeTop', e.pageY)
this.stopPropagation()
},
- // open file right menu
- rightClick(e, index, indexMeasure) {
- this.rightMenuList = this.rightMenuListThree
- this.openRightMenu(e)
- console.log('rightClick')
+
+ /**
+ * open stream or measure
+ * @author wuchusheng
+ */
+ openFile(index, indexChildren) {
+ let item = this.groupLists[index].children[indexChildren]
+ /**
+ * Todo
+ * Measure or Stream?
+ */
+ if (this.groupLists[index].catalog == "CATALOG_MEASURE") {
+ item.metadata.type = "measure"
+ } else {
+ item.metadata.type = "stream"
+ }
+ this.$store.commit('selectMenu', item)
},
- // open folder right menu
- rightClickFolder(e, index, type) {
- this.rightMenuList = this.rightMenuListOne
- this.openRightMenu(e)
- console.log('rightClickFolder')
+
+ /**
+ * click right menu item
+ * @author wuchusheng
+ */
+ handleRightItem(index) {
+ console.log('groupLists', this.groupLists)
+ if (this.rightClickType == 'group') {
+ // right click group
+ let rightName = this.rightMenuListTwo[index].name
+ switch (rightName) {
+ case 'new group':
+ this.setGroup = 'create'
+ this.openCreateGroup()
+ break
+ case 'edit group':
+ this.setGroup = 'edit'
+ this.openEditGroup()
+ break
+ case 'new file':
+ this.operation = 'create'
+ this.openFileDialog()
+ break
+ case 'refresh':
+ this.getGroupLists()
+ break
+ case 'delete':
+ this.openDeleteDialog()
+ break
+ }
+ } else {
+ // right click measure or stream
+ let rightName = this.rightMenuListThree[index].name
+ switch (rightName) {
+ case 'delete':
+ this.openDeleteDialog()
+ }
+ }
+ // close right menu
+ this.$store.commit("changeShowRightMenu", false)
},
- // open group right menu
- rightClickGroup(e, index) {
- this.rightMenuList = this.rightMenuListTwo
- this.openRightMenu(e)
- console.log('rightClickGroup')
+
+ /**
+ * click right menu delete file
+ * @author wuchusheng
+ */
+ openDeleteDialog() {
+ this.dialogVisible = true
+ },
+ deleteGroupOrFile() {
+ let group = this.groupLists[this.rightGroupIndex].metadata.name
+ let type = this.groupLists[this.rightGroupIndex].catalog ==
'CATALOG_MEASURE' ? 'measure' : 'stream'
+ if (this.rightClickType == 'group') {
+ // delete group
+ this.deleteGroup(group, type)
+ } else {
+ // delete measure or stream file
+ this.deleteFile(group, type)
+ }
+ },
+ deleteGroup(group, type) {
+ let children = this.groupLists[this.rightGroupIndex].children
+ // Check whether the file is open
+ for (let i = 0; i < children.length; i++) {
+ let file = children[i]
+ let index = this.tags.findIndex((item) => item.metadata.group
=== group && item.metadata.type === type && item.metadata.name ===
file.metadata.name)
+ if (index != -1) {
+ Message({
+ message: 'There are files open in this group. Please
close these files before proceeding',
Review Comment:
Use "resource" in place of "files"
##########
ui/src/components/databaseComponents/asideComponents/AsideComponent.vue:
##########
@@ -19,335 +19,192 @@
<template>
<div style="width:100%; height:100%">
- <el-menu :default-active="currentMenu ? currentMenu.metadata.group +
currentMenu.metadata.name : ''" active-text-color="#6E38F7"
- style="height: 100%;" :collapse="isCollapse"
:collapse-transition="false">
+ <el-menu :default-active="currentMenu ? currentMenu.metadata.group +
currentMenu.metadata.name : ''"
+ active-text-color="#6E38F7" style="height: 100%;"
:collapse="isCollapse" :collapse-transition="false">
<div v-for="(item, index) in groupLists" :key="item.metadata.name"
@contextmenu.prevent="rightClickGroup($event, index)">
- <el-submenu :index="item.metadata.name + '-' + index">
+ <el-submenu :index="item.metadata.name + '-' + index"
:disabled="item.catalog == 'CATALOG_MEASURE'">
<template slot="title">
<i class="el-icon-folder"></i>
<span slot="title" :title="item.metadata.name"
style="width: 70%"
class="text-overflow-hidden">{{
- item.metadata.name
+ item.metadata.name
}}</span>
</template>
- <div @contextmenu.prevent="rightClickFolder($event, index,
'stream')">
- <el-submenu :index="item.metadata.name + '-' + index +
'-stream'">
- <template slot="title">
- <i class="el-icon-folder"></i>
- <span slot="title">Stream</span>
- </template>
- <div @contextmenu.prevent="rightClick($event,
index)">
- <el-menu-item :index="'streamFile1' + index"
@click="openStreamFile">
- <template slot="title">
- <i class="el-icon-document"></i>
- <span slot="title" title="streamFile"
style="width: 90%"
-
class="text-overflow-hidden">streamFile1</span>
- </template>
- </el-menu-item>
- </div>
- </el-submenu>
- </div>
- <div @contextmenu.prevent="rightClickFolder($event, index,
'measure')">
- <el-submenu :index="item.metadata.name + '-' + index +
'-measure'">
- <template slot="title">
- <i class="el-icon-folder"></i>
- <span slot="title">Measure</span>
- </template>
- <div v-for="(itemMeasure, indexMeasure) in
item.measure" :key="itemMeasure.metadata.name">
- <div @contextmenu.prevent="rightClick($event,
index, indexMeasure)">
- <el-menu-item
:index="itemMeasure.metadata.group + itemMeasure.metadata.name"
- @click="openMeasureFile(index,
indexMeasure)">
- <template slot="title">
- <i class="el-icon-document"></i>
- <span slot="title"
:title="itemMeasure.metadata.name" style="width: 90%"
-
class="text-overflow-hidden">{{ itemMeasure.metadata.name }}</span>
- </template>
- </el-menu-item>
- </div>
- </div>
- </el-submenu>
+ <div v-for="(itemChildren, indexChildren) in
item.children" :key="itemChildren.metadata.name">
+ <div @contextmenu.prevent="rightClick($event, index,
indexChildren)">
+ <el-menu-item :index="itemChildren.metadata.group
+ itemChildren.metadata.name"
+ @click="openFile(index, indexChildren)">
+ <template slot="title">
+ <i class="el-icon-document"></i>
+ <span slot="title"
:title="itemChildren.metadata.name" style="width: 90%"
+ class="text-overflow-hidden">{{
itemChildren.metadata.name }}</span>
+ </template>
+ </el-menu-item>
+ </div>
</div>
</el-submenu>
</div>
</el-menu>
- <div v-if="showRightMenu" class="right-menu border-radius-little
box-shadow"
- :style="{ top: topNumber + 'px', left: leftNumber + 'px' }">
- <right-menu-component
:rightMenuList="rightMenuList"></right-menu-component>
- </div>
+ <el-dialog title="Tips" :visible.sync="dialogVisible" width="25%"
center>
+ <span>Are you sure to delete this {{rightClickType}}?</span>
+ <span slot="footer" class="dialog-footer">
+ <el-button @click="dialogVisible = false">cancel</el-button>
+ <el-button type="primary"
@click="deleteGroupOrFile">delete</el-button>
+ </span>
+ </el-dialog>
+ <el-dialog width="25%" center :title="`${setGroup} group`"
:visible.sync="dialogGroupVisible">
+ <el-form ref="ruleForm" :rules="rules" :model="groupForm"
label-position="left">
+ <el-form-item label="group name" label-width="100px"
prop="name">
+ <el-input :disabled="setGroup == 'edit'"
v-model="groupForm.name" autocomplete="off"
+ style="width: 300px;"></el-input>
+ </el-form-item>
+ <el-form-item label="group type" label-width="100px"
prop="catalog">
+ <el-select v-model="groupForm.catalog" style="width:
300px;" placeholder="please select">
+ <el-option label="CATALOG_STREAM"
value="CATALOG_STREAM"></el-option>
+ <el-option label="CATALOG_MEASURE"
value="CATALOG_MEASURE"></el-option>
+ </el-select>
+ </el-form-item>
+ </el-form>
+ <div slot="footer" class="dialog-footer">
+ <el-button @click="dialogGroupVisible =
false">cancel</el-button>
+ <el-button type="primary" @click="confirmForm">{{setGroup}}
+ </el-button>
+ </div>
+ </el-dialog>
+ <dialog-file-component :visible.sync="dialogFileVisible"
:group="group" :operation="operation" :type="type"
+ @cancel="cancelFileDialog"
@confirm="confirmFileDialog"></dialog-file-component>
</div>
</template>
<script>
import { mapState } from 'vuex'
+import { getGroupList, getStreamOrMeasureList, deleteStreamOrMeasure,
deleteGroup, createGroup, editGroup, createFile } from '@/api/index'
+import { Message } from "element-ui"
import RightMenuComponent from './RightMenuComponent.vue'
+import DialogFileComponent from './DialogFileComponent.vue'
+const list1 = [{
+ icon: "el-icon-folder",
+ name: "new group",
+ id: "create Group"
+}, {
+ icon: "el-icon-folder",
+ name: "edit group",
+ id: "edit Group"
+}, {
+ icon: "el-icon-document",
+ name: "new file",
+ id: "create File"
+}, {
+ icon: "el-icon-refresh-right",
+ name: "refresh",
+ id: "refresh Group"
+}, {
+ icon: "el-icon-delete",
+ name: "delete",
+ id: "delete Group"
+}]
+const list2 = [/*{
+ icon: "el-icon-document",
+ name: "edit file",
+ id: "edit File"
+},*/ {
+ icon: "el-icon-delete",
+ name: "delete",
+ id: "delete File"
+}]
+const rule = {
+ name: [
+ {
+ required: true, message: 'Please enter the name of the group',
trigger: 'blur'
+ }
+ ],
+ catalog: [
+ {
+ required: true, message: 'Please select the type of the group',
trigger: 'blur'
+ }
+ ]
+}
export default {
name: 'AsideComponent',
+
+ computed: {
+ ...mapState({
+ isCollapse: (state) => state.aside.isCollapse,
+ // showRightMenu: (state) => state.menuState.showRightMenu,
+ // closeMenu: (state) => state.menuState.closeMenu,
+ tags: (state) => state.tags.tagsList,
+ currentMenu: (state) => state.tags.currentMenu
+ })
+ },
+ components: {
+ RightMenuComponent,
+ DialogFileComponent
+ },
data() {
return {
- groupLists: [{
- "metadata": {
- "group": "",
- "name": "measure-default",
- "id": 0,
- "createRevision": "0",
- "modRevision": "0"
- },
- "catalog": "CATALOG_MEASURE",
- "resourceOpts": {
- "shardNum": 1,
- "blockNum": 12,
- "ttl": "420d"
- },
- "updatedAt": null,
- "measure": [
- {
- "metadata": {
- "group": "measure-default",
- "name": "browser_app_error_rate",
- "id": 0,
- "createRevision": "764",
- "modRevision": "764"
- },
- "tagFamilies": [
- {
- "name": "default",
- "tags": [
- {
- "name": "entity_id",
- "type": "TAG_TYPE_STRING"
- },
- {
- "name": "denominator",
- "type": "TAG_TYPE_INT"
- },
- {
- "name": "numerator",
- "type": "TAG_TYPE_INT"
- },
- {
- "name": "time_bucket",
- "type": "TAG_TYPE_INT"
- },
- {
- "name": "id",
- "type": "TAG_TYPE_ID"
- }
- ]
- }
- ],
- "fields": [
- {
- "name": "percentage",
- "fieldType": "FIELD_TYPE_INT",
- "encodingMethod": "ENCODING_METHOD_GORILLA",
- "compressionMethod": "COMPRESSION_METHOD_ZSTD"
- }
- ],
- "entity": {
- "tagNames": [
- "id"
- ]
- },
- "interval": "1h",
- "updatedAt": null
- },
- {
- "metadata": {
- "group": "measure-default",
- "name": "browser_app_error_sum",
- "id": 0,
- "createRevision": "769",
- "modRevision": "769"
- },
- "tagFamilies": [
- {
- "name": "default",
- "tags": [
- {
- "name": "entity_id",
- "type": "TAG_TYPE_STRING"
- },
- {
- "name": "time_bucket",
- "type": "TAG_TYPE_INT"
- },
- {
- "name": "id",
- "type": "TAG_TYPE_ID"
- }
- ]
- }
- ],
- "fields": [
- {
- "name": "value",
- "fieldType": "FIELD_TYPE_INT",
- "encodingMethod": "ENCODING_METHOD_GORILLA",
- "compressionMethod": "COMPRESSION_METHOD_ZSTD"
- }
- ],
- "entity": {
- "tagNames": [
- "id"
- ]
- },
- "interval": "1h",
- "updatedAt": null
- },
- {
- "metadata": {
- "group": "measure-default",
- "name": "browser_app_page_ajax_error_sum",
- "id": 0,
- "createRevision": "790",
- "modRevision": "790"
- },
- "tagFamilies": [
- {
- "name": "default",
- "tags": [
- {
- "name": "entity_id",
- "type": "TAG_TYPE_STRING"
- },
- {
- "name": "service_id",
- "type": "TAG_TYPE_STRING"
- },
- {
- "name": "time_bucket",
- "type": "TAG_TYPE_INT"
- },
- {
- "name": "id",
- "type": "TAG_TYPE_ID"
- }
- ]
- }
- ],
- "fields": [
- {
- "name": "value",
- "fieldType": "FIELD_TYPE_INT",
- "encodingMethod": "ENCODING_METHOD_GORILLA",
- "compressionMethod": "COMPRESSION_METHOD_ZSTD"
- }
- ],
- "entity": {
- "tagNames": [
- "id"
- ]
- },
- "interval": "1h",
- "updatedAt": null
- }
- ]
- },
- {
- "metadata": {
- "group": "",
- "name": "stream-browser_error_log",
- "id": 0,
- "createRevision": "0",
- "modRevision": "0"
- },
- "catalog": "CATALOG_STREAM",
- "resourceOpts": {
- "shardNum": 2,
- "blockNum": 0,
- "ttl": "420d"
- },
- "updatedAt": null
+ groupLists: [],
+ rightMenuListTwo: list1, // right click group menu
+ rightMenuListThree: list2, // right click file menu
+ rightGroupIndex: 0, // right click group list index
+ rightChildIndex: 0, // right click file list index
+ rightClickType: 'group', // right click group or file
+ dialogVisible: false, // delete dialog
+ dialogGroupVisible: false, // group dialog
+ dialogFileVisible: false, // File dialog
+ setGroup: 'create', // group dialog is create or edit
+ operation: 'create', // file dialog is create or edit
+ type: 'stream', // file dialog is stream or measure
+ group: '',
+ groupForm: { // group dialog form
+ name: null,
+ catalog: 'CATALOG_STREAM'
},
- {
- "metadata": {
- "group": "",
- "name": "stream-default",
- "id": 0,
- "createRevision": "0",
- "modRevision": "0"
- },
- "catalog": "CATALOG_STREAM",
- "resourceOpts": {
- "shardNum": 1,
- "blockNum": 0,
- "ttl": "420d"
- },
- "updatedAt": null
- },],
- topNumber: 0,
- leftNumber: 0,
- rightMenuListOne: [{
- icon: "el-icon-document",
- name: "new File",
- id: "create File"
- }, {
- icon: "el-icon-refresh-right",
- name: "refresh",
- id: "refresh Folder"
- }, {
- icon: "el-icon-delete",
- name: "delete",
- id: "delete Folder"
- }],
- rightMenuList: this.rightMenuListOne,
- rightMenuListTwo: [{
- icon: "el-icon-folder",
- name: "new Group",
- id: "create Group"
- }, {
- icon: "el-icon-refresh-right",
- name: "refresh",
- id: "refresh Group"
- }, {
- icon: "el-icon-delete",
- name: "delete",
- id: "delete Group"
- }],
- rightMenuListThree: [{
- icon: "el-icon-refresh-right",
- name: "refresh",
- id: "refresh File"
- }, {
- icon: "el-icon-delete",
- name: "delete",
- id: "delete File"
- }]
+ rules: rule, // group dialog form rules
}
},
-
- components: {
- RightMenuComponent
- },
-
- async created() {
- console.log('this is aside created')
+ created() {
+ // get group list
this.getGroupLists()
- },
-
- computed: {
- ...mapState({
- isCollapse: (state) => state.aside.isCollapse,
- showRightMenu: (state) => state.menuState.showRightMenu,
- currentMenu: (state) => state.tags.currentMenu
+ // monitor click right menu item
+ this.$bus.$on('handleRightItem', (index) => {
+ this.handleRightItem(index)
})
},
methods: {
- async getGroupLists() {
- try {
- const data = await this.$http.get('/api/v1/group/schema/lists')
- if (data.status != 200) {
- this.$message.error(data.status, data.statusText)
- } else {
- this.groupLists = data.data.group
- console.log(data)
- }
- } catch (err) {
- console.log(err)
- this.$message.errorNet()
- }
+
+ /**
+ * get group data
+ * @author wuchusheng
Review Comment:
Remove "@author"
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]