feat: implement Topology on the dashboard (#14)
5
dist/LICENSE
vendored
@ -18,7 +18,7 @@ three-orbit-controls 82.1.0: https://github.com/mattdesl/three-orbit-controls MI
|
|||||||
vue-demi 0.12.1: https://github.com/antfu/vue-demi MIT
|
vue-demi 0.12.1: https://github.com/antfu/vue-demi MIT
|
||||||
vueuse/core 6.8.0: https://github.com/vueuse/vueuse MIT
|
vueuse/core 6.8.0: https://github.com/vueuse/vueuse MIT
|
||||||
vue/devtools-api 6.0.0-beta.20.1: https://github.com/vuejs/vue-devtools MIT
|
vue/devtools-api 6.0.0-beta.20.1: https://github.com/vuejs/vue-devtools MIT
|
||||||
element-plus 1.2.0-beta.3: https://github.com/element-plus/element-plus MIT
|
element-plus 2.0.1: https://github.com/element-plus/element-plus MIT
|
||||||
vue-types 4.1.1: https://github.com/dwightjack/vue-types MIT
|
vue-types 4.1.1: https://github.com/dwightjack/vue-types MIT
|
||||||
is-plain-object 5.0.0: https://github.com/jonschlinkert/is-plain-object MIT
|
is-plain-object 5.0.0: https://github.com/jonschlinkert/is-plain-object MIT
|
||||||
vue-grid-layout 3.0.0-beta1: https://github.com/jbaysolutions/vue-grid-layout MIT
|
vue-grid-layout 3.0.0-beta1: https://github.com/jbaysolutions/vue-grid-layout MIT
|
||||||
@ -29,3 +29,6 @@ batch-processor 1.0.0: https://github.com/wnr/batch-processor MIT
|
|||||||
echarts 5.2.2: https://github.com/apache/echarts Apache-2.0 License
|
echarts 5.2.2: https://github.com/apache/echarts Apache-2.0 License
|
||||||
zrender 5.2.1: https://github.com/ecomfe/zrender BSD-3-Clause License
|
zrender 5.2.1: https://github.com/ecomfe/zrender BSD-3-Clause License
|
||||||
tslib 2.3.0: https://github.com/Microsoft/tslib 0BSD License
|
tslib 2.3.0: https://github.com/Microsoft/tslib 0BSD License
|
||||||
|
d3-tip 0.9.1: https: //github.com/Caged/d3-tip MIT Licensee
|
||||||
|
d3 7.3.0: https://github.com/d3/d3 ISC License
|
||||||
|
ctrl/tinycolor 3.4.0: https: //github.com/scttcper/tinycolor MIT Licensee
|
||||||
|
21
dist/licenses/LICENSE-ctrl-tinycolor
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2015-present Evan You
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
1460
package-lock.json
generated
@ -11,8 +11,10 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^0.24.0",
|
"axios": "^0.24.0",
|
||||||
|
"d3": "^7.3.0",
|
||||||
|
"d3-tip": "^0.9.1",
|
||||||
"echarts": "^5.2.2",
|
"echarts": "^5.2.2",
|
||||||
"element-plus": "^1.2.0-beta.3",
|
"element-plus": "^2.0.2",
|
||||||
"pinia": "^2.0.5",
|
"pinia": "^2.0.5",
|
||||||
"three": "^0.131.3",
|
"three": "^0.131.3",
|
||||||
"three-orbit-controls": "^82.1.0",
|
"three-orbit-controls": "^82.1.0",
|
||||||
@ -24,6 +26,8 @@
|
|||||||
"vuex": "^4.0.0-0"
|
"vuex": "^4.0.0-0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/d3": "^7.1.0",
|
||||||
|
"@types/d3-tip": "^3.5.5",
|
||||||
"@types/echarts": "^4.9.12",
|
"@types/echarts": "^4.9.12",
|
||||||
"@types/jest": "^24.0.19",
|
"@types/jest": "^24.0.19",
|
||||||
"@types/three": "^0.131.0",
|
"@types/three": "^0.131.0",
|
||||||
|
@ -13,6 +13,5 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License. -->
|
limitations under the License. -->
|
||||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||||
<title>all_inbox</title>
|
|
||||||
<path d="M15 15.984h6v3q0 0.797-0.609 1.406t-1.406 0.609h-13.969q-0.797 0-1.406-0.609t-0.609-1.406v-3h6q0 1.219 0.891 2.109t2.109 0.891 2.109-0.891 0.891-2.109zM18.984 9v-3.984h-13.969v3.984h3.984q0 1.219 0.891 2.109t2.109 0.891 2.109-0.891 0.891-2.109h3.984zM18.984 3q0.797 0 1.406 0.609t0.609 1.406v6.984q0 0.797-0.609 1.406t-1.406 0.609h-13.969q-0.797 0-1.406-0.609t-0.609-1.406v-6.984q0-0.797 0.609-1.406t1.406-0.609h13.969z"></path>
|
<path d="M15 15.984h6v3q0 0.797-0.609 1.406t-1.406 0.609h-13.969q-0.797 0-1.406-0.609t-0.609-1.406v-3h6q0 1.219 0.891 2.109t2.109 0.891 2.109-0.891 0.891-2.109zM18.984 9v-3.984h-13.969v3.984h3.984q0 1.219 0.891 2.109t2.109 0.891 2.109-0.891 0.891-2.109h3.984zM18.984 3q0.797 0 1.406 0.609t0.609 1.406v6.984q0 0.797-0.609 1.406t-1.406 0.609h-13.969q-0.797 0-1.406-0.609t-0.609-1.406v-6.984q0-0.797 0.609-1.406t1.406-0.609h13.969z"></path>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
@ -13,4 +13,4 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License. -->
|
limitations under the License. -->
|
||||||
|
|
||||||
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><title>icn/chevron-left</title><path d="M7.414 7.989l2.295 2.306a1 1 0 1 1-1.418 1.41l-3-3.015a1 1 0 0 1 .004-1.414l3-2.985a1 1 0 1 1 1.41 1.418l-2.29 2.28z" id="a"/></svg>
|
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M7.414 7.989l2.295 2.306a1 1 0 1 1-1.418 1.41l-3-3.015a1 1 0 0 1 .004-1.414l3-2.985a1 1 0 1 1 1.41 1.418l-2.29 2.28z" id="a"/></svg>
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
17
src/assets/icons/device_hub.svg
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<!-- Licensed to the 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.
|
||||||
|
The 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. -->
|
||||||
|
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||||
|
<path d="M17.016 15.984h3.984v5.016h-5.016v-3.047l-3.984-4.219-3.984 4.219v3.047h-5.016v-5.016h3.984l4.031-3.984v-3.188q-0.891-0.328-1.453-1.078t-0.563-1.734q0-1.219 0.891-2.109t2.109-0.891 2.109 0.891 0.891 2.109q0 0.984-0.563 1.734t-1.453 1.078v3.188z"></path>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
@ -13,6 +13,5 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License. -->
|
limitations under the License. -->
|
||||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||||
<title>folder_open</title>
|
|
||||||
<path d="M20.016 18v-9.984h-16.031v9.984h16.031zM20.016 6q0.797 0 1.383 0.609t0.586 1.406v9.984q0 0.797-0.586 1.406t-1.383 0.609h-16.031q-0.797 0-1.383-0.609t-0.586-1.406v-12q0-0.797 0.586-1.406t1.383-0.609h6l2.016 2.016h8.016z"></path>
|
<path d="M20.016 18v-9.984h-16.031v9.984h16.031zM20.016 6q0.797 0 1.383 0.609t0.586 1.406v9.984q0 0.797-0.586 1.406t-1.383 0.609h-16.031q-0.797 0-1.383-0.609t-0.586-1.406v-12q0-0.797 0.586-1.406t1.383-0.609h6l2.016 2.016h8.016z"></path>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
@ -13,6 +13,5 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License. -->
|
limitations under the License. -->
|
||||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||||
<title>insert_image</title>
|
|
||||||
<path d="M8.484 13.5l-3.469 4.5h13.969l-4.5-6-3.469 4.5zM21 18.984q0 0.797-0.609 1.406t-1.406 0.609h-13.969q-0.797 0-1.406-0.609t-0.609-1.406v-13.969q0-0.797 0.609-1.406t1.406-0.609h13.969q0.797 0 1.406 0.609t0.609 1.406v13.969z"></path>
|
<path d="M8.484 13.5l-3.469 4.5h13.969l-4.5-6-3.469 4.5zM21 18.984q0 0.797-0.609 1.406t-1.406 0.609h-13.969q-0.797 0-1.406-0.609t-0.609-1.406v-13.969q0-0.797 0.609-1.406t1.406-0.609h13.969q0.797 0 1.406 0.609t0.609 1.406v13.969z"></path>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
17
src/assets/icons/keyboard_backspace.svg
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<!-- Licensed to the 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.
|
||||||
|
The 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. -->
|
||||||
|
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||||
|
<path d="M21 11.016v1.969h-14.156l3.563 3.609-1.406 1.406-6-6 6-6 1.406 1.406-3.563 3.609h14.156z"></path>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 977 B |
@ -13,6 +13,5 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License. -->
|
limitations under the License. -->
|
||||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||||
<title>playlist_add</title>
|
|
||||||
<path d="M2.016 15.984v-1.969h7.969v1.969h-7.969zM18 14.016h3.984v1.969h-3.984v4.031h-2.016v-4.031h-3.984v-1.969h3.984v-4.031h2.016v4.031zM14.016 6v2.016h-12v-2.016h12zM14.016 9.984v2.016h-12v-2.016h12z"></path>
|
<path d="M2.016 15.984v-1.969h7.969v1.969h-7.969zM18 14.016h3.984v1.969h-3.984v4.031h-2.016v-4.031h-3.984v-1.969h3.984v-4.031h2.016v4.031zM14.016 6v2.016h-12v-2.016h12zM14.016 9.984v2.016h-12v-2.016h12z"></path>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
@ -13,6 +13,5 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License. -->
|
limitations under the License. -->
|
||||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||||
<title>save</title>
|
|
||||||
<path d="M15 9v-3.984h-9.984v3.984h9.984zM12 18.984q1.219 0 2.109-0.891t0.891-2.109-0.891-2.109-2.109-0.891-2.109 0.891-0.891 2.109 0.891 2.109 2.109 0.891zM17.016 3l3.984 3.984v12q0 0.797-0.609 1.406t-1.406 0.609h-13.969q-0.844 0-1.43-0.586t-0.586-1.43v-13.969q0-0.844 0.586-1.43t1.43-0.586h12z"></path>
|
<path d="M15 9v-3.984h-9.984v3.984h9.984zM12 18.984q1.219 0 2.109-0.891t0.891-2.109-0.891-2.109-2.109-0.891-2.109 0.891-0.891 2.109 0.891 2.109 2.109 0.891zM17.016 3l3.984 3.984v12q0 0.797-0.609 1.406t-1.406 0.609h-13.969q-0.844 0-1.43-0.586t-0.586-1.43v-13.969q0-0.844 0.586-1.43t1.43-0.586h12z"></path>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.1 KiB |
@ -13,6 +13,5 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License. -->
|
limitations under the License. -->
|
||||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||||
<title>save_alt</title>
|
|
||||||
<path d="M12.984 12.656l2.625-2.578 1.406 1.406-5.016 5.016-5.016-5.016 1.406-1.406 2.625 2.578v-9.656h1.969v9.656zM18.984 12h2.016v6.984q0 0.797-0.609 1.406t-1.406 0.609h-13.969q-0.797 0-1.406-0.609t-0.609-1.406v-6.984h2.016v6.984h13.969v-6.984z"></path>
|
<path d="M12.984 12.656l2.625-2.578 1.406 1.406-5.016 5.016-5.016-5.016 1.406-1.406 2.625 2.578v-9.656h1.969v9.656zM18.984 12h2.016v6.984q0 0.797-0.609 1.406t-1.406 0.609h-13.969q-0.797 0-1.406-0.609t-0.609-1.406v-6.984h2.016v6.984h13.969v-6.984z"></path>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
@ -13,6 +13,5 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License. -->
|
limitations under the License. -->
|
||||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||||
<title>settings</title>
|
|
||||||
<path d="M12 15.516q1.453 0 2.484-1.031t1.031-2.484-1.031-2.484-2.484-1.031-2.484 1.031-1.031 2.484 1.031 2.484 2.484 1.031zM19.453 12.984l2.109 1.641q0.328 0.234 0.094 0.656l-2.016 3.469q-0.188 0.328-0.609 0.188l-2.484-0.984q-0.984 0.703-1.688 0.984l-0.375 2.625q-0.094 0.422-0.469 0.422h-4.031q-0.375 0-0.469-0.422l-0.375-2.625q-0.891-0.375-1.688-0.984l-2.484 0.984q-0.422 0.141-0.609-0.188l-2.016-3.469q-0.234-0.422 0.094-0.656l2.109-1.641q-0.047-0.328-0.047-0.984t0.047-0.984l-2.109-1.641q-0.328-0.234-0.094-0.656l2.016-3.469q0.188-0.328 0.609-0.188l2.484 0.984q0.984-0.703 1.688-0.984l0.375-2.625q0.094-0.422 0.469-0.422h4.031q0.375 0 0.469 0.422l0.375 2.625q0.891 0.375 1.688 0.984l2.484-0.984q0.422-0.141 0.609 0.188l2.016 3.469q0.234 0.422-0.094 0.656l-2.109 1.641q0.047 0.328 0.047 0.984t-0.047 0.984z"></path>
|
<path d="M12 15.516q1.453 0 2.484-1.031t1.031-2.484-1.031-2.484-2.484-1.031-2.484 1.031-1.031 2.484 1.031 2.484 2.484 1.031zM19.453 12.984l2.109 1.641q0.328 0.234 0.094 0.656l-2.016 3.469q-0.188 0.328-0.609 0.188l-2.484-0.984q-0.984 0.703-1.688 0.984l-0.375 2.625q-0.094 0.422-0.469 0.422h-4.031q-0.375 0-0.469-0.422l-0.375-2.625q-0.891-0.375-1.688-0.984l-2.484 0.984q-0.422 0.141-0.609-0.188l-2.016-3.469q-0.234-0.422 0.094-0.656l2.109-1.641q-0.047-0.328-0.047-0.984t0.047-0.984l-2.109-1.641q-0.328-0.234-0.094-0.656l2.016-3.469q0.188-0.328 0.609-0.188l2.484 0.984q0.984-0.703 1.688-0.984l0.375-2.625q0.094-0.422 0.469-0.422h4.031q0.375 0 0.469 0.422l0.375 2.625q0.891 0.375 1.688 0.984l2.484-0.984q0.422-0.141 0.609 0.188l2.016 3.469q0.234 0.422-0.094 0.656l-2.109 1.641q0.047 0.328 0.047 0.984t-0.047 0.984z"></path>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
15
src/assets/icons/topology-dark.svg
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<!-- Licensed to the 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.
|
||||||
|
The 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. -->
|
||||||
|
<svg t="1645261422781" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1440" width="48" height="48"><path d="M900.032 646.016h-56.064V502.976a16 16 0 0 0-16-16H544v-96h62.976c22.144 0 40-17.92 40-40V161.024a40 40 0 0 0-40-40H417.024a40 40 0 0 0-40 40v189.952c0 22.144 17.92 40 40 40H480v96H195.968a16 16 0 0 0-16 16v143.04h-55.936a38.016 38.016 0 0 0-38.016 38.016v176c0 20.928 17.024 37.952 37.952 37.952h176a38.016 38.016 0 0 0 38.016-38.016v-176a38.016 38.016 0 0 0-37.952-37.952h-56V550.976H480v95.04h-56a38.016 38.016 0 0 0-38.016 38.016v176c0 20.928 17.024 37.952 38.016 37.952h176a38.016 38.016 0 0 0 38.016-38.016v-176a38.016 38.016 0 0 0-38.016-37.952H544V550.976h236.032v95.04h-56.064a38.016 38.016 0 0 0-37.952 38.016v176c0 20.928 17.024 37.952 38.016 37.952h176a38.016 38.016 0 0 0 37.952-38.016v-176a38.016 38.016 0 0 0-38.016-37.952zM440.96 184.96h141.952v141.952H441.024V185.024zM278.016 838.016H145.92V705.92h132.032v132.032z m299.968 0H446.08V705.92H577.92v132.032z m300.032 0h-132.032V705.92h132.032v132.032z" p-id="1441"></path></svg>
|
After Width: | Height: | Size: 1.8 KiB |
15
src/assets/icons/topology-light.svg
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<!-- Licensed to the 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.
|
||||||
|
The 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. -->
|
||||||
|
<svg t="1645261422781" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1440" width="48" height="48"><path d="M900.032 646.016h-56.064V502.976a16 16 0 0 0-16-16H544v-96h62.976c22.144 0 40-17.92 40-40V161.024a40 40 0 0 0-40-40H417.024a40 40 0 0 0-40 40v189.952c0 22.144 17.92 40 40 40H480v96H195.968a16 16 0 0 0-16 16v143.04h-55.936a38.016 38.016 0 0 0-38.016 38.016v176c0 20.928 17.024 37.952 37.952 37.952h176a38.016 38.016 0 0 0 38.016-38.016v-176a38.016 38.016 0 0 0-37.952-37.952h-56V550.976H480v95.04h-56a38.016 38.016 0 0 0-38.016 38.016v176c0 20.928 17.024 37.952 38.016 37.952h176a38.016 38.016 0 0 0 38.016-38.016v-176a38.016 38.016 0 0 0-38.016-37.952H544V550.976h236.032v95.04h-56.064a38.016 38.016 0 0 0-37.952 38.016v176c0 20.928 17.024 37.952 38.016 37.952h176a38.016 38.016 0 0 0 37.952-38.016v-176a38.016 38.016 0 0 0-38.016-37.952zM440.96 184.96h141.952v141.952H441.024V185.024zM278.016 838.016H145.92V705.92h132.032v132.032z m299.968 0H446.08V705.92H577.92v132.032z m300.032 0h-132.032V705.92h132.032v132.032z" p-id="1441" fill="#ffffff"></path></svg>
|
After Width: | Height: | Size: 1.8 KiB |
49
src/assets/img/icons.ts
Executable file
@ -0,0 +1,49 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the 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.
|
||||||
|
* The 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.
|
||||||
|
*/
|
||||||
|
const requireComponent = require.context("./technologies", false, /\.png$/);
|
||||||
|
const requireTool = require.context("./tools", false, /\.png$/);
|
||||||
|
const result: { [key: string]: string } = {};
|
||||||
|
const t: { [key: string]: string } = {};
|
||||||
|
|
||||||
|
function capitalizeFirstLetter(str: string) {
|
||||||
|
return str.toUpperCase();
|
||||||
|
}
|
||||||
|
function validateFileName(str: string): string | undefined {
|
||||||
|
if (/^\S+\.png$/.test(str)) {
|
||||||
|
return str.replace(/^\S+\/(\w+)\.png$/, (rs, $1) =>
|
||||||
|
capitalizeFirstLetter($1)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
[...requireComponent.keys()].forEach((filePath: string) => {
|
||||||
|
const componentConfig = requireComponent(filePath);
|
||||||
|
|
||||||
|
const fileName = validateFileName(filePath);
|
||||||
|
if (fileName) {
|
||||||
|
result[fileName] = componentConfig;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
[...requireTool.keys()].forEach((filePath: string) => {
|
||||||
|
const componentConfig = requireTool(filePath);
|
||||||
|
|
||||||
|
const fileName = validateFileName(filePath);
|
||||||
|
if (fileName) {
|
||||||
|
t[fileName] = componentConfig;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default { ...result, ...t };
|
Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 5.8 KiB |
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 4.0 KiB |
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 4.6 KiB |
Before Width: | Height: | Size: 6.7 KiB After Width: | Height: | Size: 6.7 KiB |
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 4.7 KiB |
@ -118,9 +118,9 @@ limitations under the License. -->
|
|||||||
v-for="(i, j) in local.months"
|
v-for="(i, j) in local.months"
|
||||||
@click="
|
@click="
|
||||||
is($event) &&
|
is($event) &&
|
||||||
((state.showMonths = m === 'M'),
|
((state.showMonths = state.m === 'M'),
|
||||||
(state.month = j),
|
(state.month = j),
|
||||||
m === 'M' && ok('m'))
|
state.m === 'M' && ok('m'))
|
||||||
"
|
"
|
||||||
:class="[
|
:class="[
|
||||||
status(
|
status(
|
||||||
@ -142,7 +142,7 @@ limitations under the License. -->
|
|||||||
v-for="(i, j) in years"
|
v-for="(i, j) in years"
|
||||||
@click="
|
@click="
|
||||||
is($event) &&
|
is($event) &&
|
||||||
((state.showYears = m === 'Y'),
|
((state.showYears = state.m === 'Y'),
|
||||||
(state.year = i),
|
(state.year = i),
|
||||||
state.m === 'Y' && ok('y'))
|
state.m === 'Y' && ok('y'))
|
||||||
"
|
"
|
||||||
@ -278,6 +278,7 @@ limitations under the License. -->
|
|||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, onMounted, watch, reactive } from "vue";
|
import { computed, onMounted, watch, reactive } from "vue";
|
||||||
|
import type { PropType } from "vue";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
/*global defineProps, defineEmits */
|
/*global defineProps, defineEmits */
|
||||||
const emit = defineEmits(["input", "setDates", "ok"]);
|
const emit = defineEmits(["input", "setDates", "ok"]);
|
||||||
@ -286,7 +287,7 @@ const props = defineProps({
|
|||||||
value: { type: Date },
|
value: { type: Date },
|
||||||
left: { type: Boolean, default: false },
|
left: { type: Boolean, default: false },
|
||||||
right: { type: Boolean, default: false },
|
right: { type: Boolean, default: false },
|
||||||
dates: { default: [] },
|
dates: { type: Array as PropType<number[] | string[]>, default: () => [] },
|
||||||
disabledDate: { type: Function, default: () => false },
|
disabledDate: { type: Function, default: () => false },
|
||||||
format: {
|
format: {
|
||||||
type: String,
|
type: String,
|
||||||
@ -353,10 +354,10 @@ const parse = (num: number): number => {
|
|||||||
return Math.floor(num / 1000);
|
return Math.floor(num / 1000);
|
||||||
};
|
};
|
||||||
const start = computed(() => {
|
const start = computed(() => {
|
||||||
return parse(props.dates[0]);
|
return parse(Number(props.dates[0]));
|
||||||
});
|
});
|
||||||
const end = computed(() => {
|
const end = computed(() => {
|
||||||
return parse(props.dates[1]);
|
return parse(Number(props.dates[1]));
|
||||||
});
|
});
|
||||||
const ys = computed(() => {
|
const ys = computed(() => {
|
||||||
return Math.floor(state.year / 10) * 10;
|
return Math.floor(state.year / 10) * 10;
|
||||||
@ -550,7 +551,7 @@ const ok = (info: any) => {
|
|||||||
emit("setDates", _time);
|
emit("setDates", _time);
|
||||||
}
|
}
|
||||||
emit("input", _time);
|
emit("input", _time);
|
||||||
ok(info === "h");
|
emit("ok", info === "h");
|
||||||
};
|
};
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
const is = (c: string) => props.format.indexOf(c) !== -1;
|
const is = (c: string) => props.format.indexOf(c) !== -1;
|
||||||
|
@ -20,12 +20,15 @@ import { watch, ref, Ref, onMounted, onBeforeUnmount, unref } from "vue";
|
|||||||
import type { PropType } from "vue";
|
import type { PropType } from "vue";
|
||||||
import { useECharts } from "@/hooks/useEcharts";
|
import { useECharts } from "@/hooks/useEcharts";
|
||||||
import { addResizeListener, removeResizeListener } from "@/utils/event";
|
import { addResizeListener, removeResizeListener } from "@/utils/event";
|
||||||
|
import { useTimeoutFn } from "@/hooks/useTimeout";
|
||||||
|
|
||||||
/*global Nullable, defineProps*/
|
/*global Nullable, defineProps, defineEmits*/
|
||||||
|
const emits = defineEmits(["select"]);
|
||||||
const chartRef = ref<Nullable<HTMLDivElement>>(null);
|
const chartRef = ref<Nullable<HTMLDivElement>>(null);
|
||||||
const { setOptions, resize } = useECharts(chartRef as Ref<HTMLDivElement>);
|
const { setOptions, resize, getInstance } = useECharts(
|
||||||
|
chartRef as Ref<HTMLDivElement>
|
||||||
|
);
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
clickEvent: { type: Function as PropType<(param: unknown) => void> },
|
|
||||||
height: { type: String, default: "100%" },
|
height: { type: String, default: "100%" },
|
||||||
width: { type: String, default: "100%" },
|
width: { type: String, default: "100%" },
|
||||||
option: {
|
option: {
|
||||||
@ -34,9 +37,16 @@ const props = defineProps({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(async () => {
|
||||||
setOptions(props.option);
|
await setOptions(props.option);
|
||||||
addResizeListener(unref(chartRef), resize);
|
addResizeListener(unref(chartRef), resize);
|
||||||
|
useTimeoutFn(() => {
|
||||||
|
const instance = getInstance();
|
||||||
|
|
||||||
|
instance.on("click", (params: any) => {
|
||||||
|
emits("select", params);
|
||||||
|
});
|
||||||
|
}, 1000);
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
|
@ -43,18 +43,17 @@ interface Option {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*global defineProps, defineEmits*/
|
/*global defineProps, defineEmits*/
|
||||||
|
|
||||||
const emit = defineEmits(["change"]);
|
const emit = defineEmits(["change"]);
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
options: {
|
options: {
|
||||||
type: Array as PropType<Option[]>,
|
type: Array as PropType<(Option & { disabled: boolean })[]>,
|
||||||
default: () => [],
|
default: () => [],
|
||||||
},
|
},
|
||||||
value: {
|
value: {
|
||||||
type: [Array, String] as PropType<string[] | string>,
|
type: [Array, String] as PropType<string[] | string>,
|
||||||
default: () => [],
|
default: () => [],
|
||||||
},
|
},
|
||||||
size: { type: String, default: "small" },
|
size: { type: null, default: "default" },
|
||||||
placeholder: { type: String, default: "Select a option" },
|
placeholder: { type: String, default: "Select a option" },
|
||||||
borderRadius: { type: Number, default: 3 },
|
borderRadius: { type: Number, default: 3 },
|
||||||
multiple: { type: Boolean, default: false },
|
multiple: { type: Boolean, default: false },
|
||||||
@ -77,7 +76,7 @@ watch(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" scope>
|
<style lang="scss" scoped>
|
||||||
.icon {
|
.icon {
|
||||||
width: 16px;
|
width: 16px;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
|
@ -145,11 +145,12 @@ limitations under the License. -->
|
|||||||
import { ref, computed, onMounted, onBeforeUnmount, watch } from "vue";
|
import { ref, computed, onMounted, onBeforeUnmount, watch } from "vue";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import DateCalendar from "./DateCalendar.vue";
|
import DateCalendar from "./DateCalendar.vue";
|
||||||
|
import { useTimeoutFn } from "@/hooks/useTimeout";
|
||||||
/*global defineProps, defineEmits */
|
/*global defineProps, defineEmits */
|
||||||
const datepicker = ref(null);
|
const datepicker = ref(null);
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const show = ref<boolean>(false);
|
const show = ref<boolean>(false);
|
||||||
const dates = ref<Date[]>([]);
|
const dates = ref<Date | string[] | any>([]);
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
position: { type: String, default: "bottom" },
|
position: { type: String, default: "bottom" },
|
||||||
name: [String],
|
name: [String],
|
||||||
@ -244,7 +245,7 @@ const range = computed(() => {
|
|||||||
const text = computed(() => {
|
const text = computed(() => {
|
||||||
const val = props.value;
|
const val = props.value;
|
||||||
const txt = dates.value
|
const txt = dates.value
|
||||||
.map((date) => tf(date))
|
.map((date: Date) => tf(date))
|
||||||
.join(` ${props.rangeSeparator} `);
|
.join(` ${props.rangeSeparator} `);
|
||||||
if (Array.isArray(val)) {
|
if (Array.isArray(val)) {
|
||||||
return val.length > 1 ? txt : "";
|
return val.length > 1 ? txt : "";
|
||||||
@ -270,9 +271,9 @@ const ok = (leaveOpened: boolean) => {
|
|||||||
emit("input", get());
|
emit("input", get());
|
||||||
!leaveOpened &&
|
!leaveOpened &&
|
||||||
!props.showButtons &&
|
!props.showButtons &&
|
||||||
setTimeout(() => {
|
useTimeoutFn(() => {
|
||||||
show.value = range.value;
|
show.value = range.value;
|
||||||
});
|
}, 1);
|
||||||
};
|
};
|
||||||
const setDates = (d: Date) => {
|
const setDates = (d: Date) => {
|
||||||
dates.value[1] = d;
|
dates.value[1] = d;
|
||||||
|
33
src/assets/img/icons.js → src/graphql/fetch.ts
Executable file → Normal file
@ -14,21 +14,24 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
const requireComponent = require.context("../../assets", false, /\.png$/);
|
import axios, { AxiosResponse } from "axios";
|
||||||
|
import { cancelToken } from "@/utils/cancelToken";
|
||||||
|
|
||||||
const result = {};
|
async function query(param: {
|
||||||
function capitalizeFirstLetter(str) {
|
queryStr: string;
|
||||||
return str.toUpperCase();
|
conditions: { [key: string]: unknown };
|
||||||
}
|
}) {
|
||||||
function validateFileName(str) {
|
const res: AxiosResponse = await axios.post(
|
||||||
return (
|
"/graphql",
|
||||||
/^\S+\.png$/.test(str) &&
|
{ query: param.queryStr, variables: { ...param.conditions } },
|
||||||
str.replace(/^\S+\/(\w+)\.png$/, (rs, $1) => capitalizeFirstLetter($1))
|
{ cancelToken: cancelToken() }
|
||||||
);
|
);
|
||||||
|
if (res.data.errors) {
|
||||||
|
res.data.errors = res.data.errors
|
||||||
|
.map((e: { message: string }) => e.message)
|
||||||
|
.join(" ");
|
||||||
|
}
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
requireComponent.keys().forEach((filePath) => {
|
|
||||||
const componentConfig = requireComponent(filePath);
|
export default query;
|
||||||
const fileName = validateFileName(filePath);
|
|
||||||
result[fileName] = componentConfig;
|
|
||||||
});
|
|
||||||
export default result;
|
|
77
src/graphql/fragments/topology.ts
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the 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.
|
||||||
|
* The 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.
|
||||||
|
*/
|
||||||
|
export const ServicesTopology = {
|
||||||
|
variable: "$duration: Duration!, $serviceIds: [ID!]!",
|
||||||
|
query: `
|
||||||
|
topology: getServicesTopology(duration: $duration, serviceIds: $serviceIds) {
|
||||||
|
nodes {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
type
|
||||||
|
isReal
|
||||||
|
}
|
||||||
|
calls {
|
||||||
|
id
|
||||||
|
source
|
||||||
|
detectPoints
|
||||||
|
target
|
||||||
|
}
|
||||||
|
}`,
|
||||||
|
};
|
||||||
|
export const EndpointTopology = {
|
||||||
|
variable: ["$endpointId: ID!", "$duration: Duration!"],
|
||||||
|
query: `
|
||||||
|
topology: getEndpointDependencies(endpointId: $endpointId, duration: $duration) {
|
||||||
|
nodes {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
serviceId
|
||||||
|
serviceName
|
||||||
|
type
|
||||||
|
isReal
|
||||||
|
}
|
||||||
|
calls {
|
||||||
|
id
|
||||||
|
source
|
||||||
|
target
|
||||||
|
detectPoints
|
||||||
|
}
|
||||||
|
}`,
|
||||||
|
};
|
||||||
|
export const InstanceTopology = {
|
||||||
|
variable:
|
||||||
|
"$clientServiceId: ID!, $serverServiceId: ID!, $duration: Duration!",
|
||||||
|
query: `
|
||||||
|
topology: getServiceInstanceTopology(clientServiceId: $clientServiceId,
|
||||||
|
serverServiceId: $serverServiceId, duration: $duration) {
|
||||||
|
nodes {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
type
|
||||||
|
isReal
|
||||||
|
serviceName
|
||||||
|
serviceId
|
||||||
|
}
|
||||||
|
calls {
|
||||||
|
id
|
||||||
|
source
|
||||||
|
detectPoints
|
||||||
|
target
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
};
|
@ -19,9 +19,15 @@ import { cancelToken } from "@/utils/cancelToken";
|
|||||||
import * as app from "./query/app";
|
import * as app from "./query/app";
|
||||||
import * as selector from "./query/selector";
|
import * as selector from "./query/selector";
|
||||||
import * as dashboard from "./query/dashboard";
|
import * as dashboard from "./query/dashboard";
|
||||||
|
import * as topology from "./query/topology";
|
||||||
|
|
||||||
const query: { [key: string]: string } = { ...app, ...selector, ...dashboard };
|
const query: { [key: string]: string } = {
|
||||||
class Graph {
|
...app,
|
||||||
|
...selector,
|
||||||
|
...dashboard,
|
||||||
|
...topology,
|
||||||
|
};
|
||||||
|
class Graphql {
|
||||||
private queryData = "";
|
private queryData = "";
|
||||||
public query(queryData: string) {
|
public query(queryData: string) {
|
||||||
this.queryData = queryData;
|
this.queryData = queryData;
|
||||||
@ -51,4 +57,4 @@ class Graph {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default new Graph();
|
export default new Graphql();
|
25
src/graphql/query/topology.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the 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.
|
||||||
|
* The 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.
|
||||||
|
*/
|
||||||
|
import {
|
||||||
|
InstanceTopology,
|
||||||
|
EndpointTopology,
|
||||||
|
ServicesTopology,
|
||||||
|
} from "../fragments/topology";
|
||||||
|
|
||||||
|
export const getInstanceTopology = `query queryData(${InstanceTopology.variable}) {${InstanceTopology.query}}`;
|
||||||
|
export const getEndpointTopology = `query queryData(${EndpointTopology.variable}) {${EndpointTopology.query}}`;
|
||||||
|
export const getServicesTopology = `query queryData(${ServicesTopology.variable}) {${ServicesTopology.query}}`;
|
@ -19,6 +19,7 @@ import {
|
|||||||
LineSeriesOption,
|
LineSeriesOption,
|
||||||
HeatmapSeriesOption,
|
HeatmapSeriesOption,
|
||||||
PieSeriesOption,
|
PieSeriesOption,
|
||||||
|
SankeySeriesOption,
|
||||||
} from "echarts/charts";
|
} from "echarts/charts";
|
||||||
import {
|
import {
|
||||||
TitleComponentOption,
|
TitleComponentOption,
|
||||||
@ -46,6 +47,7 @@ export type ECOption = echarts.ComposeOption<
|
|||||||
| LegendComponentOption
|
| LegendComponentOption
|
||||||
| HeatmapSeriesOption
|
| HeatmapSeriesOption
|
||||||
| PieSeriesOption
|
| PieSeriesOption
|
||||||
|
| SankeySeriesOption
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export function useECharts(
|
export function useECharts(
|
||||||
|
@ -28,6 +28,7 @@ export function useQueryProcessor(config: any) {
|
|||||||
const appStore = useAppStoreWithOut();
|
const appStore = useAppStoreWithOut();
|
||||||
const dashboardStore = useDashboardStore();
|
const dashboardStore = useDashboardStore();
|
||||||
const selectorStore = useSelectorStore();
|
const selectorStore = useSelectorStore();
|
||||||
|
|
||||||
if (!selectorStore.currentService && dashboardStore.entity !== "All") {
|
if (!selectorStore.currentService && dashboardStore.entity !== "All") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -40,6 +41,9 @@ export function useQueryProcessor(config: any) {
|
|||||||
"ServiceInstanceRelation",
|
"ServiceInstanceRelation",
|
||||||
"EndpointRelation",
|
"EndpointRelation",
|
||||||
].includes(dashboardStore.entity);
|
].includes(dashboardStore.entity);
|
||||||
|
if (isRelation && !selectorStore.currentDestService) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const fragment = config.metrics.map((name: string, index: number) => {
|
const fragment = config.metrics.map((name: string, index: number) => {
|
||||||
const metricType = config.metricTypes[index] || "";
|
const metricType = config.metricTypes[index] || "";
|
||||||
const labels = ["0", "1", "2", "3", "4"];
|
const labels = ["0", "1", "2", "3", "4"];
|
||||||
@ -257,3 +261,28 @@ export function usePodsSource(
|
|||||||
});
|
});
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
export function useQueryTopologyMetrics(metrics: string[], ids: string[]) {
|
||||||
|
const appStore = useAppStoreWithOut();
|
||||||
|
const conditions: { [key: string]: unknown } = {
|
||||||
|
duration: appStore.durationTime,
|
||||||
|
ids,
|
||||||
|
};
|
||||||
|
const variables: string[] = [`$duration: Duration!`, `$ids: [ID!]!`];
|
||||||
|
const fragmentList = metrics.map((d: string, index: number) => {
|
||||||
|
conditions[`m${index}`] = d;
|
||||||
|
variables.push(`$m${index}: String!`);
|
||||||
|
|
||||||
|
return `${d}: getValues(metric: {
|
||||||
|
name: $m${index}
|
||||||
|
ids: $ids
|
||||||
|
}, duration: $duration) {
|
||||||
|
values {
|
||||||
|
id
|
||||||
|
value
|
||||||
|
}
|
||||||
|
}`;
|
||||||
|
});
|
||||||
|
const queryStr = `query queryData(${variables}) {${fragmentList.join(" ")}}`;
|
||||||
|
|
||||||
|
return { queryStr, conditions };
|
||||||
|
}
|
||||||
|
@ -73,8 +73,23 @@ const msg = {
|
|||||||
fontSize: "Font Size",
|
fontSize: "Font Size",
|
||||||
showBackground: "Show Background",
|
showBackground: "Show Background",
|
||||||
areaOpacity: "Area Opacity",
|
areaOpacity: "Area Opacity",
|
||||||
editGraph: "Edit Graph Options",
|
editGraph: "Edit Options",
|
||||||
dashboardName: "Select Dashboard Name",
|
dashboardName: "Select Dashboard Name",
|
||||||
|
linkDashboard: "Dashboard name related with topology calls",
|
||||||
|
linkServerMetrics: "Server metrics related with topology calls",
|
||||||
|
linkClientMetrics: "Client metrics related with topology calls",
|
||||||
|
nodeDashboard: "Dashboard name related with topology nodes",
|
||||||
|
nodeMetrics: "Metrics related with topology nodes",
|
||||||
|
instanceDashboard: "Dashboard name related with service instances",
|
||||||
|
endpointDashboard: "Dashboard name related with endpoints",
|
||||||
|
callSettings: "Call settings",
|
||||||
|
nodeSettings: "Node Settings",
|
||||||
|
conditions: "Conditions",
|
||||||
|
legendSettings: "Legend Settings",
|
||||||
|
setLegend: "Set Legend",
|
||||||
|
backgroundColors: "Background Colors",
|
||||||
|
fontColors: "Font Colors",
|
||||||
|
iconTheme: "Icon Theme",
|
||||||
hourTip: "Select Hour",
|
hourTip: "Select Hour",
|
||||||
minuteTip: "Select Minute",
|
minuteTip: "Select Minute",
|
||||||
secondTip: "Select Second",
|
secondTip: "Select Second",
|
||||||
@ -224,6 +239,7 @@ const msg = {
|
|||||||
defaultOrder: "Default Order",
|
defaultOrder: "Default Order",
|
||||||
chartType: "Chart Type",
|
chartType: "Chart Type",
|
||||||
currentDepth: "Current Depth",
|
currentDepth: "Current Depth",
|
||||||
|
defaultDepth: "Default Depth",
|
||||||
traceTagsTip: `Only tags defined in the core/default/searchableTracesTags are searchable.
|
traceTagsTip: `Only tags defined in the core/default/searchableTracesTags are searchable.
|
||||||
Check more details on the Configuration Vocabulary page`,
|
Check more details on the Configuration Vocabulary page`,
|
||||||
tagsLink: "Configuration Vocabulary page",
|
tagsLink: "Configuration Vocabulary page",
|
||||||
|
@ -23,6 +23,7 @@ const msg = {
|
|||||||
infrastructure: "基础结构",
|
infrastructure: "基础结构",
|
||||||
virtualMachine: "虚拟机",
|
virtualMachine: "虚拟机",
|
||||||
kubernetes: "Kubernetes",
|
kubernetes: "Kubernetes",
|
||||||
|
dashboardNew: "新建仪表板",
|
||||||
dashboardHome: "仪表盘首页",
|
dashboardHome: "仪表盘首页",
|
||||||
dashboardList: "仪表盘列表",
|
dashboardList: "仪表盘列表",
|
||||||
log: "日志",
|
log: "日志",
|
||||||
@ -71,8 +72,23 @@ const msg = {
|
|||||||
fontSize: "字体大小",
|
fontSize: "字体大小",
|
||||||
showBackground: "显示背景",
|
showBackground: "显示背景",
|
||||||
areaOpacity: "透明度",
|
areaOpacity: "透明度",
|
||||||
editGraph: "编辑图表选项",
|
editGraph: "选项编辑",
|
||||||
dashboardName: "选择仪表板名称",
|
dashboardName: "选择仪表板名称",
|
||||||
|
linkDashboard: "拓扑线关联的仪表板名称",
|
||||||
|
linkServerMetrics: "拓扑线服务端关联的指标",
|
||||||
|
linkClientMetrics: "拓扑线客户端关联的指标",
|
||||||
|
nodeDashboard: "拓节点关联的仪表板名称",
|
||||||
|
nodeMetrics: "拓扑节点关联的指标",
|
||||||
|
instanceDashboard: "拓节点关联的实例的仪表板名称",
|
||||||
|
endpointDashboard: "拓节点端点的实例的仪表板名称",
|
||||||
|
callSettings: "拓扑线设置",
|
||||||
|
nodeSettings: "拓扑点设置",
|
||||||
|
conditions: "条件",
|
||||||
|
legendSettings: "图例设置",
|
||||||
|
setLegend: "设置图例",
|
||||||
|
backgroundColors: "背景颜色",
|
||||||
|
fontColors: "字体颜色",
|
||||||
|
iconTheme: "图标主题",
|
||||||
hourTip: "选择小时",
|
hourTip: "选择小时",
|
||||||
minuteTip: "选择分钟",
|
minuteTip: "选择分钟",
|
||||||
secondTip: "选择秒数",
|
secondTip: "选择秒数",
|
||||||
@ -224,6 +240,7 @@ const msg = {
|
|||||||
defaultOrder: "默认顺序",
|
defaultOrder: "默认顺序",
|
||||||
chartType: "图表类型",
|
chartType: "图表类型",
|
||||||
currentDepth: "当前深度",
|
currentDepth: "当前深度",
|
||||||
|
defaultDepth: "默认深度",
|
||||||
traceTagsTip:
|
traceTagsTip:
|
||||||
"只有core/default/searchableTracesTags中定义的标记才可搜索。查看配置词汇表页面上的更多详细信息。",
|
"只有core/default/searchableTracesTags中定义的标记才可搜索。查看配置词汇表页面上的更多详细信息。",
|
||||||
tagsLink: "配置词汇页",
|
tagsLink: "配置词汇页",
|
||||||
|
43
src/router/alarm.ts
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the 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.
|
||||||
|
* The 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.
|
||||||
|
*/
|
||||||
|
import { RouteRecordRaw } from "vue-router";
|
||||||
|
import Layout from "@/layout/Index.vue";
|
||||||
|
|
||||||
|
export const routesAlarm: Array<RouteRecordRaw> = [
|
||||||
|
{
|
||||||
|
path: "",
|
||||||
|
name: "Alarm",
|
||||||
|
meta: {
|
||||||
|
title: "alarm",
|
||||||
|
icon: "spam",
|
||||||
|
hasGroup: false,
|
||||||
|
exact: true,
|
||||||
|
},
|
||||||
|
component: Layout,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: "/alarm",
|
||||||
|
name: "Alarm",
|
||||||
|
meta: {
|
||||||
|
title: "alarmList",
|
||||||
|
exact: false,
|
||||||
|
},
|
||||||
|
component: () => import("@/views/Log.vue"),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
@ -60,7 +60,17 @@ export const routesDashboard: Array<RouteRecordRaw> = [
|
|||||||
{
|
{
|
||||||
path: "/dashboard/:layerId/:entity/:serviceId/:name",
|
path: "/dashboard/:layerId/:entity/:serviceId/:name",
|
||||||
component: () => import("@/views/dashboard/Edit.vue"),
|
component: () => import("@/views/dashboard/Edit.vue"),
|
||||||
name: "CreateService",
|
name: "View",
|
||||||
|
meta: {
|
||||||
|
title: "dashboardEdit",
|
||||||
|
exact: false,
|
||||||
|
notShow: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/dashboard/:layerId/:entity/:serviceId/:destServiceId/:name",
|
||||||
|
component: () => import("@/views/dashboard/Edit.vue"),
|
||||||
|
name: "ViewServiceRelation",
|
||||||
meta: {
|
meta: {
|
||||||
title: "dashboardEdit",
|
title: "dashboardEdit",
|
||||||
exact: false,
|
exact: false,
|
||||||
@ -77,6 +87,16 @@ export const routesDashboard: Array<RouteRecordRaw> = [
|
|||||||
notShow: true,
|
notShow: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "/dashboard/:layerId/:entity/:serviceId/:podId/:destServiceId/:destPodId/:name",
|
||||||
|
component: () => import("@/views/dashboard/Edit.vue"),
|
||||||
|
name: "ViewPodRelation",
|
||||||
|
meta: {
|
||||||
|
title: "dashboardEdit",
|
||||||
|
exact: true,
|
||||||
|
notShow: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
@ -25,6 +25,8 @@ import { routesLog } from "./log";
|
|||||||
import { routesEvent } from "./event";
|
import { routesEvent } from "./event";
|
||||||
import { routesAlert } from "./alert";
|
import { routesAlert } from "./alert";
|
||||||
import { routesSetting } from "./setting";
|
import { routesSetting } from "./setting";
|
||||||
|
import { routesAlarm } from "./alarm";
|
||||||
|
import { useTimeoutFn } from "@/hooks/useTimeout";
|
||||||
|
|
||||||
const routes: Array<RouteRecordRaw> = [
|
const routes: Array<RouteRecordRaw> = [
|
||||||
...routesGen,
|
...routesGen,
|
||||||
@ -36,6 +38,7 @@ const routes: Array<RouteRecordRaw> = [
|
|||||||
...routesEvent,
|
...routesEvent,
|
||||||
...routesAlert,
|
...routesAlert,
|
||||||
...routesSetting,
|
...routesSetting,
|
||||||
|
...routesAlarm,
|
||||||
];
|
];
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
@ -49,7 +52,7 @@ router.beforeEach((to, from, next) => {
|
|||||||
// const token = window.localStorage.getItem("skywalking-authority");
|
// const token = window.localStorage.getItem("skywalking-authority");
|
||||||
if ((window as any).axiosCancel.length !== 0) {
|
if ((window as any).axiosCancel.length !== 0) {
|
||||||
for (const func of (window as any).axiosCancel) {
|
for (const func of (window as any).axiosCancel) {
|
||||||
setTimeout(func(), 0);
|
useTimeoutFn(func(), 0);
|
||||||
}
|
}
|
||||||
(window as any).axiosCancel = [];
|
(window as any).axiosCancel = [];
|
||||||
}
|
}
|
||||||
|
@ -101,7 +101,8 @@ export const ConfigData2: any = {
|
|||||||
},
|
},
|
||||||
children: [],
|
children: [],
|
||||||
};
|
};
|
||||||
export const ConfigData3: any = {
|
export const ConfigData3: any = [
|
||||||
|
{
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
w: 8,
|
w: 8,
|
||||||
@ -121,4 +122,68 @@ export const ConfigData3: any = {
|
|||||||
unit: "min",
|
unit: "min",
|
||||||
},
|
},
|
||||||
children: [],
|
children: [],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
export const ConfigData4: any = {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
w: 8,
|
||||||
|
h: 12,
|
||||||
|
i: "0",
|
||||||
|
metrics: ["service_relation_server_resp_time"],
|
||||||
|
metricTypes: ["readMetricsValues"],
|
||||||
|
type: "Widget",
|
||||||
|
widget: {
|
||||||
|
title: "service_relation_server_resp_time",
|
||||||
|
tips: "Tooltip",
|
||||||
|
},
|
||||||
|
graph: {
|
||||||
|
type: "Line",
|
||||||
|
},
|
||||||
|
standard: {
|
||||||
|
unit: "min",
|
||||||
|
},
|
||||||
|
children: [],
|
||||||
|
};
|
||||||
|
export const ConfigData5: any = {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
w: 8,
|
||||||
|
h: 12,
|
||||||
|
i: "0",
|
||||||
|
metrics: ["endpoint_relation_cpm"],
|
||||||
|
metricTypes: ["readMetricsValues"],
|
||||||
|
type: "Widget",
|
||||||
|
widget: {
|
||||||
|
title: "endpoint_relation_cpm",
|
||||||
|
tips: "Tooltip",
|
||||||
|
},
|
||||||
|
graph: {
|
||||||
|
type: "Line",
|
||||||
|
},
|
||||||
|
standard: {
|
||||||
|
unit: "min",
|
||||||
|
},
|
||||||
|
children: [],
|
||||||
|
};
|
||||||
|
export const ConfigData6: any = {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
w: 8,
|
||||||
|
h: 12,
|
||||||
|
i: "0",
|
||||||
|
metrics: ["service_instance_relation_server_cpm"],
|
||||||
|
metricTypes: ["readMetricsValues"],
|
||||||
|
type: "Widget",
|
||||||
|
widget: {
|
||||||
|
title: "service_instance_relation_server_cpm",
|
||||||
|
tips: "Tooltip",
|
||||||
|
},
|
||||||
|
graph: {
|
||||||
|
type: "Line",
|
||||||
|
},
|
||||||
|
standard: {
|
||||||
|
unit: "min",
|
||||||
|
},
|
||||||
|
children: [],
|
||||||
};
|
};
|
||||||
|
@ -17,14 +17,22 @@
|
|||||||
import { defineStore } from "pinia";
|
import { defineStore } from "pinia";
|
||||||
import { store } from "@/store";
|
import { store } from "@/store";
|
||||||
import { LayoutConfig } from "@/types/dashboard";
|
import { LayoutConfig } from "@/types/dashboard";
|
||||||
import graph from "@/graph";
|
import graphql from "@/graphql";
|
||||||
import { ConfigData, ConfigData1, ConfigData2, ConfigData3 } from "../data";
|
import query from "@/graphql/fetch";
|
||||||
|
import {
|
||||||
|
ConfigData,
|
||||||
|
ConfigData1,
|
||||||
|
ConfigData2,
|
||||||
|
ConfigData3,
|
||||||
|
ConfigData4,
|
||||||
|
ConfigData5,
|
||||||
|
ConfigData6,
|
||||||
|
} from "../data";
|
||||||
import { useAppStoreWithOut } from "@/store/modules/app";
|
import { useAppStoreWithOut } from "@/store/modules/app";
|
||||||
import { useSelectorStore } from "@/store/modules/selectors";
|
import { useSelectorStore } from "@/store/modules/selectors";
|
||||||
import { NewControl } from "../data";
|
import { NewControl } from "../data";
|
||||||
import { Duration } from "@/types/app";
|
import { Duration } from "@/types/app";
|
||||||
import axios, { AxiosResponse } from "axios";
|
import { AxiosResponse } from "axios";
|
||||||
import { cancelToken } from "@/utils/cancelToken";
|
|
||||||
interface DashboardState {
|
interface DashboardState {
|
||||||
showConfig: boolean;
|
showConfig: boolean;
|
||||||
layout: LayoutConfig[];
|
layout: LayoutConfig[];
|
||||||
@ -34,6 +42,7 @@ interface DashboardState {
|
|||||||
activedGridItem: string;
|
activedGridItem: string;
|
||||||
durationTime: Duration;
|
durationTime: Duration;
|
||||||
selectorStore: any;
|
selectorStore: any;
|
||||||
|
showTopology: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const dashboardStore = defineStore({
|
export const dashboardStore = defineStore({
|
||||||
@ -47,6 +56,7 @@ export const dashboardStore = defineStore({
|
|||||||
activedGridItem: "",
|
activedGridItem: "",
|
||||||
durationTime: useAppStoreWithOut().durationTime,
|
durationTime: useAppStoreWithOut().durationTime,
|
||||||
selectorStore: useSelectorStore(),
|
selectorStore: useSelectorStore(),
|
||||||
|
showTopology: false,
|
||||||
}),
|
}),
|
||||||
actions: {
|
actions: {
|
||||||
setLayout(data: LayoutConfig[]) {
|
setLayout(data: LayoutConfig[]) {
|
||||||
@ -73,6 +83,17 @@ export const dashboardStore = defineStore({
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
if (type === "Topology") {
|
||||||
|
newWidget.w = 4;
|
||||||
|
newWidget.h = 6;
|
||||||
|
newWidget.graph = {
|
||||||
|
fontColor: "white",
|
||||||
|
backgroundColor: "green",
|
||||||
|
iconTheme: true,
|
||||||
|
content: "Topology",
|
||||||
|
fontSize: 18,
|
||||||
|
};
|
||||||
|
}
|
||||||
this.layout = this.layout.map((d: LayoutConfig) => {
|
this.layout = this.layout.map((d: LayoutConfig) => {
|
||||||
d.y = d.y + newWidget.h;
|
d.y = d.y + newWidget.h;
|
||||||
return d;
|
return d;
|
||||||
@ -154,11 +175,23 @@ export const dashboardStore = defineStore({
|
|||||||
this.layout = [ConfigData2];
|
this.layout = [ConfigData2];
|
||||||
}
|
}
|
||||||
if (type == "All") {
|
if (type == "All") {
|
||||||
this.layout = [ConfigData3];
|
this.layout = ConfigData3;
|
||||||
}
|
}
|
||||||
if (type == "Service") {
|
if (type == "Service") {
|
||||||
this.layout = [ConfigData];
|
this.layout = [ConfigData];
|
||||||
}
|
}
|
||||||
|
if (type == "ServiceRelation") {
|
||||||
|
this.layout = [ConfigData4];
|
||||||
|
}
|
||||||
|
if (type == "ServiceInstanceRelation") {
|
||||||
|
this.layout = [ConfigData6];
|
||||||
|
}
|
||||||
|
if (type == "EndpointRelation") {
|
||||||
|
this.layout = [ConfigData5];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setTopology(show: boolean) {
|
||||||
|
this.showTopology = show;
|
||||||
},
|
},
|
||||||
setConfigs(param: { [key: string]: unknown }) {
|
setConfigs(param: { [key: string]: unknown }) {
|
||||||
const actived = this.activedGridItem.split("-");
|
const actived = this.activedGridItem.split("-");
|
||||||
@ -181,14 +214,14 @@ export const dashboardStore = defineStore({
|
|||||||
this.selectedGrid = this.layout[index];
|
this.selectedGrid = this.layout[index];
|
||||||
},
|
},
|
||||||
async fetchMetricType(item: string) {
|
async fetchMetricType(item: string) {
|
||||||
const res: AxiosResponse = await graph
|
const res: AxiosResponse = await graphql
|
||||||
.query("queryTypeOfMetrics")
|
.query("queryTypeOfMetrics")
|
||||||
.params({ name: item });
|
.params({ name: item });
|
||||||
|
|
||||||
return res.data;
|
return res.data;
|
||||||
},
|
},
|
||||||
async fetchMetricList(regex: string) {
|
async fetchMetricList(regex: string) {
|
||||||
const res: AxiosResponse = await graph
|
const res: AxiosResponse = await graphql
|
||||||
.query("queryMetrics")
|
.query("queryMetrics")
|
||||||
.params({ regex });
|
.params({ regex });
|
||||||
|
|
||||||
@ -198,11 +231,7 @@ export const dashboardStore = defineStore({
|
|||||||
queryStr: string;
|
queryStr: string;
|
||||||
conditions: { [key: string]: unknown };
|
conditions: { [key: string]: unknown };
|
||||||
}) {
|
}) {
|
||||||
const res: AxiosResponse = await axios.post(
|
const res: AxiosResponse = await query(param);
|
||||||
"/graphql",
|
|
||||||
{ query: param.queryStr, variables: { ...param.conditions } },
|
|
||||||
{ cancelToken: cancelToken() }
|
|
||||||
);
|
|
||||||
return res.data;
|
return res.data;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -18,17 +18,19 @@ import { defineStore } from "pinia";
|
|||||||
import { Duration } from "@/types/app";
|
import { Duration } from "@/types/app";
|
||||||
import { Service, Instance, Endpoint } from "@/types/selector";
|
import { Service, Instance, Endpoint } from "@/types/selector";
|
||||||
import { store } from "@/store";
|
import { store } from "@/store";
|
||||||
import graph from "@/graph";
|
import graphql from "@/graphql";
|
||||||
import { AxiosResponse } from "axios";
|
import { AxiosResponse } from "axios";
|
||||||
import { useAppStoreWithOut } from "@/store/modules/app";
|
import { useAppStoreWithOut } from "@/store/modules/app";
|
||||||
|
|
||||||
interface SelectorState {
|
interface SelectorState {
|
||||||
services: Service[];
|
services: Service[];
|
||||||
|
destServices: Service[];
|
||||||
pods: Array<Instance | Endpoint>;
|
pods: Array<Instance | Endpoint>;
|
||||||
currentService: Nullable<Service>;
|
currentService: Nullable<Service>;
|
||||||
currentPod: Nullable<Instance | Endpoint>;
|
currentPod: Nullable<Instance | Endpoint>;
|
||||||
currentDestService: Nullable<Service>;
|
currentDestService: Nullable<Service>;
|
||||||
currentDestPod: Nullable<Instance | Endpoint>;
|
currentDestPod: Nullable<Instance | Endpoint>;
|
||||||
|
destPods: Array<Instance | Endpoint>;
|
||||||
durationTime: Duration;
|
durationTime: Duration;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -36,7 +38,9 @@ export const selectorStore = defineStore({
|
|||||||
id: "selector",
|
id: "selector",
|
||||||
state: (): SelectorState => ({
|
state: (): SelectorState => ({
|
||||||
services: [],
|
services: [],
|
||||||
|
destServices: [],
|
||||||
pods: [],
|
pods: [],
|
||||||
|
destPods: [],
|
||||||
currentService: null,
|
currentService: null,
|
||||||
currentPod: null,
|
currentPod: null,
|
||||||
currentDestService: null,
|
currentDestService: null,
|
||||||
@ -44,103 +48,134 @@ export const selectorStore = defineStore({
|
|||||||
durationTime: useAppStoreWithOut().durationTime,
|
durationTime: useAppStoreWithOut().durationTime,
|
||||||
}),
|
}),
|
||||||
actions: {
|
actions: {
|
||||||
setCurrentService(service: Service) {
|
setCurrentService(service: Nullable<Service>) {
|
||||||
this.currentService = service;
|
this.currentService = service;
|
||||||
},
|
},
|
||||||
|
setCurrentDestService(service: Nullable<Service>) {
|
||||||
|
this.currentDestService = service;
|
||||||
|
},
|
||||||
setCurrentPod(pod: Nullable<Instance | Endpoint>) {
|
setCurrentPod(pod: Nullable<Instance | Endpoint>) {
|
||||||
this.currentPod = pod;
|
this.currentPod = pod;
|
||||||
},
|
},
|
||||||
|
setCurrentDestPod(pod: Nullable<Instance | Endpoint>) {
|
||||||
|
this.currentDestPod = pod;
|
||||||
|
},
|
||||||
async fetchLayers(): Promise<AxiosResponse> {
|
async fetchLayers(): Promise<AxiosResponse> {
|
||||||
const res: AxiosResponse = await graph.query("queryLayers").params({});
|
const res: AxiosResponse = await graphql.query("queryLayers").params({});
|
||||||
|
|
||||||
return res.data || {};
|
return res.data || {};
|
||||||
},
|
},
|
||||||
async fetchServices(layer: string): Promise<AxiosResponse> {
|
async fetchServices(layer: string): Promise<AxiosResponse> {
|
||||||
const res: AxiosResponse = await graph
|
const res: AxiosResponse = await graphql
|
||||||
.query("queryServices")
|
.query("queryServices")
|
||||||
.params({ layer });
|
.params({ layer });
|
||||||
|
|
||||||
if (!res.data.errors) {
|
if (!res.data.errors) {
|
||||||
this.services = res.data.data.services || [];
|
this.services = res.data.data.services || [];
|
||||||
|
this.destServices = res.data.data.services || [];
|
||||||
}
|
}
|
||||||
return res.data;
|
return res.data;
|
||||||
},
|
},
|
||||||
async getServiceInstances(param?: {
|
async getServiceInstances(param?: {
|
||||||
serviceId: string;
|
serviceId: string;
|
||||||
|
isRelation: boolean;
|
||||||
}): Promise<Nullable<AxiosResponse>> {
|
}): Promise<Nullable<AxiosResponse>> {
|
||||||
const serviceId = param ? param.serviceId : this.currentService?.id;
|
const serviceId = param ? param.serviceId : this.currentService?.id;
|
||||||
if (!serviceId) {
|
if (!serviceId) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const res: AxiosResponse = await graph.query("queryInstances").params({
|
const res: AxiosResponse = await graphql.query("queryInstances").params({
|
||||||
serviceId,
|
serviceId,
|
||||||
duration: this.durationTime,
|
duration: this.durationTime,
|
||||||
});
|
});
|
||||||
if (!res.data.errors) {
|
if (!res.data.errors) {
|
||||||
|
if (param && param.isRelation) {
|
||||||
|
this.destPods = res.data.data.pods || [];
|
||||||
|
return res.data;
|
||||||
|
}
|
||||||
this.pods = res.data.data.pods || [];
|
this.pods = res.data.data.pods || [];
|
||||||
}
|
}
|
||||||
return res.data;
|
return res.data;
|
||||||
},
|
},
|
||||||
async getEndpoints(params?: {
|
async getEndpoints(params: {
|
||||||
keyword?: string;
|
keyword?: string;
|
||||||
serviceId?: string;
|
serviceId?: string;
|
||||||
|
isRelation?: boolean;
|
||||||
}): Promise<Nullable<AxiosResponse>> {
|
}): Promise<Nullable<AxiosResponse>> {
|
||||||
if (!params) {
|
if (!params) {
|
||||||
params = {};
|
params = {};
|
||||||
}
|
}
|
||||||
if (!params.keyword) {
|
|
||||||
params.keyword = "";
|
|
||||||
}
|
|
||||||
const serviceId = params.serviceId || this.currentService?.id;
|
const serviceId = params.serviceId || this.currentService?.id;
|
||||||
if (!serviceId) {
|
if (!serviceId) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const res: AxiosResponse = await graph.query("queryEndpoints").params({
|
const res: AxiosResponse = await graphql.query("queryEndpoints").params({
|
||||||
serviceId,
|
serviceId,
|
||||||
duration: this.durationTime,
|
duration: this.durationTime,
|
||||||
keyword: params.keyword,
|
keyword: params.keyword || "",
|
||||||
});
|
});
|
||||||
if (!res.data.errors) {
|
if (!res.data.errors) {
|
||||||
|
if (params.isRelation) {
|
||||||
|
this.destPods = res.data.data.pods || [];
|
||||||
|
return res.data;
|
||||||
|
}
|
||||||
this.pods = res.data.data.pods || [];
|
this.pods = res.data.data.pods || [];
|
||||||
}
|
}
|
||||||
return res.data;
|
return res.data;
|
||||||
},
|
},
|
||||||
async getService(serviceId: string) {
|
async getService(serviceId: string, isRelation: boolean) {
|
||||||
if (!serviceId) {
|
if (!serviceId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const res: AxiosResponse = await graph.query("queryService").params({
|
const res: AxiosResponse = await graphql.query("queryService").params({
|
||||||
serviceId,
|
serviceId,
|
||||||
});
|
});
|
||||||
if (!res.data.errors) {
|
if (!res.data.errors) {
|
||||||
this.currentService = res.data.data.service || {};
|
if (isRelation) {
|
||||||
|
this.setCurrentDestService(res.data.data.service);
|
||||||
|
this.destServices = [res.data.data.service];
|
||||||
|
return res.data;
|
||||||
|
}
|
||||||
|
this.setCurrentService(res.data.data.service);
|
||||||
this.services = [res.data.data.service];
|
this.services = [res.data.data.service];
|
||||||
}
|
}
|
||||||
|
|
||||||
return res.data;
|
return res.data;
|
||||||
},
|
},
|
||||||
async getInstance(instanceId: string) {
|
async getInstance(instanceId: string, isRelation?: boolean) {
|
||||||
if (!instanceId) {
|
if (!instanceId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const res: AxiosResponse = await graph.query("queryInstance").params({
|
const res: AxiosResponse = await graphql.query("queryInstance").params({
|
||||||
instanceId,
|
instanceId,
|
||||||
});
|
});
|
||||||
if (!res.data.errors) {
|
if (!res.data.errors) {
|
||||||
|
if (isRelation) {
|
||||||
|
this.currentDestPod = res.data.data.instance || null;
|
||||||
|
this.destPods = [res.data.data.instance];
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.currentPod = res.data.data.instance || null;
|
this.currentPod = res.data.data.instance || null;
|
||||||
|
this.pods = [res.data.data.instance];
|
||||||
}
|
}
|
||||||
|
|
||||||
return res.data;
|
return res.data;
|
||||||
},
|
},
|
||||||
async getEndpoint(endpointId: string) {
|
async getEndpoint(endpointId: string, isRelation?: string) {
|
||||||
if (!endpointId) {
|
if (!endpointId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const res: AxiosResponse = await graph.query("queryEndpoint").params({
|
const res: AxiosResponse = await graphql.query("queryEndpoint").params({
|
||||||
endpointId,
|
endpointId,
|
||||||
});
|
});
|
||||||
if (!res.data.errors) {
|
if (!res.data.errors) {
|
||||||
|
if (isRelation) {
|
||||||
|
this.currentDestPod = res.data.data.endpoint || null;
|
||||||
|
this.destPods = [res.data.data.endpoint];
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.currentPod = res.data.data.endpoint || null;
|
this.currentPod = res.data.data.endpoint || null;
|
||||||
|
this.pods = [res.data.data.endpoint];
|
||||||
}
|
}
|
||||||
|
|
||||||
return res.data;
|
return res.data;
|
||||||
|
460
src/store/modules/topology.ts
Normal file
@ -0,0 +1,460 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the 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.
|
||||||
|
* The 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.
|
||||||
|
*/
|
||||||
|
import { defineStore } from "pinia";
|
||||||
|
import { store } from "@/store";
|
||||||
|
import { Service } from "@/types/selector";
|
||||||
|
import { Node, Call } from "@/types/topology";
|
||||||
|
import graphql from "@/graphql";
|
||||||
|
import { useSelectorStore } from "@/store/modules/selectors";
|
||||||
|
import { useAppStoreWithOut } from "@/store/modules/app";
|
||||||
|
import { AxiosResponse } from "axios";
|
||||||
|
import query from "@/graphql/fetch";
|
||||||
|
|
||||||
|
interface MetricVal {
|
||||||
|
[key: string]: { values: { id: string; value: unknown }[] };
|
||||||
|
}
|
||||||
|
interface TopologyState {
|
||||||
|
node: Nullable<Node>;
|
||||||
|
call: Nullable<Call>;
|
||||||
|
calls: Call[];
|
||||||
|
nodes: Node[];
|
||||||
|
nodeMetrics: MetricVal;
|
||||||
|
linkServerMetrics: MetricVal;
|
||||||
|
linkClientMetrics: MetricVal;
|
||||||
|
defaultDepth: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const topologyStore = defineStore({
|
||||||
|
id: "topology",
|
||||||
|
state: (): TopologyState => ({
|
||||||
|
calls: [],
|
||||||
|
nodes: [],
|
||||||
|
node: null,
|
||||||
|
call: null,
|
||||||
|
nodeMetrics: {},
|
||||||
|
linkServerMetrics: {},
|
||||||
|
linkClientMetrics: {},
|
||||||
|
defaultDepth: "2",
|
||||||
|
}),
|
||||||
|
actions: {
|
||||||
|
setNode(node: Node) {
|
||||||
|
this.node = node;
|
||||||
|
},
|
||||||
|
setLink(link: Call) {
|
||||||
|
this.call = link;
|
||||||
|
},
|
||||||
|
setInstanceTopology(data: { nodes: Node[]; calls: Call[] }) {
|
||||||
|
for (const call of data.calls) {
|
||||||
|
for (const node of data.nodes) {
|
||||||
|
if (call.source === node.id) {
|
||||||
|
call.sourceObj = node;
|
||||||
|
}
|
||||||
|
if (call.target === node.id) {
|
||||||
|
call.targetObj = node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
call.value = call.value || 1;
|
||||||
|
}
|
||||||
|
this.calls = data.calls;
|
||||||
|
this.nodes = data.nodes;
|
||||||
|
},
|
||||||
|
setTopology(data: { nodes: Node[]; calls: Call[] }) {
|
||||||
|
const obj = {} as any;
|
||||||
|
const services = useSelectorStore().services;
|
||||||
|
const nodes = data.nodes.reduce((prev: Node[], next: Node) => {
|
||||||
|
if (!obj[next.id]) {
|
||||||
|
obj[next.id] = true;
|
||||||
|
const s = services.filter((d: Service) => d.id === next.id)[0] || {};
|
||||||
|
next.layer = s.layers ? s.layers[0] : null;
|
||||||
|
prev.push(next);
|
||||||
|
}
|
||||||
|
return prev;
|
||||||
|
}, []);
|
||||||
|
const calls = data.calls.reduce((prev: Call[], next: Call) => {
|
||||||
|
if (!obj[next.id]) {
|
||||||
|
obj[next.id] = true;
|
||||||
|
next.value = next.value || 1;
|
||||||
|
for (const node of data.nodes) {
|
||||||
|
if (next.source === node.id) {
|
||||||
|
next.sourceObj = node;
|
||||||
|
}
|
||||||
|
if (next.target === node.id) {
|
||||||
|
next.targetObj = node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
next.value = next.value || 1;
|
||||||
|
prev.push(next);
|
||||||
|
}
|
||||||
|
return prev;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
this.calls = calls;
|
||||||
|
this.nodes = nodes;
|
||||||
|
},
|
||||||
|
setNodeMetrics(m: { id: string; value: unknown }[]) {
|
||||||
|
this.nodeMetrics = m;
|
||||||
|
},
|
||||||
|
setLinkServerMetrics(m: { id: string; value: unknown }[]) {
|
||||||
|
this.linkServerMetrics = m;
|
||||||
|
},
|
||||||
|
setLinkClientMetrics(m: { id: string; value: unknown }[]) {
|
||||||
|
this.linkClientMetrics = m;
|
||||||
|
},
|
||||||
|
setDefaultDepth(val: number) {
|
||||||
|
this.defaultDepth = val;
|
||||||
|
},
|
||||||
|
async getDepthServiceTopology(serviceIds: string[], depth: number) {
|
||||||
|
const res = await this.getServicesTopology(serviceIds);
|
||||||
|
if (depth > 1) {
|
||||||
|
const ids = res.nodes
|
||||||
|
.map((item: Node) => item.id)
|
||||||
|
.filter((d: string) => !serviceIds.includes(d));
|
||||||
|
if (!ids.length) {
|
||||||
|
this.setTopology(res);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const json = await this.getServicesTopology(ids);
|
||||||
|
if (depth > 2) {
|
||||||
|
const pods = json.nodes
|
||||||
|
.map((item: Node) => item.id)
|
||||||
|
.filter((d: string) => ![...ids, ...serviceIds].includes(d));
|
||||||
|
if (!pods.length) {
|
||||||
|
const nodes = [...res.nodes, ...json.nodes];
|
||||||
|
const calls = [...res.calls, ...json.calls];
|
||||||
|
this.setTopology({ nodes, calls });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const topo = await this.getServicesTopology(pods);
|
||||||
|
if (depth > 3) {
|
||||||
|
const services = topo.nodes
|
||||||
|
.map((item: Node) => item.id)
|
||||||
|
.filter(
|
||||||
|
(d: string) => ![...ids, ...pods, ...serviceIds].includes(d)
|
||||||
|
);
|
||||||
|
if (!services.length) {
|
||||||
|
const nodes = [...res.nodes, ...json.nodes, ...topo.nodes];
|
||||||
|
const calls = [...res.calls, ...json.calls, ...topo.calls];
|
||||||
|
this.setTopology({ nodes, calls });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const data = await this.getServicesTopology(services);
|
||||||
|
if (depth > 4) {
|
||||||
|
const nodeIds = data.nodes
|
||||||
|
.map((item: Node) => item.id)
|
||||||
|
.filter(
|
||||||
|
(d: string) =>
|
||||||
|
![...services, ...ids, ...pods, ...serviceIds].includes(d)
|
||||||
|
);
|
||||||
|
if (!nodeIds.length) {
|
||||||
|
const nodes = [
|
||||||
|
...res.nodes,
|
||||||
|
...json.nodes,
|
||||||
|
...topo.nodes,
|
||||||
|
...data.nodes,
|
||||||
|
];
|
||||||
|
const calls = [
|
||||||
|
...res.calls,
|
||||||
|
...json.calls,
|
||||||
|
...topo.calls,
|
||||||
|
...data.calls,
|
||||||
|
];
|
||||||
|
this.setTopology({ nodes, calls });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const toposObj = await this.getServicesTopology(nodeIds);
|
||||||
|
const nodes = [
|
||||||
|
...res.nodes,
|
||||||
|
...json.nodes,
|
||||||
|
...topo.nodes,
|
||||||
|
...data.nodes,
|
||||||
|
...toposObj.nodes,
|
||||||
|
];
|
||||||
|
const calls = [
|
||||||
|
...res.calls,
|
||||||
|
...json.calls,
|
||||||
|
...topo.calls,
|
||||||
|
...data.calls,
|
||||||
|
...toposObj.calls,
|
||||||
|
];
|
||||||
|
this.setTopology({ nodes, calls });
|
||||||
|
} else {
|
||||||
|
const nodes = [
|
||||||
|
...res.nodes,
|
||||||
|
...json.nodes,
|
||||||
|
...topo.nodes,
|
||||||
|
...data.nodes,
|
||||||
|
];
|
||||||
|
const calls = [
|
||||||
|
...res.calls,
|
||||||
|
...json.calls,
|
||||||
|
...topo.calls,
|
||||||
|
...data.calls,
|
||||||
|
];
|
||||||
|
this.setTopology({ nodes, calls });
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const nodes = [...res.nodes, ...json.nodes, ...topo.nodes];
|
||||||
|
const calls = [...res.calls, ...json.calls, ...topo.calls];
|
||||||
|
this.setTopology({ nodes, calls });
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.setTopology({
|
||||||
|
nodes: [...res.nodes, ...json.nodes],
|
||||||
|
calls: [...res.calls, ...json.calls],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.setTopology(res);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async getServicesTopology(serviceIds: string[]) {
|
||||||
|
const duration = useAppStoreWithOut().durationTime;
|
||||||
|
const res: AxiosResponse = await graphql
|
||||||
|
.query("getServicesTopology")
|
||||||
|
.params({
|
||||||
|
serviceIds,
|
||||||
|
duration,
|
||||||
|
});
|
||||||
|
if (res.data.errors) {
|
||||||
|
return res.data;
|
||||||
|
}
|
||||||
|
return res.data.data.topology;
|
||||||
|
},
|
||||||
|
async getInstanceTopology() {
|
||||||
|
const serverServiceId = useSelectorStore().currentService.id;
|
||||||
|
const clientServiceId = useSelectorStore().currentDestService.id;
|
||||||
|
const duration = useAppStoreWithOut().durationTime;
|
||||||
|
const res: AxiosResponse = await graphql
|
||||||
|
.query("getInstanceTopology")
|
||||||
|
.params({
|
||||||
|
clientServiceId,
|
||||||
|
serverServiceId,
|
||||||
|
duration,
|
||||||
|
});
|
||||||
|
if (!res.data.errors) {
|
||||||
|
this.setInstanceTopology(res.data.data.topology);
|
||||||
|
}
|
||||||
|
return res.data;
|
||||||
|
},
|
||||||
|
async updateEndpointTopology(endpointIds: string[], depth: number) {
|
||||||
|
const res = await this.getEndpointTopology(endpointIds);
|
||||||
|
if (depth > 1) {
|
||||||
|
const ids = res.nodes
|
||||||
|
.map((item: Node) => item.id)
|
||||||
|
.filter((d: string) => !endpointIds.includes(d));
|
||||||
|
if (!ids.length) {
|
||||||
|
this.setTopology(res);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const json = await this.getEndpointTopology(ids);
|
||||||
|
if (depth > 2) {
|
||||||
|
const pods = json.nodes
|
||||||
|
.map((item: Node) => item.id)
|
||||||
|
.filter((d: string) => ![...ids, ...endpointIds].includes(d));
|
||||||
|
if (!pods.length) {
|
||||||
|
const nodes = [...res.nodes, ...json.nodes];
|
||||||
|
const calls = [...res.calls, ...json.calls];
|
||||||
|
this.setTopology({ nodes, calls });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const topo = await this.getEndpointTopology(pods);
|
||||||
|
if (depth > 3) {
|
||||||
|
const endpoints = topo.nodes
|
||||||
|
.map((item: Node) => item.id)
|
||||||
|
.filter(
|
||||||
|
(d: string) => ![...ids, ...pods, ...endpointIds].includes(d)
|
||||||
|
);
|
||||||
|
if (!endpoints.length) {
|
||||||
|
const nodes = [...res.nodes, ...json.nodes, ...topo.nodes];
|
||||||
|
const calls = [...res.calls, ...json.calls, ...topo.calls];
|
||||||
|
this.setTopology({ nodes, calls });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const data = await this.getEndpointTopology(endpoints);
|
||||||
|
if (depth > 4) {
|
||||||
|
const nodeIds = data.nodes
|
||||||
|
.map((item: Node) => item.id)
|
||||||
|
.filter(
|
||||||
|
(d: string) =>
|
||||||
|
![...endpoints, ...ids, ...pods, ...endpointIds].includes(d)
|
||||||
|
);
|
||||||
|
if (!nodeIds.length) {
|
||||||
|
const nodes = [
|
||||||
|
...res.nodes,
|
||||||
|
...json.nodes,
|
||||||
|
...topo.nodes,
|
||||||
|
...data.nodes,
|
||||||
|
];
|
||||||
|
const calls = [
|
||||||
|
...res.calls,
|
||||||
|
...json.calls,
|
||||||
|
...topo.calls,
|
||||||
|
...data.calls,
|
||||||
|
];
|
||||||
|
this.setTopology({ nodes, calls });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const toposObj = await this.getEndpointTopology(nodeIds);
|
||||||
|
const nodes = [
|
||||||
|
...res.nodes,
|
||||||
|
...json.nodes,
|
||||||
|
...topo.nodes,
|
||||||
|
...data.nodes,
|
||||||
|
...toposObj.nodes,
|
||||||
|
];
|
||||||
|
const calls = [
|
||||||
|
...res.calls,
|
||||||
|
...json.calls,
|
||||||
|
...topo.calls,
|
||||||
|
...data.calls,
|
||||||
|
...toposObj.calls,
|
||||||
|
];
|
||||||
|
this.setTopology({ nodes, calls });
|
||||||
|
} else {
|
||||||
|
const nodes = [
|
||||||
|
...res.nodes,
|
||||||
|
...json.nodes,
|
||||||
|
...topo.nodes,
|
||||||
|
...data.nodes,
|
||||||
|
];
|
||||||
|
const calls = [
|
||||||
|
...res.calls,
|
||||||
|
...json.calls,
|
||||||
|
...topo.calls,
|
||||||
|
...data.calls,
|
||||||
|
];
|
||||||
|
this.setTopology({ nodes, calls });
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const nodes = [...res.nodes, ...json.nodes, ...topo.nodes];
|
||||||
|
const calls = [...res.calls, ...json.calls, ...topo.calls];
|
||||||
|
this.setTopology({ nodes, calls });
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.setTopology({
|
||||||
|
nodes: [...res.nodes, ...json.nodes],
|
||||||
|
calls: [...res.calls, ...json.calls],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.setTopology(res);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async getEndpointTopology(endpointIds: string[]) {
|
||||||
|
const duration = useAppStoreWithOut().durationTime;
|
||||||
|
const variables = ["$duration: Duration!"];
|
||||||
|
const fragment = endpointIds.map((id: string, index: number) => {
|
||||||
|
return `endpointTopology${index}: getEndpointDependencies(endpointId: "${id}", duration: $duration) {
|
||||||
|
nodes {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
serviceId
|
||||||
|
serviceName
|
||||||
|
type
|
||||||
|
isReal
|
||||||
|
}
|
||||||
|
calls {
|
||||||
|
id
|
||||||
|
source
|
||||||
|
target
|
||||||
|
detectPoints
|
||||||
|
}
|
||||||
|
}`;
|
||||||
|
});
|
||||||
|
const queryStr = `query queryData(${variables}) {${fragment}}`;
|
||||||
|
const conditions = { duration };
|
||||||
|
const res: AxiosResponse = await query({ queryStr, conditions });
|
||||||
|
|
||||||
|
if (res.data.errors) {
|
||||||
|
return res.data;
|
||||||
|
}
|
||||||
|
const topo = res.data.data;
|
||||||
|
const calls = [] as any;
|
||||||
|
const nodes = [] as any;
|
||||||
|
for (const key of Object.keys(topo)) {
|
||||||
|
calls.push(...topo[key].calls);
|
||||||
|
nodes.push(...topo[key].nodes);
|
||||||
|
}
|
||||||
|
// this.setTopology({ calls, nodes });
|
||||||
|
|
||||||
|
return { calls, nodes };
|
||||||
|
},
|
||||||
|
async getNodeMetrics(param: {
|
||||||
|
queryStr: string;
|
||||||
|
conditions: { [key: string]: unknown };
|
||||||
|
}) {
|
||||||
|
const res: AxiosResponse = await query(param);
|
||||||
|
|
||||||
|
if (res.data.errors) {
|
||||||
|
return res.data;
|
||||||
|
}
|
||||||
|
this.setNodeMetrics(res.data.data);
|
||||||
|
return res.data;
|
||||||
|
},
|
||||||
|
async getLegendMetrics(param: {
|
||||||
|
queryStr: string;
|
||||||
|
conditions: { [key: string]: unknown };
|
||||||
|
}) {
|
||||||
|
const res: AxiosResponse = await query(param);
|
||||||
|
|
||||||
|
if (res.data.errors) {
|
||||||
|
return res.data;
|
||||||
|
}
|
||||||
|
const data = res.data.data;
|
||||||
|
const metrics = Object.keys(data);
|
||||||
|
this.nodes = this.nodes.map((d: Node | any) => {
|
||||||
|
for (const m of metrics) {
|
||||||
|
for (const val of data[m].values) {
|
||||||
|
if (d.id === val.id) {
|
||||||
|
d[m] = val.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return d;
|
||||||
|
});
|
||||||
|
return res.data;
|
||||||
|
},
|
||||||
|
async getCallServerMetrics(param: {
|
||||||
|
queryStr: string;
|
||||||
|
conditions: { [key: string]: unknown };
|
||||||
|
}) {
|
||||||
|
const res: AxiosResponse = await query(param);
|
||||||
|
|
||||||
|
if (res.data.errors) {
|
||||||
|
return res.data;
|
||||||
|
}
|
||||||
|
this.setLinkServerMetrics(res.data.data);
|
||||||
|
return res.data;
|
||||||
|
},
|
||||||
|
async getCallClientMetrics(param: {
|
||||||
|
queryStr: string;
|
||||||
|
conditions: { [key: string]: unknown };
|
||||||
|
}) {
|
||||||
|
const res: AxiosResponse = await query(param);
|
||||||
|
|
||||||
|
if (res.data.errors) {
|
||||||
|
return res.data;
|
||||||
|
}
|
||||||
|
this.setLinkClientMetrics(res.data.data);
|
||||||
|
return res.data;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export function useTopologyStore(): any {
|
||||||
|
return topologyStore(store);
|
||||||
|
}
|
@ -95,6 +95,10 @@
|
|||||||
background-color: #a7aebb;
|
background-color: #a7aebb;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mb-5 {
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
.ml-5 {
|
.ml-5 {
|
||||||
margin-left: 5px;
|
margin-left: 5px;
|
||||||
}
|
}
|
||||||
@ -129,11 +133,19 @@
|
|||||||
@keyframes loading {
|
@keyframes loading {
|
||||||
0% {
|
0% {
|
||||||
transform: rotate(0deg);
|
transform: rotate(0deg);
|
||||||
transform: rotate(0deg);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
to {
|
to {
|
||||||
transform: rotate(1turn);
|
transform: rotate(1turn);
|
||||||
transform: rotate(1turn);
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark-dialog {
|
||||||
|
background-color: #333840;
|
||||||
|
color: #ddd;
|
||||||
|
|
||||||
|
.el-loading-mask {
|
||||||
|
background-color: #333840;
|
||||||
|
color: #ddd;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -55,7 +55,8 @@ export type GraphConfig =
|
|||||||
| TableConfig
|
| TableConfig
|
||||||
| EndpointListConfig
|
| EndpointListConfig
|
||||||
| ServiceListConfig
|
| ServiceListConfig
|
||||||
| InstanceListConfig;
|
| InstanceListConfig
|
||||||
|
| TopologyConfig;
|
||||||
export interface BarConfig {
|
export interface BarConfig {
|
||||||
type?: string;
|
type?: string;
|
||||||
showBackground?: boolean;
|
showBackground?: boolean;
|
||||||
@ -110,3 +111,13 @@ export interface EndpointListConfig {
|
|||||||
dashboardName: string;
|
dashboardName: string;
|
||||||
fontSize: number;
|
fontSize: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface TopologyConfig {
|
||||||
|
type?: string;
|
||||||
|
backgroundColor?: string;
|
||||||
|
fontColor?: string;
|
||||||
|
iconTheme?: boolean;
|
||||||
|
content?: string;
|
||||||
|
fontSize?: number;
|
||||||
|
depth?: string;
|
||||||
|
}
|
||||||
|
2
src/types/global.d.ts
vendored
@ -26,6 +26,8 @@ import type {
|
|||||||
declare module "three";
|
declare module "three";
|
||||||
declare module "three-orbit-controls";
|
declare module "three-orbit-controls";
|
||||||
declare module "element-plus";
|
declare module "element-plus";
|
||||||
|
declare module "d3-tip";
|
||||||
|
declare module "d3";
|
||||||
declare global {
|
declare global {
|
||||||
const __APP_INFO__: {
|
const __APP_INFO__: {
|
||||||
pkg: {
|
pkg: {
|
||||||
|
34
src/types/topology.d.ts
vendored
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the 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.
|
||||||
|
* The 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.
|
||||||
|
*/
|
||||||
|
export interface Call {
|
||||||
|
source: string | any;
|
||||||
|
target: string | any;
|
||||||
|
id: string;
|
||||||
|
detectPoints: string[];
|
||||||
|
type?: string;
|
||||||
|
sourceObj?: any;
|
||||||
|
targetObj?: any;
|
||||||
|
value?: number;
|
||||||
|
}
|
||||||
|
export interface Node {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
type: string;
|
||||||
|
isReal: boolean;
|
||||||
|
layer?: string;
|
||||||
|
serviceName?: string;
|
||||||
|
}
|
@ -16,7 +16,13 @@
|
|||||||
*/
|
*/
|
||||||
import * as echarts from "echarts/core";
|
import * as echarts from "echarts/core";
|
||||||
|
|
||||||
import { BarChart, LineChart, PieChart, HeatmapChart } from "echarts/charts";
|
import {
|
||||||
|
BarChart,
|
||||||
|
LineChart,
|
||||||
|
PieChart,
|
||||||
|
HeatmapChart,
|
||||||
|
SankeyChart,
|
||||||
|
} from "echarts/charts";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
TitleComponent,
|
TitleComponent,
|
||||||
@ -39,6 +45,7 @@ echarts.use([
|
|||||||
LineChart,
|
LineChart,
|
||||||
PieChart,
|
PieChart,
|
||||||
HeatmapChart,
|
HeatmapChart,
|
||||||
|
SankeyChart,
|
||||||
SVGRenderer,
|
SVGRenderer,
|
||||||
DataZoomComponent,
|
DataZoomComponent,
|
||||||
VisualMapComponent,
|
VisualMapComponent,
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
import graph from "@/graph";
|
import graphql from "@/graphql";
|
||||||
import { AxiosResponse } from "axios";
|
import { AxiosResponse } from "axios";
|
||||||
|
|
||||||
const getLocalTime = (utc: string, time: Date): Date => {
|
const getLocalTime = (utc: string, time: Date): Date => {
|
||||||
@ -38,7 +38,9 @@ const setTimezoneOffset = () => {
|
|||||||
export const queryOAPTimeInfo = async (): Promise<void> => {
|
export const queryOAPTimeInfo = async (): Promise<void> => {
|
||||||
let utc = window.localStorage.getItem("utc");
|
let utc = window.localStorage.getItem("utc");
|
||||||
if (!utc) {
|
if (!utc) {
|
||||||
const res: AxiosResponse = await graph.query("queryOAPTimeInfo").params({});
|
const res: AxiosResponse = await graphql
|
||||||
|
.query("queryOAPTimeInfo")
|
||||||
|
.params({});
|
||||||
if (
|
if (
|
||||||
!res.data ||
|
!res.data ||
|
||||||
!res.data.data ||
|
!res.data.data ||
|
||||||
|
@ -22,7 +22,7 @@ limitations under the License. -->
|
|||||||
<span class="label">{{ t("language") }}</span>
|
<span class="label">{{ t("language") }}</span>
|
||||||
<el-switch
|
<el-switch
|
||||||
v-model="lang"
|
v-model="lang"
|
||||||
:change="setLang"
|
@change="setLang"
|
||||||
active-text="En"
|
active-text="En"
|
||||||
inactive-text="Zh"
|
inactive-text="Zh"
|
||||||
style="height: 25px"
|
style="height: 25px"
|
||||||
|
@ -23,7 +23,17 @@ limitations under the License. -->
|
|||||||
:destroy-on-close="true"
|
:destroy-on-close="true"
|
||||||
@closed="dashboardStore.setConfigPanel(false)"
|
@closed="dashboardStore.setConfigPanel(false)"
|
||||||
>
|
>
|
||||||
<config-edit />
|
<TopologyConfig v-if="dashboardStore.selectedGrid.type === 'Topology'" />
|
||||||
|
<Widget v-else />
|
||||||
|
</el-dialog>
|
||||||
|
<el-dialog
|
||||||
|
v-model="dashboardStore.showTopology"
|
||||||
|
:destroy-on-close="true"
|
||||||
|
fullscreen
|
||||||
|
@closed="dashboardStore.setTopology(false)"
|
||||||
|
custom-class="dark-dialog"
|
||||||
|
>
|
||||||
|
<Topology />
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -32,7 +42,9 @@ import { useI18n } from "vue-i18n";
|
|||||||
import GridLayout from "./panel/Layout.vue";
|
import GridLayout from "./panel/Layout.vue";
|
||||||
// import { LayoutConfig } from "@/types/dashboard";
|
// import { LayoutConfig } from "@/types/dashboard";
|
||||||
import Tool from "./panel/Tool.vue";
|
import Tool from "./panel/Tool.vue";
|
||||||
import ConfigEdit from "./configuration/ConfigEdit.vue";
|
import Widget from "./configuration/Widget.vue";
|
||||||
|
import TopologyConfig from "./configuration/Topology.vue";
|
||||||
|
import Topology from "./related/topology/Index.vue";
|
||||||
import { useDashboardStore } from "@/store/modules/dashboard";
|
import { useDashboardStore } from "@/store/modules/dashboard";
|
||||||
import { useAppStoreWithOut } from "@/store/modules/app";
|
import { useAppStoreWithOut } from "@/store/modules/app";
|
||||||
|
|
||||||
|
@ -39,14 +39,14 @@ limitations under the License. -->
|
|||||||
<el-table-column prop="date" label="Date" />
|
<el-table-column prop="date" label="Date" />
|
||||||
<el-table-column label="Operations">
|
<el-table-column label="Operations">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-button size="mini" @click="handleEdit(scope.$index, scope.row)">
|
<el-button size="small" @click="handleEdit(scope.$index, scope.row)">
|
||||||
{{ t("view") }}
|
{{ t("view") }}
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button size="mini" @click="handleEdit(scope.$index, scope.row)">
|
<el-button size="small" @click="handleEdit(scope.$index, scope.row)">
|
||||||
{{ t("edit") }}
|
{{ t("edit") }}
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button
|
<el-button
|
||||||
size="mini"
|
size="small"
|
||||||
type="danger"
|
type="danger"
|
||||||
@click="handleDelete(scope.$index, scope.row)"
|
@click="handleDelete(scope.$index, scope.row)"
|
||||||
>
|
>
|
||||||
|
@ -18,7 +18,7 @@ limitations under the License. -->
|
|||||||
<div class="item">
|
<div class="item">
|
||||||
<div class="label">{{ t("name") }}</div>
|
<div class="label">{{ t("name") }}</div>
|
||||||
<el-input
|
<el-input
|
||||||
size="small"
|
size="default"
|
||||||
v-model="states.name"
|
v-model="states.name"
|
||||||
placeholder="Please input name"
|
placeholder="Please input name"
|
||||||
/>
|
/>
|
||||||
@ -28,7 +28,6 @@ limitations under the License. -->
|
|||||||
<Selector
|
<Selector
|
||||||
v-model="states.selectedLayer"
|
v-model="states.selectedLayer"
|
||||||
:options="states.layers"
|
:options="states.layers"
|
||||||
size="small"
|
|
||||||
placeholder="Select a layer"
|
placeholder="Select a layer"
|
||||||
@change="changeLayer"
|
@change="changeLayer"
|
||||||
class="selectors"
|
class="selectors"
|
||||||
@ -39,14 +38,13 @@ limitations under the License. -->
|
|||||||
<Selector
|
<Selector
|
||||||
v-model="states.entity"
|
v-model="states.entity"
|
||||||
:options="EntityType"
|
:options="EntityType"
|
||||||
size="small"
|
|
||||||
placeholder="Select a entity"
|
placeholder="Select a entity"
|
||||||
@change="changeEntity"
|
@change="changeEntity"
|
||||||
class="selectors"
|
class="selectors"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="btn">
|
<div class="btn">
|
||||||
<el-button class="create" size="small" type="primary" @click="onCreate">
|
<el-button class="create" size="default" type="primary" @click="onCreate">
|
||||||
{{ t("create") }}
|
{{ t("create") }}
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -20,6 +20,7 @@ limitations under the License. -->
|
|||||||
placeholder="Please input dashboard name"
|
placeholder="Please input dashboard name"
|
||||||
@change="changeDashboard"
|
@change="changeDashboard"
|
||||||
class="selectors"
|
class="selectors"
|
||||||
|
size="small"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>{{ t("metrics") }}</div>
|
<div>{{ t("metrics") }}</div>
|
||||||
@ -31,7 +32,7 @@ limitations under the License. -->
|
|||||||
<Selector
|
<Selector
|
||||||
:value="metric"
|
:value="metric"
|
||||||
:options="states.metricList"
|
:options="states.metricList"
|
||||||
size="mini"
|
size="small"
|
||||||
placeholder="Select a metric"
|
placeholder="Select a metric"
|
||||||
@change="changeMetrics(index, $event)"
|
@change="changeMetrics(index, $event)"
|
||||||
class="selectors"
|
class="selectors"
|
||||||
@ -39,7 +40,7 @@ limitations under the License. -->
|
|||||||
<Selector
|
<Selector
|
||||||
:value="states.metricTypes[index]"
|
:value="states.metricTypes[index]"
|
||||||
:options="states.metricTypeList[index]"
|
:options="states.metricTypeList[index]"
|
||||||
size="mini"
|
size="small"
|
||||||
:disabled="
|
:disabled="
|
||||||
dashboardStore.selectedGrid.graph.type && !states.isTable && index !== 0
|
dashboardStore.selectedGrid.graph.type && !states.isTable && index !== 0
|
||||||
"
|
"
|
||||||
|
@ -18,7 +18,7 @@ limitations under the License. -->
|
|||||||
<el-input
|
<el-input
|
||||||
class="input"
|
class="input"
|
||||||
v-model="state.unit"
|
v-model="state.unit"
|
||||||
size="mini"
|
size="small"
|
||||||
placeholder="Please input Unit"
|
placeholder="Please input Unit"
|
||||||
@change="changeStandardOpt({ unit: state.unit })"
|
@change="changeStandardOpt({ unit: state.unit })"
|
||||||
/>
|
/>
|
||||||
@ -28,7 +28,7 @@ limitations under the License. -->
|
|||||||
<Selector
|
<Selector
|
||||||
:value="state.sortOrder"
|
:value="state.sortOrder"
|
||||||
:options="SortOrder"
|
:options="SortOrder"
|
||||||
size="mini"
|
size="small"
|
||||||
placeholder="Select a sort order"
|
placeholder="Select a sort order"
|
||||||
class="selector"
|
class="selector"
|
||||||
@change="changeStandardOpt({ sortOrder: state.sortOrder })"
|
@change="changeStandardOpt({ sortOrder: state.sortOrder })"
|
||||||
@ -39,7 +39,7 @@ limitations under the License. -->
|
|||||||
<el-input
|
<el-input
|
||||||
class="input"
|
class="input"
|
||||||
v-model="state.max"
|
v-model="state.max"
|
||||||
size="mini"
|
size="small"
|
||||||
placeholder="auto"
|
placeholder="auto"
|
||||||
@change="changeStandardOpt({ max: state.max })"
|
@change="changeStandardOpt({ max: state.max })"
|
||||||
/>
|
/>
|
||||||
@ -49,7 +49,7 @@ limitations under the License. -->
|
|||||||
<el-input
|
<el-input
|
||||||
class="input"
|
class="input"
|
||||||
v-model="state.min"
|
v-model="state.min"
|
||||||
size="mini"
|
size="small"
|
||||||
placeholder="auto"
|
placeholder="auto"
|
||||||
@change="changeStandardOpt({ min: state.min })"
|
@change="changeStandardOpt({ min: state.min })"
|
||||||
/>
|
/>
|
||||||
@ -59,7 +59,7 @@ limitations under the License. -->
|
|||||||
<el-input
|
<el-input
|
||||||
class="input"
|
class="input"
|
||||||
v-model="state.plus"
|
v-model="state.plus"
|
||||||
size="mini"
|
size="small"
|
||||||
placeholder="none"
|
placeholder="none"
|
||||||
@change="changeStandardOpt({ plus: state.plus })"
|
@change="changeStandardOpt({ plus: state.plus })"
|
||||||
/>
|
/>
|
||||||
@ -69,7 +69,7 @@ limitations under the License. -->
|
|||||||
<el-input
|
<el-input
|
||||||
class="input"
|
class="input"
|
||||||
v-model="state.minus"
|
v-model="state.minus"
|
||||||
size="mini"
|
size="small"
|
||||||
placeholder="none"
|
placeholder="none"
|
||||||
@change="changeStandardOpt({ minus: state.minus })"
|
@change="changeStandardOpt({ minus: state.minus })"
|
||||||
/>
|
/>
|
||||||
@ -79,7 +79,7 @@ limitations under the License. -->
|
|||||||
<el-input
|
<el-input
|
||||||
class="input"
|
class="input"
|
||||||
v-model="state.multiply"
|
v-model="state.multiply"
|
||||||
size="mini"
|
size="small"
|
||||||
placeholder="none"
|
placeholder="none"
|
||||||
@change="changeStandardOpt({ multiply: state.multiply })"
|
@change="changeStandardOpt({ multiply: state.multiply })"
|
||||||
/>
|
/>
|
||||||
@ -89,7 +89,7 @@ limitations under the License. -->
|
|||||||
<el-input
|
<el-input
|
||||||
class="input"
|
class="input"
|
||||||
v-model="state.divide"
|
v-model="state.divide"
|
||||||
size="mini"
|
size="small"
|
||||||
placeholder="none"
|
placeholder="none"
|
||||||
@change="changeStandardOpt({ divide: state.divide })"
|
@change="changeStandardOpt({ divide: state.divide })"
|
||||||
/>
|
/>
|
||||||
@ -99,7 +99,7 @@ limitations under the License. -->
|
|||||||
<el-input
|
<el-input
|
||||||
class="input"
|
class="input"
|
||||||
v-model="state.milliseconds"
|
v-model="state.milliseconds"
|
||||||
size="mini"
|
size="small"
|
||||||
placeholder="none"
|
placeholder="none"
|
||||||
@change="changeStandardOpt({ milliseconds: state.milliseconds })"
|
@change="changeStandardOpt({ milliseconds: state.milliseconds })"
|
||||||
/>
|
/>
|
||||||
@ -109,7 +109,7 @@ limitations under the License. -->
|
|||||||
<el-input
|
<el-input
|
||||||
class="input"
|
class="input"
|
||||||
v-model="state.seconds"
|
v-model="state.seconds"
|
||||||
size="mini"
|
size="small"
|
||||||
placeholder="none"
|
placeholder="none"
|
||||||
@change="changeStandardOpt({ seconds: state.seconds })"
|
@change="changeStandardOpt({ seconds: state.seconds })"
|
||||||
/>
|
/>
|
||||||
|
52
src/views/dashboard/configuration/Topology.vue
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
<!-- Licensed to the 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.
|
||||||
|
The 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. -->
|
||||||
|
<template>
|
||||||
|
<WidgetOptions />
|
||||||
|
<TopologyOptions />
|
||||||
|
<div class="footer">
|
||||||
|
<el-button size="small">
|
||||||
|
{{ t("cancel") }}
|
||||||
|
</el-button>
|
||||||
|
<el-button size="small" type="primary" @click="applyConfig">
|
||||||
|
{{ t("apply") }}
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
import WidgetOptions from "./WidgetOptions.vue";
|
||||||
|
import TopologyOptions from "./graph-styles/TopologyItem.vue";
|
||||||
|
import { useDashboardStore } from "@/store/modules/dashboard";
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
const dashboardStore = useDashboardStore();
|
||||||
|
|
||||||
|
function applyConfig() {
|
||||||
|
dashboardStore.setConfigs(dashboardStore.selectedGrid);
|
||||||
|
dashboardStore.setConfigPanel(false);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.footer {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
border-top: 1px solid #eee;
|
||||||
|
padding: 10px;
|
||||||
|
text-align: right;
|
||||||
|
width: 100%;
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
</style>
|
@ -60,10 +60,10 @@ limitations under the License. -->
|
|||||||
</el-collapse>
|
</el-collapse>
|
||||||
</div>
|
</div>
|
||||||
<div class="footer">
|
<div class="footer">
|
||||||
<el-button size="mini">
|
<el-button size="small">
|
||||||
{{ t("cancel") }}
|
{{ t("cancel") }}
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button size="mini" type="primary" @click="applyConfig">
|
<el-button size="small" type="primary" @click="applyConfig">
|
||||||
{{ t("apply") }}
|
{{ t("apply") }}
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
@ -18,7 +18,7 @@ limitations under the License. -->
|
|||||||
<el-input
|
<el-input
|
||||||
class="input"
|
class="input"
|
||||||
v-model="title"
|
v-model="title"
|
||||||
size="mini"
|
size="small"
|
||||||
placeholder="Please input title"
|
placeholder="Please input title"
|
||||||
@change="updateWidgetConfig({ title })"
|
@change="updateWidgetConfig({ title })"
|
||||||
/>
|
/>
|
||||||
@ -28,7 +28,7 @@ limitations under the License. -->
|
|||||||
<el-input
|
<el-input
|
||||||
class="input"
|
class="input"
|
||||||
v-model="tips"
|
v-model="tips"
|
||||||
size="mini"
|
size="small"
|
||||||
placeholder="Please input tips"
|
placeholder="Please input tips"
|
||||||
@change="updateWidgetConfig({ tips })"
|
@change="updateWidgetConfig({ tips })"
|
||||||
/>
|
/>
|
||||||
|
@ -27,7 +27,7 @@ limitations under the License. -->
|
|||||||
<el-input
|
<el-input
|
||||||
class="input"
|
class="input"
|
||||||
v-model="tableHeaderCol1"
|
v-model="tableHeaderCol1"
|
||||||
size="mini"
|
size="small"
|
||||||
placeholder="none"
|
placeholder="none"
|
||||||
@change="updateConfig({ tableHeaderCol1 })"
|
@change="updateConfig({ tableHeaderCol1 })"
|
||||||
/>
|
/>
|
||||||
@ -37,7 +37,7 @@ limitations under the License. -->
|
|||||||
<el-input
|
<el-input
|
||||||
class="input"
|
class="input"
|
||||||
v-model="tableHeaderCol2"
|
v-model="tableHeaderCol2"
|
||||||
size="mini"
|
size="small"
|
||||||
placeholder="none"
|
placeholder="none"
|
||||||
@change="updateConfig({ tableHeaderCol2 })"
|
@change="updateConfig({ tableHeaderCol2 })"
|
||||||
/>
|
/>
|
||||||
|
@ -18,7 +18,7 @@ limitations under the License. -->
|
|||||||
<el-input
|
<el-input
|
||||||
class="input"
|
class="input"
|
||||||
v-model="topN"
|
v-model="topN"
|
||||||
size="mini"
|
size="small"
|
||||||
placeholder="none"
|
placeholder="none"
|
||||||
type="number"
|
type="number"
|
||||||
:min="1"
|
:min="1"
|
||||||
|
145
src/views/dashboard/configuration/graph-styles/TopologyItem.vue
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
<!-- Licensed to the 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.
|
||||||
|
The 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. -->
|
||||||
|
<template>
|
||||||
|
<div class="item">
|
||||||
|
<span class="label">{{ t("backgroundColors") }}</span>
|
||||||
|
<Selector
|
||||||
|
:value="backgroundColor"
|
||||||
|
:options="colors"
|
||||||
|
size="small"
|
||||||
|
placeholder="Select a color"
|
||||||
|
class="input"
|
||||||
|
@change="changeConfig({ backgroundColor: $event[0].value })"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="item">
|
||||||
|
<span class="label">{{ t("fontSize") }}</span>
|
||||||
|
<el-slider
|
||||||
|
class="slider"
|
||||||
|
v-model="fontSize"
|
||||||
|
show-input
|
||||||
|
input-size="small"
|
||||||
|
:min="12"
|
||||||
|
:max="30"
|
||||||
|
:step="1"
|
||||||
|
@change="changeConfig({ fontSize })"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="item">
|
||||||
|
<span class="label">{{ t("fontColors") }}</span>
|
||||||
|
<Selector
|
||||||
|
:value="fontColor"
|
||||||
|
:options="colors"
|
||||||
|
size="small"
|
||||||
|
placeholder="Select a color"
|
||||||
|
class="input"
|
||||||
|
@change="changeConfig({ fontColor: $event[0].value })"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="item">
|
||||||
|
<span class="label">{{ t("iconTheme") }}</span>
|
||||||
|
<el-switch
|
||||||
|
v-model="iconTheme"
|
||||||
|
active-text="Light"
|
||||||
|
inactive-text="Dark"
|
||||||
|
@change="changeConfig({ iconTheme })"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="item">
|
||||||
|
<span class="label">{{ t("content") }}</span>
|
||||||
|
<el-input
|
||||||
|
class="input"
|
||||||
|
v-model="content"
|
||||||
|
size="small"
|
||||||
|
@change="changeConfig({ content })"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="item">
|
||||||
|
<span class="label">{{ t("defaultDepth") }}</span>
|
||||||
|
<Selector
|
||||||
|
class="input"
|
||||||
|
size="small"
|
||||||
|
:value="depth"
|
||||||
|
:options="DepthList"
|
||||||
|
@change="changeDepth($event)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
import { ref } from "vue";
|
||||||
|
import { useDashboardStore } from "@/store/modules/dashboard";
|
||||||
|
import { DepthList } from "../../data";
|
||||||
|
import { Option } from "@/types/app";
|
||||||
|
import { useTopologyStore } from "@/store/modules/topology";
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
const dashboardStore = useDashboardStore();
|
||||||
|
const topologyStore = useTopologyStore();
|
||||||
|
const { selectedGrid } = dashboardStore;
|
||||||
|
const iconTheme = ref(selectedGrid.graph.iconTheme || true);
|
||||||
|
const backgroundColor = ref(selectedGrid.graph.backgroundColor || "green");
|
||||||
|
const fontColor = ref(selectedGrid.graph.fontColor || "white");
|
||||||
|
const content = ref<string>(selectedGrid.graph.content);
|
||||||
|
const fontSize = ref<number>(selectedGrid.graph.fontSize);
|
||||||
|
const depth = ref<string>(selectedGrid.graph.depth || "2");
|
||||||
|
const colors = [
|
||||||
|
{
|
||||||
|
label: "Green",
|
||||||
|
value: "green",
|
||||||
|
},
|
||||||
|
{ label: "Blue", value: "blue" },
|
||||||
|
{ label: "Red", value: "red" },
|
||||||
|
{ label: "Grey", value: "grey" },
|
||||||
|
{ label: "White", value: "white" },
|
||||||
|
{ label: "Black", value: "black" },
|
||||||
|
{ label: "Orange", value: "orange" },
|
||||||
|
];
|
||||||
|
topologyStore.setDefaultDepth(depth.value);
|
||||||
|
function changeConfig(param: { [key: string]: unknown }) {
|
||||||
|
const { selectedGrid } = dashboardStore;
|
||||||
|
const graph = {
|
||||||
|
...selectedGrid.graph,
|
||||||
|
...param,
|
||||||
|
};
|
||||||
|
dashboardStore.selectWidget({ ...selectedGrid, graph });
|
||||||
|
}
|
||||||
|
function changeDepth(opt: Option[]) {
|
||||||
|
const val = opt[0].value;
|
||||||
|
topologyStore.setDefaultDepth(val);
|
||||||
|
changeConfig({ depth: val });
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.slider {
|
||||||
|
width: 500px;
|
||||||
|
margin-top: -3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input {
|
||||||
|
width: 500px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
</style>
|
143
src/views/dashboard/controls/Topology.vue
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
<!-- Licensed to the 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.
|
||||||
|
The 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. -->
|
||||||
|
<template>
|
||||||
|
<div class="topology">
|
||||||
|
<div class="header flex-h">
|
||||||
|
<div>{{ data.widget?.title || "" }}</div>
|
||||||
|
<div>
|
||||||
|
<el-tooltip :content="data.widget?.tips">
|
||||||
|
<span>
|
||||||
|
<Icon
|
||||||
|
iconName="info_outline"
|
||||||
|
size="sm"
|
||||||
|
class="operation"
|
||||||
|
v-show="data.widget?.tips"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</el-tooltip>
|
||||||
|
<el-popover placement="bottom" trigger="click" :width="100">
|
||||||
|
<template #reference>
|
||||||
|
<span>
|
||||||
|
<Icon iconName="ellipsis_v" size="middle" class="operation" />
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
<div class="tools" @click="editConfig">
|
||||||
|
<span>{{ t("edit") }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="tools" @click="removeTopo">
|
||||||
|
<span>{{ t("delete") }}</span>
|
||||||
|
</div>
|
||||||
|
</el-popover>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="body"
|
||||||
|
@click="ViewTopology"
|
||||||
|
:style="{ backgroundColor: Colors[data.graph.backgroundColor] }"
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
:iconName="data.graph.iconTheme ? 'topology-light' : 'topology-dark'"
|
||||||
|
size="middle"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
:style="{
|
||||||
|
color: Colors[data.graph.fontColor],
|
||||||
|
fontSize: data.graph.fontSize + 'px',
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
{{ data.graph.content }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import type { PropType } from "vue";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
import { useDashboardStore } from "@/store/modules/dashboard";
|
||||||
|
import { Colors } from "../data";
|
||||||
|
/*global defineProps */
|
||||||
|
const props = defineProps({
|
||||||
|
data: {
|
||||||
|
type: Object as PropType<any>,
|
||||||
|
default: () => ({ graph: {} }),
|
||||||
|
},
|
||||||
|
activeIndex: { type: String, default: "" },
|
||||||
|
});
|
||||||
|
const { t } = useI18n();
|
||||||
|
const dashboardStore = useDashboardStore();
|
||||||
|
|
||||||
|
function editConfig() {
|
||||||
|
dashboardStore.setConfigPanel(true);
|
||||||
|
dashboardStore.selectWidget(props.data);
|
||||||
|
}
|
||||||
|
function ViewTopology() {
|
||||||
|
dashboardStore.setTopology(true);
|
||||||
|
}
|
||||||
|
function removeTopo() {
|
||||||
|
dashboardStore.removeControls(props.data);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.topology {
|
||||||
|
font-size: 12px;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
height: 30px;
|
||||||
|
padding: 5px;
|
||||||
|
width: 100%;
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.operation {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tools {
|
||||||
|
padding: 5px 0;
|
||||||
|
color: #999;
|
||||||
|
cursor: pointer;
|
||||||
|
position: relative;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: #409eff;
|
||||||
|
background-color: #eee;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.body {
|
||||||
|
text-align: center;
|
||||||
|
width: 100%;
|
||||||
|
height: calc(100% - 30px);
|
||||||
|
cursor: pointer;
|
||||||
|
box-sizing: border-box;
|
||||||
|
color: #333;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-box-orient: horizontal;
|
||||||
|
-webkit-box-pack: center;
|
||||||
|
-webkit-box-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-data {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #888;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
padding-top: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
@ -18,20 +18,20 @@ limitations under the License. -->
|
|||||||
<div>{{ data.widget?.title || "" }}</div>
|
<div>{{ data.widget?.title || "" }}</div>
|
||||||
<div>
|
<div>
|
||||||
<el-tooltip :content="data.widget?.tips">
|
<el-tooltip :content="data.widget?.tips">
|
||||||
|
<span>
|
||||||
<Icon
|
<Icon
|
||||||
iconName="info_outline"
|
iconName="info_outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
class="operation"
|
class="operation"
|
||||||
v-show="data.widget?.tips"
|
v-show="data.widget?.tips"
|
||||||
/>
|
/>
|
||||||
|
</span>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
<el-popover
|
<el-popover placement="bottom" trigger="click" :width="100">
|
||||||
placement="bottom"
|
|
||||||
trigger="click"
|
|
||||||
:style="{ width: '100px' }"
|
|
||||||
>
|
|
||||||
<template #reference>
|
<template #reference>
|
||||||
|
<span>
|
||||||
<Icon iconName="ellipsis_v" size="middle" class="operation" />
|
<Icon iconName="ellipsis_v" size="middle" class="operation" />
|
||||||
|
</span>
|
||||||
</template>
|
</template>
|
||||||
<div class="tools" @click="editConfig">
|
<div class="tools" @click="editConfig">
|
||||||
<span>{{ t("edit") }}</span>
|
<span>{{ t("edit") }}</span>
|
||||||
@ -99,6 +99,7 @@ export default defineComponent({
|
|||||||
|
|
||||||
async function queryMetrics() {
|
async function queryMetrics() {
|
||||||
const params = await useQueryProcessor(props.data);
|
const params = await useQueryProcessor(props.data);
|
||||||
|
|
||||||
if (!params) {
|
if (!params) {
|
||||||
state.source = {};
|
state.source = {};
|
||||||
return;
|
return;
|
||||||
@ -137,15 +138,18 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
watch(
|
watch(
|
||||||
() => selectorStore.currentService,
|
() => [selectorStore.currentService, selectorStore.currentDestService],
|
||||||
() => {
|
() => {
|
||||||
if (dashboardStore.entity === EntityType[0].value) {
|
if (
|
||||||
|
dashboardStore.entity === EntityType[0].value ||
|
||||||
|
dashboardStore.entity === EntityType[4].value
|
||||||
|
) {
|
||||||
queryMetrics();
|
queryMetrics();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
watch(
|
watch(
|
||||||
() => selectorStore.currentPod,
|
() => [selectorStore.currentPod, selectorStore.currentDestPod],
|
||||||
() => {
|
() => {
|
||||||
if (dashboardStore.entity === EntityType[0].value) {
|
if (dashboardStore.entity === EntityType[0].value) {
|
||||||
return;
|
return;
|
||||||
|
@ -142,7 +142,7 @@ export enum MetricCatalog {
|
|||||||
export const EntityType = [
|
export const EntityType = [
|
||||||
{ value: "Service", label: "Service", key: 1 },
|
{ value: "Service", label: "Service", key: 1 },
|
||||||
{ value: "All", label: "All", key: 10 },
|
{ value: "All", label: "All", key: 10 },
|
||||||
{ value: "Endpoint", label: "Service Endpoint", key: 3 },
|
{ value: "Endpoint", label: "Endpoint", key: 3 },
|
||||||
{ value: "ServiceInstance", label: "Service Instance", key: 3 },
|
{ value: "ServiceInstance", label: "Service Instance", key: 3 },
|
||||||
{ value: "ServiceRelation", label: "Service Relation", key: 2 },
|
{ value: "ServiceRelation", label: "Service Relation", key: 2 },
|
||||||
{
|
{
|
||||||
@ -152,6 +152,7 @@ export const EntityType = [
|
|||||||
},
|
},
|
||||||
{ value: "EndpointRelation", label: "Endpoint Relation", key: 4 },
|
{ value: "EndpointRelation", label: "Endpoint Relation", key: 4 },
|
||||||
];
|
];
|
||||||
|
export const hasTopology = ["All", "Service", "ServiceRelation", "Endpoint"];
|
||||||
export const TableEntity: any = {
|
export const TableEntity: any = {
|
||||||
InstanceList: EntityType[3].value,
|
InstanceList: EntityType[3].value,
|
||||||
EndpointList: EntityType[2].value,
|
EndpointList: EntityType[2].value,
|
||||||
@ -165,8 +166,40 @@ export const ToolIcons = [
|
|||||||
{ name: "playlist_add", content: "Add Widget", id: "addWidget" },
|
{ name: "playlist_add", content: "Add Widget", id: "addWidget" },
|
||||||
{ name: "all_inbox", content: "Add Tab", id: "addTab" },
|
{ name: "all_inbox", content: "Add Tab", id: "addTab" },
|
||||||
// { name: "insert_image", content: "Add Image", id: "addImage" },
|
// { name: "insert_image", content: "Add Image", id: "addImage" },
|
||||||
{ name: "save_alt", content: "Export", id: "export" },
|
// { name: "save_alt", content: "Export", id: "export" },
|
||||||
{ name: "folder_open", content: "Import", id: "import" },
|
// { name: "folder_open", content: "Import", id: "import" },
|
||||||
{ name: "settings", content: "Settings", id: "settings" },
|
// { name: "settings", content: "Settings", id: "settings" },
|
||||||
{ name: "save", content: "Apply", id: "applay" },
|
{ name: "device_hub", content: "Add Topology", id: "topology" },
|
||||||
|
// { name: "save", content: "Apply", id: "apply" },
|
||||||
];
|
];
|
||||||
|
export const ScopeType = [
|
||||||
|
{ value: "Service", label: "Service", key: 1 },
|
||||||
|
{ value: "Endpoint", label: "Endpoint", key: 3 },
|
||||||
|
{ value: "ServiceInstance", label: "Service Instance", key: 3 },
|
||||||
|
];
|
||||||
|
export const LegendConditions = [
|
||||||
|
{ label: "&&", value: "and" },
|
||||||
|
{ label: "||", value: "or" },
|
||||||
|
];
|
||||||
|
export const MetricConditions = [
|
||||||
|
{ label: ">", value: ">" },
|
||||||
|
{ label: "<", value: "<" },
|
||||||
|
];
|
||||||
|
export enum LegendOpt {
|
||||||
|
NAME = "name",
|
||||||
|
VALUE = "value",
|
||||||
|
CONDITION = "condition",
|
||||||
|
}
|
||||||
|
export const DepthList = ["1", "2", "3", "4", "5"].map((item: string) => ({
|
||||||
|
value: item,
|
||||||
|
label: item,
|
||||||
|
}));
|
||||||
|
export const Colors: any = {
|
||||||
|
green: "#67C23A",
|
||||||
|
blue: "#409EFF",
|
||||||
|
red: "#F56C6C",
|
||||||
|
grey: "#909399",
|
||||||
|
white: "#fff",
|
||||||
|
black: "#000",
|
||||||
|
orange: "#E6A23C",
|
||||||
|
};
|
||||||
|
@ -42,10 +42,11 @@ import { useDashboardStore } from "@/store/modules/dashboard";
|
|||||||
import { LayoutConfig } from "@/types/dashboard";
|
import { LayoutConfig } from "@/types/dashboard";
|
||||||
import Widget from "../controls/Widget.vue";
|
import Widget from "../controls/Widget.vue";
|
||||||
import Tab from "../controls/Tab.vue";
|
import Tab from "../controls/Tab.vue";
|
||||||
|
import Topology from "../controls/Topology.vue";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: "Layout",
|
name: "Layout",
|
||||||
components: { Widget, Tab },
|
components: { Widget, Tab, Topology },
|
||||||
setup() {
|
setup() {
|
||||||
const dashboardStore = useDashboardStore();
|
const dashboardStore = useDashboardStore();
|
||||||
function layoutUpdatedEvent(newLayout: LayoutConfig[]) {
|
function layoutUpdatedEvent(newLayout: LayoutConfig[]) {
|
||||||
|
@ -20,7 +20,7 @@ limitations under the License. -->
|
|||||||
<Selector
|
<Selector
|
||||||
v-model="states.currentService"
|
v-model="states.currentService"
|
||||||
:options="selectorStore.services"
|
:options="selectorStore.services"
|
||||||
size="mini"
|
size="small"
|
||||||
placeholder="Select a service"
|
placeholder="Select a service"
|
||||||
@change="changeService"
|
@change="changeService"
|
||||||
class="selectors"
|
class="selectors"
|
||||||
@ -29,7 +29,7 @@ limitations under the License. -->
|
|||||||
<div class="selectors-item" v-if="states.key === 3 || states.key === 4">
|
<div class="selectors-item" v-if="states.key === 3 || states.key === 4">
|
||||||
<span class="label">
|
<span class="label">
|
||||||
{{
|
{{
|
||||||
dashboardStore.entity === "Endpoint"
|
["EndpointRelation", "Endpoint"].includes(dashboardStore.entity)
|
||||||
? "$Endpoint"
|
? "$Endpoint"
|
||||||
: "$ServiceInstance"
|
: "$ServiceInstance"
|
||||||
}}
|
}}
|
||||||
@ -37,48 +37,58 @@ limitations under the License. -->
|
|||||||
<Selector
|
<Selector
|
||||||
v-model="states.currentPod"
|
v-model="states.currentPod"
|
||||||
:options="selectorStore.pods"
|
:options="selectorStore.pods"
|
||||||
size="mini"
|
size="small"
|
||||||
placeholder="Select a data"
|
placeholder="Select a data"
|
||||||
@change="changePods"
|
@change="changePods"
|
||||||
class="selectorPod"
|
class="selectorPod"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="selectors-item" v-if="states.key === 2">
|
<div class="selectors-item" v-if="states.key === 2 || states.key === 4">
|
||||||
<span class="label">$DestinationService</span>
|
<span class="label">$DestinationService</span>
|
||||||
<Selector
|
<Selector
|
||||||
v-model="selectorStore.currentDestService"
|
v-model="states.currentDestService"
|
||||||
:options="selectorStore.services"
|
:options="selectorStore.destServices"
|
||||||
size="mini"
|
size="small"
|
||||||
placeholder="Select a service"
|
placeholder="Select a service"
|
||||||
@change="changeService"
|
@change="changeDestService"
|
||||||
class="selectors"
|
class="selectors"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="selectors-item" v-if="states.key === 4">
|
<div class="selectors-item" v-if="states.key === 4">
|
||||||
<span class="label">$DestinationServiceInstance</span>
|
<span class="label">
|
||||||
|
{{
|
||||||
|
dashboardStore.entity === "EndpointRelation"
|
||||||
|
? "$DestinationEndpoint"
|
||||||
|
: "$DestinationServiceInstance"
|
||||||
|
}}</span
|
||||||
|
>
|
||||||
<Selector
|
<Selector
|
||||||
v-model="states.currentPod"
|
v-model="states.currentDestPod"
|
||||||
:options="selectorStore.pods"
|
:options="selectorStore.destPods"
|
||||||
size="mini"
|
size="small"
|
||||||
placeholder="Select a data"
|
placeholder="Select a data"
|
||||||
@change="changePods"
|
@change="changePods"
|
||||||
class="selectors"
|
class="selectorPod"
|
||||||
:borderRadius="4"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="tool-icons">
|
<div class="tool-icons">
|
||||||
<el-tooltip
|
<span
|
||||||
|
@click="clickIcons(t)"
|
||||||
v-for="(t, index) in ToolIcons"
|
v-for="(t, index) in ToolIcons"
|
||||||
:key="index"
|
:key="index"
|
||||||
class="item"
|
:title="t.content"
|
||||||
:content="t.content"
|
|
||||||
placement="top"
|
|
||||||
>
|
>
|
||||||
<span class="icon-btn" @click="clickIcons(t)">
|
<Icon
|
||||||
<Icon size="sm" :iconName="t.name" />
|
class="icon-btn"
|
||||||
|
size="sm"
|
||||||
|
:iconName="t.name"
|
||||||
|
v-if="
|
||||||
|
t.id !== 'topology' ||
|
||||||
|
(t.id === 'topology' && hasTopology.includes(dashboardStore.entity))
|
||||||
|
"
|
||||||
|
/>
|
||||||
</span>
|
</span>
|
||||||
</el-tooltip>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -88,7 +98,7 @@ import { reactive, watch } from "vue";
|
|||||||
import { useRoute } from "vue-router";
|
import { useRoute } from "vue-router";
|
||||||
import { useDashboardStore } from "@/store/modules/dashboard";
|
import { useDashboardStore } from "@/store/modules/dashboard";
|
||||||
import { useAppStoreWithOut } from "@/store/modules/app";
|
import { useAppStoreWithOut } from "@/store/modules/app";
|
||||||
import { EntityType, ToolIcons } from "../data";
|
import { EntityType, ToolIcons, hasTopology } from "../data";
|
||||||
import { useSelectorStore } from "@/store/modules/selectors";
|
import { useSelectorStore } from "@/store/modules/selectors";
|
||||||
import { ElMessage } from "element-plus";
|
import { ElMessage } from "element-plus";
|
||||||
import { Option } from "@/types/app";
|
import { Option } from "@/types/app";
|
||||||
@ -105,12 +115,16 @@ const states = reactive<{
|
|||||||
key: number;
|
key: number;
|
||||||
currentService: string;
|
currentService: string;
|
||||||
currentPod: string;
|
currentPod: string;
|
||||||
|
currentDestService: string;
|
||||||
|
currentDestPod: string;
|
||||||
}>({
|
}>({
|
||||||
destService: "",
|
destService: "",
|
||||||
destPod: "",
|
destPod: "",
|
||||||
key: (type && type.key) || 0,
|
key: (type && type.key) || 0,
|
||||||
currentService: "",
|
currentService: "",
|
||||||
currentPod: "",
|
currentPod: "",
|
||||||
|
currentDestService: "",
|
||||||
|
currentDestPod: "",
|
||||||
});
|
});
|
||||||
|
|
||||||
dashboardStore.setLayer(String(params.layerId));
|
dashboardStore.setLayer(String(params.layerId));
|
||||||
@ -127,56 +141,136 @@ function initSelector() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function setSelector() {
|
async function setSelector() {
|
||||||
if (params.podId) {
|
if (
|
||||||
await selectorStore.getService(String(params.serviceId));
|
[
|
||||||
states.currentService = selectorStore.currentService.value;
|
EntityType[2].value,
|
||||||
await fetchPods(String(params.entity), false);
|
EntityType[3].value,
|
||||||
const currentPod = selectorStore.pods.filter(
|
EntityType[5].value,
|
||||||
(d: { id: string }) => d.id === String(params.podId)
|
EntityType[6].value,
|
||||||
)[0];
|
].includes(String(params.entity))
|
||||||
selectorStore.setCurrentPod(currentPod);
|
) {
|
||||||
states.currentPod = currentPod.label;
|
setSourceSelector();
|
||||||
|
if (
|
||||||
|
[EntityType[2].value, EntityType[3].value].includes(String(params.entity))
|
||||||
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// entity=Service with serviceId
|
setDestSelector();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// entity=Service/ServiceRelation
|
||||||
const json = await selectorStore.fetchServices(dashboardStore.layerId);
|
const json = await selectorStore.fetchServices(dashboardStore.layerId);
|
||||||
if (json.errors) {
|
if (json.errors) {
|
||||||
ElMessage.error(json.errors);
|
ElMessage.error(json.errors);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const currentService = selectorStore.services.filter(
|
let currentService, currentDestService;
|
||||||
(d: { id: string }) => d.id === String(params.serviceId)
|
for (const d of selectorStore.services) {
|
||||||
)[0];
|
if (d.id === String(params.serviceId)) {
|
||||||
|
currentService = d;
|
||||||
|
}
|
||||||
|
if (d.id === String(params.destServiceId)) {
|
||||||
|
currentDestService = d;
|
||||||
|
}
|
||||||
|
}
|
||||||
selectorStore.setCurrentService(currentService);
|
selectorStore.setCurrentService(currentService);
|
||||||
|
selectorStore.setCurrentDestService(currentDestService);
|
||||||
states.currentService = selectorStore.currentService.value;
|
states.currentService = selectorStore.currentService.value;
|
||||||
|
states.currentDestService = selectorStore.currentDestService.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setSourceSelector() {
|
||||||
|
await selectorStore.getService(String(params.serviceId));
|
||||||
|
states.currentService = selectorStore.currentService.value;
|
||||||
|
const e = String(params.entity).split("Relation")[0];
|
||||||
|
await fetchPods(e, selectorStore.currentService.id, false);
|
||||||
|
if (!(selectorStore.pods.length && selectorStore.pods[0])) {
|
||||||
|
selectorStore.setCurrentPod(null);
|
||||||
|
states.currentPod = "";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const pod = params.podId || selectorStore.pods[0].id;
|
||||||
|
const currentPod = selectorStore.pods.filter(
|
||||||
|
(d: { id: string }) => d.id === pod
|
||||||
|
)[0];
|
||||||
|
if (currentPod) {
|
||||||
|
selectorStore.setCurrentPod(currentPod);
|
||||||
|
states.currentPod = currentPod.label;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setDestSelector() {
|
||||||
|
await selectorStore.getService(String(params.destServiceId), true);
|
||||||
|
states.currentDestService = selectorStore.currentDestService.value;
|
||||||
|
await fetchPods(
|
||||||
|
String(params.entity),
|
||||||
|
selectorStore.currentDestService.id,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
if (!(selectorStore.destPods.length && selectorStore.destPods[0])) {
|
||||||
|
selectorStore.setCurrentDestPod(null);
|
||||||
|
states.currentDestPod = "";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const destPod = params.destPodId || selectorStore.destPods[0].id;
|
||||||
|
const currentDestPod = selectorStore.destPods.filter(
|
||||||
|
(d: { id: string }) => d.id === destPod
|
||||||
|
)[0];
|
||||||
|
if (currentDestPod) {
|
||||||
|
selectorStore.setCurrentDestPod(currentDestPod);
|
||||||
|
states.currentDestPod = currentDestPod.label;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getServices() {
|
async function getServices() {
|
||||||
if (!dashboardStore.layerId) {
|
if (!dashboardStore.layerId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (dashboardStore.entity === EntityType[1].value) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const json = await selectorStore.fetchServices(dashboardStore.layerId);
|
const json = await selectorStore.fetchServices(dashboardStore.layerId);
|
||||||
if (json.errors) {
|
if (json.errors) {
|
||||||
ElMessage.error(json.errors);
|
ElMessage.error(json.errors);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (dashboardStore.entity === EntityType[1].value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
selectorStore.setCurrentService(
|
selectorStore.setCurrentService(
|
||||||
selectorStore.services.length ? selectorStore.services[0] : null
|
selectorStore.services.length ? selectorStore.services[0] : null
|
||||||
);
|
);
|
||||||
|
selectorStore.setCurrentDestService(
|
||||||
|
selectorStore.services.length ? selectorStore.services[1] : null
|
||||||
|
);
|
||||||
states.currentService = selectorStore.currentService.value;
|
states.currentService = selectorStore.currentService.value;
|
||||||
fetchPods(dashboardStore.entity, true);
|
states.currentDestService = selectorStore.currentDestService.value;
|
||||||
|
const e = dashboardStore.entity.split("Relation")[0];
|
||||||
|
if (
|
||||||
|
[EntityType[2].value, EntityType[3].value].includes(dashboardStore.entity)
|
||||||
|
) {
|
||||||
|
fetchPods(e, selectorStore.currentService.id, true);
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
[EntityType[5].value, EntityType[6].value].includes(dashboardStore.entity)
|
||||||
|
) {
|
||||||
|
fetchPods(dashboardStore.entity, selectorStore.currentDestService.id, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function changeService(service: Service[]) {
|
async function changeService(service: Service[]) {
|
||||||
if (service[0]) {
|
if (service[0]) {
|
||||||
states.currentService = service[0].value;
|
states.currentService = service[0].value;
|
||||||
selectorStore.setCurrentService(service[0]);
|
selectorStore.setCurrentService(service[0]);
|
||||||
fetchPods(dashboardStore.entity, true);
|
fetchPods(dashboardStore.entity, selectorStore.currentService.id, true);
|
||||||
} else {
|
} else {
|
||||||
selectorStore.setCurrentService("");
|
selectorStore.setCurrentService(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function changeDestService(service: Service[]) {
|
||||||
|
if (service[0]) {
|
||||||
|
states.currentDestService = service[0].value;
|
||||||
|
selectorStore.setCurrentDestService(service[0]);
|
||||||
|
} else {
|
||||||
|
selectorStore.setCurrentDestService(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -199,6 +293,9 @@ function clickIcons(t: { id: string; content: string; name: string }) {
|
|||||||
case "addImage":
|
case "addImage":
|
||||||
dashboardStore.addControl("Image");
|
dashboardStore.addControl("Image");
|
||||||
break;
|
break;
|
||||||
|
case "topology":
|
||||||
|
dashboardStore.addControl("Topology");
|
||||||
|
break;
|
||||||
case "settings":
|
case "settings":
|
||||||
dashboardStore.setConfigPanel(true);
|
dashboardStore.setConfigPanel(true);
|
||||||
break;
|
break;
|
||||||
@ -207,11 +304,11 @@ function clickIcons(t: { id: string; content: string; name: string }) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchPods(type: string, setPod: boolean) {
|
async function fetchPods(type: string, serviceId: string, setPod: boolean) {
|
||||||
let resp;
|
let resp;
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case "Endpoint":
|
case EntityType[2].value:
|
||||||
resp = await selectorStore.getEndpoints();
|
resp = await selectorStore.getEndpoints({ serviceId });
|
||||||
if (setPod) {
|
if (setPod) {
|
||||||
selectorStore.setCurrentPod(
|
selectorStore.setCurrentPod(
|
||||||
selectorStore.pods.length ? selectorStore.pods[0] : null
|
selectorStore.pods.length ? selectorStore.pods[0] : null
|
||||||
@ -219,8 +316,8 @@ async function fetchPods(type: string, setPod: boolean) {
|
|||||||
states.currentPod = selectorStore.currentPod.label;
|
states.currentPod = selectorStore.currentPod.label;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "ServiceInstance":
|
case EntityType[3].value:
|
||||||
resp = await selectorStore.getServiceInstances();
|
resp = await selectorStore.getServiceInstances({ serviceId });
|
||||||
if (setPod) {
|
if (setPod) {
|
||||||
selectorStore.setCurrentPod(
|
selectorStore.setCurrentPod(
|
||||||
selectorStore.pods.length ? selectorStore.pods[0] : null
|
selectorStore.pods.length ? selectorStore.pods[0] : null
|
||||||
@ -228,6 +325,27 @@ async function fetchPods(type: string, setPod: boolean) {
|
|||||||
states.currentPod = selectorStore.currentPod.label;
|
states.currentPod = selectorStore.currentPod.label;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case EntityType[6].value:
|
||||||
|
resp = await selectorStore.getEndpoints({ serviceId, isRelation: true });
|
||||||
|
if (setPod) {
|
||||||
|
selectorStore.setCurrentDestPod(
|
||||||
|
selectorStore.destPods.length ? selectorStore.destPods[0] : null
|
||||||
|
);
|
||||||
|
states.currentDestPod = selectorStore.currentDestPod.label;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case EntityType[5].value:
|
||||||
|
resp = await selectorStore.getServiceInstances({
|
||||||
|
serviceId,
|
||||||
|
isRelation: true,
|
||||||
|
});
|
||||||
|
if (setPod) {
|
||||||
|
selectorStore.setCurrentDestPod(
|
||||||
|
selectorStore.destPods.length ? selectorStore.destPods[0] : null
|
||||||
|
);
|
||||||
|
states.currentDestPod = selectorStore.currentDestPod.label;
|
||||||
|
}
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
resp = {};
|
resp = {};
|
||||||
}
|
}
|
||||||
@ -258,9 +376,13 @@ watch(
|
|||||||
padding: 4px 2px;
|
padding: 4px 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tool-icons {
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
.icon-btn {
|
.icon-btn {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
padding: 0 5px;
|
padding: 3px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
border: 1px solid #ccc;
|
border: 1px solid #ccc;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
@ -268,9 +390,6 @@ watch(
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
background-color: #eee;
|
background-color: #eee;
|
||||||
color: #666;
|
color: #666;
|
||||||
}
|
|
||||||
|
|
||||||
.item {
|
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
30
src/views/dashboard/related/topology/Index.vue
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<!-- Licensed to the 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.
|
||||||
|
The 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. -->
|
||||||
|
<template>
|
||||||
|
<PodTopology v-if="isSankey" />
|
||||||
|
<Graph v-else />
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref } from "vue";
|
||||||
|
import Graph from "./components/Graph.vue";
|
||||||
|
import PodTopology from "./components/PodTopology.vue";
|
||||||
|
import { EntityType } from "../../data";
|
||||||
|
import { useDashboardStore } from "@/store/modules/dashboard";
|
||||||
|
|
||||||
|
const dashboardStore = useDashboardStore();
|
||||||
|
const isSankey = ref<boolean>(
|
||||||
|
[EntityType[2].value, EntityType[4].value].includes(dashboardStore.entity)
|
||||||
|
);
|
||||||
|
</script>
|
586
src/views/dashboard/related/topology/components/Graph.vue
Normal file
@ -0,0 +1,586 @@
|
|||||||
|
<!-- Licensed to the 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.
|
||||||
|
The 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. -->
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
ref="chart"
|
||||||
|
class="micro-topo-chart"
|
||||||
|
v-loading="loading"
|
||||||
|
:style="`height: ${height}px`"
|
||||||
|
>
|
||||||
|
<div class="setting" v-show="showSetting">
|
||||||
|
<Settings @update="updateSettings" @updateNodes="freshNodes" />
|
||||||
|
</div>
|
||||||
|
<div class="tool">
|
||||||
|
<span class="label">{{ t("currentDepth") }}</span>
|
||||||
|
<Selector
|
||||||
|
class="inputs"
|
||||||
|
:value="depth"
|
||||||
|
:options="DepthList"
|
||||||
|
placeholder="Select a option"
|
||||||
|
@change="changeDepth"
|
||||||
|
/>
|
||||||
|
<span class="switch-icon ml-5" title="Settings" @click="setConfig">
|
||||||
|
<Icon size="middle" iconName="settings" />
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="switch-icon ml-5"
|
||||||
|
title="Back to overview topology"
|
||||||
|
@click="backToTopology"
|
||||||
|
>
|
||||||
|
<Icon size="middle" iconName="keyboard_backspace" />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="operations-list"
|
||||||
|
v-if="topologyStore.node"
|
||||||
|
:style="{
|
||||||
|
top: operationsPos.y + 'px',
|
||||||
|
left: operationsPos.x + 'px',
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
v-for="(item, index) of items"
|
||||||
|
:key="index"
|
||||||
|
@click="item.func(item.dashboard)"
|
||||||
|
>
|
||||||
|
{{ item.title }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, onMounted, onBeforeUnmount, reactive } from "vue";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
import * as d3 from "d3";
|
||||||
|
import d3tip from "d3-tip";
|
||||||
|
import zoom from "../utils/zoom";
|
||||||
|
import { simulationInit, simulationSkip } from "../utils/simulation";
|
||||||
|
import nodeElement from "../utils/nodeElement";
|
||||||
|
import { linkElement, anchorElement, arrowMarker } from "../utils/linkElement";
|
||||||
|
import topoLegend from "../utils/legend";
|
||||||
|
import { Node, Call } from "@/types/topology";
|
||||||
|
import { useSelectorStore } from "@/store/modules/selectors";
|
||||||
|
import { useTopologyStore } from "@/store/modules/topology";
|
||||||
|
import { useDashboardStore } from "@/store/modules/dashboard";
|
||||||
|
import { EntityType, DepthList } from "../../../data";
|
||||||
|
import router from "@/router";
|
||||||
|
import { ElMessage } from "element-plus";
|
||||||
|
import Settings from "./Settings.vue";
|
||||||
|
import { Option } from "@/types/app";
|
||||||
|
import { Service } from "@/types/selector";
|
||||||
|
|
||||||
|
/*global Nullable */
|
||||||
|
const { t } = useI18n();
|
||||||
|
const selectorStore = useSelectorStore();
|
||||||
|
const topologyStore = useTopologyStore();
|
||||||
|
const dashboardStore = useDashboardStore();
|
||||||
|
const height = ref<number>(document.body.clientHeight - 90);
|
||||||
|
const width = ref<number>(document.body.clientWidth - 40);
|
||||||
|
const loading = ref<boolean>(false);
|
||||||
|
const simulation = ref<any>(null);
|
||||||
|
const svg = ref<Nullable<any>>(null);
|
||||||
|
const chart = ref<Nullable<HTMLDivElement>>(null);
|
||||||
|
const tip = ref<any>(null);
|
||||||
|
const graph = ref<any>(null);
|
||||||
|
const node = ref<any>(null);
|
||||||
|
const link = ref<any>(null);
|
||||||
|
const anchor = ref<any>(null);
|
||||||
|
const arrow = ref<any>(null);
|
||||||
|
const legend = ref<any>(null);
|
||||||
|
const showSetting = ref<boolean>(false);
|
||||||
|
const settings = ref<any>({});
|
||||||
|
const operationsPos = reactive<{ x: number; y: number }>({ x: NaN, y: NaN });
|
||||||
|
const items = ref<
|
||||||
|
{ id: string; title: string; func: any; dashboard?: string }[]
|
||||||
|
>([
|
||||||
|
{ id: "inspect", title: "Inspect", func: handleInspect },
|
||||||
|
{ id: "alarm", title: "Alarm", func: handleGoAlarm },
|
||||||
|
]);
|
||||||
|
const depth = ref<string>(topologyStore.defaultDepth);
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
loading.value = true;
|
||||||
|
const resp = await getTopology();
|
||||||
|
loading.value = false;
|
||||||
|
if (resp && resp.errors) {
|
||||||
|
ElMessage.error(resp.errors);
|
||||||
|
}
|
||||||
|
window.addEventListener("resize", resize);
|
||||||
|
svg.value = d3
|
||||||
|
.select(chart.value)
|
||||||
|
.append("svg")
|
||||||
|
.attr("class", "topo-svg")
|
||||||
|
.attr("height", height.value)
|
||||||
|
.attr("width", width.value);
|
||||||
|
await init();
|
||||||
|
update();
|
||||||
|
});
|
||||||
|
async function init() {
|
||||||
|
tip.value = (d3tip as any)().attr("class", "d3-tip").offset([-8, 0]);
|
||||||
|
graph.value = svg.value.append("g").attr("class", "topo-svg-graph");
|
||||||
|
graph.value.call(tip.value);
|
||||||
|
simulation.value = simulationInit(
|
||||||
|
d3,
|
||||||
|
topologyStore.nodes,
|
||||||
|
topologyStore.calls,
|
||||||
|
ticked
|
||||||
|
);
|
||||||
|
node.value = graph.value.append("g").selectAll(".topo-node");
|
||||||
|
link.value = graph.value.append("g").selectAll(".topo-line");
|
||||||
|
anchor.value = graph.value.append("g").selectAll(".topo-line-anchor");
|
||||||
|
arrow.value = graph.value.append("g").selectAll(".topo-line-arrow");
|
||||||
|
svg.value.call(zoom(d3, graph.value));
|
||||||
|
// legend
|
||||||
|
legend.value = graph.value.append("g").attr("class", "topo-legend");
|
||||||
|
topoLegend(legend.value, height.value, width.value, settings.value.legend);
|
||||||
|
svg.value.on("click", (event: any) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
event.preventDefault();
|
||||||
|
topologyStore.setNode(null);
|
||||||
|
topologyStore.setLink(null);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function ticked() {
|
||||||
|
link.value.attr(
|
||||||
|
"d",
|
||||||
|
(d: Call | any) =>
|
||||||
|
`M${d.source.x} ${d.source.y} Q ${(d.source.x + d.target.x) / 2} ${
|
||||||
|
(d.target.y + d.source.y) / 2 - d.loopFactor * 90
|
||||||
|
} ${d.target.x} ${d.target.y}`
|
||||||
|
);
|
||||||
|
anchor.value.attr(
|
||||||
|
"transform",
|
||||||
|
(d: Call | any) =>
|
||||||
|
`translate(${(d.source.x + d.target.x) / 2}, ${
|
||||||
|
(d.target.y + d.source.y) / 2 - d.loopFactor * 45
|
||||||
|
})`
|
||||||
|
);
|
||||||
|
node.value.attr(
|
||||||
|
"transform",
|
||||||
|
(d: Node | any) => `translate(${d.x - 22},${d.y - 22})`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
function dragstart(d: any) {
|
||||||
|
node.value._groups[0].forEach((g: any) => {
|
||||||
|
g.__data__.fx = g.__data__.x;
|
||||||
|
g.__data__.fy = g.__data__.y;
|
||||||
|
});
|
||||||
|
if (!d.active) {
|
||||||
|
simulation.value.alphaTarget(0.1).restart();
|
||||||
|
}
|
||||||
|
d.subject.fx = d.subject.x;
|
||||||
|
d.subject.fy = d.subject.y;
|
||||||
|
d.sourceEvent.stopPropagation();
|
||||||
|
}
|
||||||
|
function dragged(d: any) {
|
||||||
|
d.subject.fx = d.x;
|
||||||
|
d.subject.fy = d.y;
|
||||||
|
}
|
||||||
|
function dragended(d: any) {
|
||||||
|
if (!d.active) {
|
||||||
|
simulation.value.alphaTarget(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function handleNodeClick(d: Node & { x: number; y: number }) {
|
||||||
|
topologyStore.setNode(d);
|
||||||
|
topologyStore.setLink(null);
|
||||||
|
operationsPos.x = d.x;
|
||||||
|
operationsPos.y = d.y + 30;
|
||||||
|
if (d.layer === String(dashboardStore.layerId)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
items.value = [
|
||||||
|
{ id: "inspect", title: "Inspect", func: handleInspect },
|
||||||
|
{ id: "alarm", title: "Alarm", func: handleGoAlarm },
|
||||||
|
];
|
||||||
|
}
|
||||||
|
function handleLinkClick(event: any, d: Call) {
|
||||||
|
if (
|
||||||
|
d.source.layer !== dashboardStore.layerId ||
|
||||||
|
d.target.layer !== dashboardStore.layerId
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
event.stopPropagation();
|
||||||
|
topologyStore.setNode(null);
|
||||||
|
topologyStore.setLink(d);
|
||||||
|
if (!settings.value.linkDashboard) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const e =
|
||||||
|
dashboardStore.entity === EntityType[1].value
|
||||||
|
? EntityType[0].value
|
||||||
|
: dashboardStore.entity;
|
||||||
|
const path = `/dashboard/${dashboardStore.layerId}/${e}Relation/${d.source.id}/${d.target.id}/${settings.value.linkDashboard}`;
|
||||||
|
const routeUrl = router.resolve({ path });
|
||||||
|
window.open(routeUrl.href, "_blank");
|
||||||
|
}
|
||||||
|
function update() {
|
||||||
|
// node element
|
||||||
|
if (!node.value || !link.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
node.value = node.value.data(topologyStore.nodes, (d: Node) => d.id);
|
||||||
|
node.value.exit().remove();
|
||||||
|
node.value = nodeElement(
|
||||||
|
d3,
|
||||||
|
node.value.enter(),
|
||||||
|
{
|
||||||
|
dragstart: dragstart,
|
||||||
|
dragged: dragged,
|
||||||
|
dragended: dragended,
|
||||||
|
handleNodeClick: handleNodeClick,
|
||||||
|
tipHtml: (data: Node) => {
|
||||||
|
const nodeMetrics: string[] = settings.value.nodeMetrics || [];
|
||||||
|
const html = nodeMetrics.map((m) => {
|
||||||
|
const metric =
|
||||||
|
topologyStore.nodeMetrics[m].values.filter(
|
||||||
|
(val: { id: string; value: unknown }) => val.id === data.id
|
||||||
|
)[0] || {};
|
||||||
|
const val = m.includes("_sla") ? metric.value / 100 : metric.value;
|
||||||
|
return ` <div class="mb-5"><span class="grey">${m}: </span>${val}</div>`;
|
||||||
|
});
|
||||||
|
return [
|
||||||
|
` <div class="mb-5"><span class="grey">name: </span>${data.name}</div>`,
|
||||||
|
...html,
|
||||||
|
].join(" ");
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tip.value,
|
||||||
|
settings.value.legend
|
||||||
|
).merge(node.value);
|
||||||
|
// line element
|
||||||
|
link.value = link.value.data(topologyStore.calls, (d: Call) => d.id);
|
||||||
|
link.value.exit().remove();
|
||||||
|
link.value = linkElement(link.value.enter()).merge(link.value);
|
||||||
|
// anchorElement
|
||||||
|
anchor.value = anchor.value.data(topologyStore.calls, (d: Call) => d.id);
|
||||||
|
anchor.value.exit().remove();
|
||||||
|
anchor.value = anchorElement(
|
||||||
|
anchor.value.enter(),
|
||||||
|
{
|
||||||
|
handleLinkClick: handleLinkClick,
|
||||||
|
tipHtml: (data: Call) => {
|
||||||
|
const linkClientMetrics: string[] =
|
||||||
|
settings.value.linkClientMetrics || [];
|
||||||
|
const linkServerMetrics: string[] =
|
||||||
|
settings.value.linkServerMetrics || [];
|
||||||
|
const htmlServer = linkServerMetrics.map((m) => {
|
||||||
|
const metric = topologyStore.linkServerMetrics[m].values.filter(
|
||||||
|
(val: { id: string; value: unknown }) => val.id === data.id
|
||||||
|
)[0];
|
||||||
|
if (metric) {
|
||||||
|
const val = m.includes("_sla") ? metric.value / 100 : metric.value;
|
||||||
|
return ` <div class="mb-5"><span class="grey">${m}: </span>${val}</div>`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const htmlClient = linkClientMetrics.map((m) => {
|
||||||
|
const metric = topologyStore.linkClientMetrics[m].values.filter(
|
||||||
|
(val: { id: string; value: unknown }) => val.id === data.id
|
||||||
|
)[0];
|
||||||
|
if (metric) {
|
||||||
|
const val = m.includes("_sla") ? metric.value / 100 : metric.value;
|
||||||
|
return ` <div class="mb-5"><span class="grey">${m}: </span>${val}</div>`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const html = [
|
||||||
|
...htmlServer,
|
||||||
|
...htmlClient,
|
||||||
|
`<div><span class="grey">${t(
|
||||||
|
"detectPoint"
|
||||||
|
)}:</span>${data.detectPoints.join(" | ")}</div>`,
|
||||||
|
].join(" ");
|
||||||
|
|
||||||
|
return html;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tip.value
|
||||||
|
).merge(anchor.value);
|
||||||
|
// arrow marker
|
||||||
|
arrow.value = arrow.value.data(topologyStore.calls, (d: Call) => d.id);
|
||||||
|
arrow.value.exit().remove();
|
||||||
|
arrow.value = arrowMarker(arrow.value.enter()).merge(arrow.value);
|
||||||
|
// force element
|
||||||
|
simulation.value.nodes(topologyStore.nodes);
|
||||||
|
simulation.value
|
||||||
|
.force("link")
|
||||||
|
.links(topologyStore.calls)
|
||||||
|
.id((d: Call) => d.id);
|
||||||
|
simulationSkip(d3, simulation.value, ticked);
|
||||||
|
const loopMap: any = {};
|
||||||
|
for (let i = 0; i < topologyStore.calls.length; i++) {
|
||||||
|
const link: any = topologyStore.calls[i];
|
||||||
|
link.loopFactor = 1;
|
||||||
|
for (let j = 0; j < topologyStore.calls.length; j++) {
|
||||||
|
if (i === j || loopMap[i]) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const otherLink = topologyStore.calls[j];
|
||||||
|
if (
|
||||||
|
link.source.id === otherLink.target.id &&
|
||||||
|
link.target.id === otherLink.source.id
|
||||||
|
) {
|
||||||
|
link.loopFactor = -1;
|
||||||
|
loopMap[j] = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async function handleInspect() {
|
||||||
|
svg.value.selectAll(".topo-svg-graph").remove();
|
||||||
|
const id = topologyStore.node.id;
|
||||||
|
topologyStore.setNode(null);
|
||||||
|
topologyStore.setLink(null);
|
||||||
|
loading.value = true;
|
||||||
|
const resp = await topologyStore.getServicesTopology([id]);
|
||||||
|
loading.value = false;
|
||||||
|
|
||||||
|
if (resp.errors) {
|
||||||
|
ElMessage.error(resp.errors);
|
||||||
|
}
|
||||||
|
await init();
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
function handleGoEndpoint(name: string) {
|
||||||
|
const path = `/dashboard/${dashboardStore.layerId}/Endpoint/${topologyStore.node.id}/${name}`;
|
||||||
|
const routeUrl = router.resolve({ path });
|
||||||
|
|
||||||
|
window.open(routeUrl.href, "_blank");
|
||||||
|
}
|
||||||
|
function handleGoInstance(name: string) {
|
||||||
|
const path = `/dashboard/${dashboardStore.layerId}/ServiceInstance/${topologyStore.node.id}/${name}`;
|
||||||
|
const routeUrl = router.resolve({ path });
|
||||||
|
|
||||||
|
window.open(routeUrl.href, "_blank");
|
||||||
|
}
|
||||||
|
function handleGoDashboard(name: string) {
|
||||||
|
const path = `/dashboard/${dashboardStore.layerId}/Service/${topologyStore.node.id}/${name}`;
|
||||||
|
const routeUrl = router.resolve({ path });
|
||||||
|
|
||||||
|
window.open(routeUrl.href, "_blank");
|
||||||
|
}
|
||||||
|
function handleGoAlarm() {
|
||||||
|
const path = `/alarm`;
|
||||||
|
const routeUrl = router.resolve({ path });
|
||||||
|
|
||||||
|
window.open(routeUrl.href, "_blank");
|
||||||
|
}
|
||||||
|
async function backToTopology() {
|
||||||
|
svg.value.selectAll(".topo-svg-graph").remove();
|
||||||
|
loading.value = true;
|
||||||
|
const resp = await getTopology();
|
||||||
|
loading.value = false;
|
||||||
|
|
||||||
|
if (resp.errors) {
|
||||||
|
ElMessage.error(resp.errors);
|
||||||
|
}
|
||||||
|
await init();
|
||||||
|
update();
|
||||||
|
topologyStore.setNode(null);
|
||||||
|
topologyStore.setLink(null);
|
||||||
|
}
|
||||||
|
async function getTopology() {
|
||||||
|
const ids = selectorStore.services.map((d: Service) => d.id);
|
||||||
|
const serviceIds =
|
||||||
|
dashboardStore.entity === EntityType[0].value
|
||||||
|
? [selectorStore.currentService.id]
|
||||||
|
: ids;
|
||||||
|
const resp = await topologyStore.getDepthServiceTopology(
|
||||||
|
serviceIds,
|
||||||
|
Number(depth.value)
|
||||||
|
);
|
||||||
|
return resp;
|
||||||
|
}
|
||||||
|
function setConfig() {
|
||||||
|
showSetting.value = !showSetting.value;
|
||||||
|
}
|
||||||
|
function resize() {
|
||||||
|
height.value = document.body.clientHeight - 90;
|
||||||
|
width.value = document.body.clientWidth - 40;
|
||||||
|
svg.value.attr("height", height.value).attr("width", width.value);
|
||||||
|
}
|
||||||
|
function updateSettings(config: any) {
|
||||||
|
items.value = [
|
||||||
|
{ id: "inspect", title: "Inspect", func: handleInspect },
|
||||||
|
{ id: "alarm", title: "Alarm", func: handleGoAlarm },
|
||||||
|
];
|
||||||
|
settings.value = config;
|
||||||
|
for (const item of config.nodeDashboard) {
|
||||||
|
if (item.scope === EntityType[0].value) {
|
||||||
|
items.value.push({
|
||||||
|
id: "dashboard",
|
||||||
|
title: "Dashboard",
|
||||||
|
func: handleGoDashboard,
|
||||||
|
...item,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (item.scope === EntityType[2].value) {
|
||||||
|
items.value.push({
|
||||||
|
id: "endpoint",
|
||||||
|
title: "Endpoint",
|
||||||
|
func: handleGoEndpoint,
|
||||||
|
...item,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (item.scope === EntityType[3].value) {
|
||||||
|
items.value.push({
|
||||||
|
id: "instance",
|
||||||
|
title: "Service Instance",
|
||||||
|
func: handleGoInstance,
|
||||||
|
...item,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async function freshNodes() {
|
||||||
|
svg.value.selectAll(".topo-svg-graph").remove();
|
||||||
|
await init();
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function changeDepth(opt: Option[]) {
|
||||||
|
depth.value = opt[0].value;
|
||||||
|
await getTopology();
|
||||||
|
freshNodes();
|
||||||
|
}
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
window.removeEventListener("resize", resize);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<style lang="scss">
|
||||||
|
.micro-topo-chart {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.setting {
|
||||||
|
position: absolute;
|
||||||
|
top: 70px;
|
||||||
|
right: 0;
|
||||||
|
width: 400px;
|
||||||
|
height: 700px;
|
||||||
|
background-color: #2b3037;
|
||||||
|
overflow: auto;
|
||||||
|
padding: 0 15px;
|
||||||
|
border-radius: 3px;
|
||||||
|
color: #ccc;
|
||||||
|
transition: all 0.5ms linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
color: #ccc;
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.operations-list {
|
||||||
|
position: absolute;
|
||||||
|
padding: 10px;
|
||||||
|
color: #333;
|
||||||
|
cursor: pointer;
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 3px;
|
||||||
|
|
||||||
|
span {
|
||||||
|
display: block;
|
||||||
|
height: 30px;
|
||||||
|
width: 140px;
|
||||||
|
line-height: 30px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
span:hover {
|
||||||
|
color: #409eff;
|
||||||
|
background-color: #eee;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool {
|
||||||
|
position: absolute;
|
||||||
|
top: 22px;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.switch-icon {
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.5ms linear;
|
||||||
|
background-color: #252a2f99;
|
||||||
|
color: #ddd;
|
||||||
|
display: inline-block;
|
||||||
|
padding: 5px 8px 8px;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topo-svg {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topo-line {
|
||||||
|
stroke-linecap: round;
|
||||||
|
stroke-width: 3px;
|
||||||
|
stroke-dasharray: 13 7;
|
||||||
|
fill: none;
|
||||||
|
animation: topo-dash 0.5s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topo-line-anchor {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topo-text {
|
||||||
|
font-family: "Lato", "Source Han Sans CN", "Microsoft YaHei", sans-serif;
|
||||||
|
fill: #ddd;
|
||||||
|
font-size: 11px;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.d3-tip {
|
||||||
|
line-height: 1;
|
||||||
|
padding: 8px;
|
||||||
|
color: #eee;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
z-index: 9999;
|
||||||
|
background: #252a2f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.d3-tip:after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: block;
|
||||||
|
font-size: 10px;
|
||||||
|
width: 100%;
|
||||||
|
line-height: 0.8;
|
||||||
|
color: #252a2f;
|
||||||
|
content: "\25BC";
|
||||||
|
position: absolute;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.d3-tip.n:after {
|
||||||
|
margin: -2px 0 0 0;
|
||||||
|
top: 100%;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
@keyframes topo-dash {
|
||||||
|
from {
|
||||||
|
stroke-dashoffset: 20;
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
stroke-dashoffset: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
274
src/views/dashboard/related/topology/components/PodTopology.vue
Normal file
@ -0,0 +1,274 @@
|
|||||||
|
<!-- Licensed to the 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.
|
||||||
|
The 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. -->
|
||||||
|
<template>
|
||||||
|
<div class="tool">
|
||||||
|
<span v-show="dashboardStore.entity === EntityType[2].value">
|
||||||
|
<span class="label">{{ t("currentDepth") }}</span>
|
||||||
|
<Selector
|
||||||
|
class="inputs"
|
||||||
|
:value="depth"
|
||||||
|
:options="DepthList"
|
||||||
|
placeholder="Select a option"
|
||||||
|
@change="changeDepth"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<span class="switch-icon ml-5" title="Settings" @click="setConfig">
|
||||||
|
<Icon size="middle" iconName="settings" />
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="switch-icon ml-5"
|
||||||
|
title="Back to overview topology"
|
||||||
|
@click="backToTopology"
|
||||||
|
>
|
||||||
|
<Icon size="middle" iconName="keyboard_backspace" />
|
||||||
|
</span>
|
||||||
|
<div class="settings" v-show="showSettings">
|
||||||
|
<Settings @update="updateConfig" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="sankey"
|
||||||
|
:style="`height:${height}px;width:${width}px;`"
|
||||||
|
v-loading="loading"
|
||||||
|
@click="handleClick"
|
||||||
|
>
|
||||||
|
<Sankey @click="selectNodeLink" />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="operations-list"
|
||||||
|
v-if="topologyStore.node"
|
||||||
|
:style="{
|
||||||
|
top: operationsPos.y + 'px',
|
||||||
|
left: operationsPos.x + 'px',
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<i v-for="(item, index) of items" :key="index" @click="item.func">
|
||||||
|
<span
|
||||||
|
v-if="
|
||||||
|
['alarm', 'inspect'].includes(item.id) ||
|
||||||
|
(item.id === 'dashboard' && settings.nodeDashboard)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{{ item.title }}
|
||||||
|
</span>
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
import { ref, onMounted, reactive } from "vue";
|
||||||
|
import { Option } from "@/types/app";
|
||||||
|
import { useTopologyStore } from "@/store/modules/topology";
|
||||||
|
import { useDashboardStore } from "@/store/modules/dashboard";
|
||||||
|
import { useSelectorStore } from "@/store/modules/selectors";
|
||||||
|
import { EntityType, DepthList } from "../../../data";
|
||||||
|
import { ElMessage } from "element-plus";
|
||||||
|
import Sankey from "./Sankey.vue";
|
||||||
|
import Settings from "./Settings.vue";
|
||||||
|
import router from "@/router";
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
const dashboardStore = useDashboardStore();
|
||||||
|
const selectorStore = useSelectorStore();
|
||||||
|
const topologyStore = useTopologyStore();
|
||||||
|
const loading = ref<boolean>(false);
|
||||||
|
const height = ref<number>(document.body.clientHeight - 150);
|
||||||
|
const width = ref<number>(document.body.clientWidth - 40);
|
||||||
|
const showSettings = ref<boolean>(false);
|
||||||
|
const settings = ref<any>({});
|
||||||
|
const operationsPos = reactive<{ x: number; y: number }>({ x: NaN, y: NaN });
|
||||||
|
const depth = ref<string>(topologyStore.defaultDepth);
|
||||||
|
const items = [
|
||||||
|
{ id: "inspect", title: "Inspect", func: inspect },
|
||||||
|
{ id: "dashboard", title: "View Dashboard", func: goDashboard },
|
||||||
|
{ id: "alarm", title: "View Alarm", func: goAlarm },
|
||||||
|
];
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
loadTopology(selectorStore.currentPod && selectorStore.currentPod.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
async function loadTopology(id: string) {
|
||||||
|
loading.value = true;
|
||||||
|
const resp = await getTopology(id);
|
||||||
|
loading.value = false;
|
||||||
|
if (resp && resp.errors) {
|
||||||
|
ElMessage.error(resp.errors);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function inspect() {
|
||||||
|
const id = topologyStore.node.id;
|
||||||
|
topologyStore.setNode(null);
|
||||||
|
topologyStore.setLink(null);
|
||||||
|
loadTopology(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
function goAlarm() {
|
||||||
|
const path = `/alarm`;
|
||||||
|
const routeUrl = router.resolve({ path });
|
||||||
|
|
||||||
|
window.open(routeUrl.href, "_blank");
|
||||||
|
topologyStore.setNode(null);
|
||||||
|
}
|
||||||
|
function goDashboard() {
|
||||||
|
const entity =
|
||||||
|
dashboardStore.entity === EntityType[2].value
|
||||||
|
? EntityType[2].value
|
||||||
|
: EntityType[3].value;
|
||||||
|
const path = `/dashboard/${dashboardStore.layerId}/${entity}/${topologyStore.node.serviceId}/${topologyStore.node.id}/${settings.value.nodeDashboard}`;
|
||||||
|
const routeUrl = router.resolve({ path });
|
||||||
|
window.open(routeUrl.href, "_blank");
|
||||||
|
topologyStore.setNode(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setConfig() {
|
||||||
|
topologyStore.setNode(null);
|
||||||
|
showSettings.value = !showSettings.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateConfig(config: any) {
|
||||||
|
settings.value = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
function backToTopology() {
|
||||||
|
loadTopology(selectorStore.currentPod.id);
|
||||||
|
topologyStore.setNode(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectNodeLink(d: any) {
|
||||||
|
if (d.dataType === "edge") {
|
||||||
|
topologyStore.setNode(null);
|
||||||
|
topologyStore.setLink(d.data);
|
||||||
|
if (!settings.value.linkDashboard) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const { sourceObj, targetObj } = d.data;
|
||||||
|
const entity =
|
||||||
|
dashboardStore.entity === EntityType[2].value
|
||||||
|
? EntityType[6].value
|
||||||
|
: EntityType[5].value;
|
||||||
|
const path = `/dashboard/${dashboardStore.layerId}/${entity}/${sourceObj.serviceId}/${sourceObj.id}/${targetObj.serviceId}/${targetObj.id}/${settings.value.linkDashboard}`;
|
||||||
|
const routeUrl = router.resolve({ path });
|
||||||
|
window.open(routeUrl.href, "_blank");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
topologyStore.setNode(d.data);
|
||||||
|
topologyStore.setLink(null);
|
||||||
|
operationsPos.x = d.event.event.clientX;
|
||||||
|
operationsPos.y = d.event.event.clientY;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function changeDepth(opt: Option[]) {
|
||||||
|
depth.value = opt[0].value;
|
||||||
|
loadTopology(selectorStore.currentPod.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getTopology(id: string) {
|
||||||
|
let resp;
|
||||||
|
switch (dashboardStore.entity) {
|
||||||
|
case EntityType[2].value:
|
||||||
|
resp = await topologyStore.updateEndpointTopology(
|
||||||
|
[id],
|
||||||
|
Number(depth.value)
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case EntityType[4].value:
|
||||||
|
resp = await topologyStore.getInstanceTopology();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return resp;
|
||||||
|
}
|
||||||
|
function handleClick(event: any) {
|
||||||
|
if (event.target.nodeName === "svg") {
|
||||||
|
topologyStore.setNode(null);
|
||||||
|
topologyStore.setLink(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.sankey {
|
||||||
|
margin-top: 10px;
|
||||||
|
background-color: #333840;
|
||||||
|
color: #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings {
|
||||||
|
position: absolute;
|
||||||
|
top: 40px;
|
||||||
|
right: 0;
|
||||||
|
width: 400px;
|
||||||
|
height: 700px;
|
||||||
|
background-color: #2b3037;
|
||||||
|
overflow: auto;
|
||||||
|
padding: 0 15px;
|
||||||
|
border-radius: 3px;
|
||||||
|
color: #ccc;
|
||||||
|
transition: all 0.5ms linear;
|
||||||
|
z-index: 99;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool {
|
||||||
|
text-align: right;
|
||||||
|
margin-top: 10px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.switch-icon {
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
line-height: 30px;
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.5ms linear;
|
||||||
|
background-color: #252a2f99;
|
||||||
|
color: #ddd;
|
||||||
|
display: inline-block;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
color: #ccc;
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.operations-list {
|
||||||
|
position: absolute;
|
||||||
|
padding: 10px;
|
||||||
|
color: #333;
|
||||||
|
cursor: pointer;
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 3px;
|
||||||
|
|
||||||
|
span {
|
||||||
|
display: block;
|
||||||
|
height: 30px;
|
||||||
|
width: 140px;
|
||||||
|
line-height: 30px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
span:hover {
|
||||||
|
color: #409eff;
|
||||||
|
background-color: #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
i {
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
131
src/views/dashboard/related/topology/components/Sankey.vue
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
<!-- Licensed to the 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.
|
||||||
|
The 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. -->
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Graph :option="option" @select="clickChart" />
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { computed } from "vue";
|
||||||
|
import { useTopologyStore } from "@/store/modules/topology";
|
||||||
|
import { Node, Call } from "@/types/topology";
|
||||||
|
|
||||||
|
/*global defineEmits */
|
||||||
|
const emit = defineEmits(["click"]);
|
||||||
|
const topologyStore = useTopologyStore();
|
||||||
|
const option = computed(() => getOption());
|
||||||
|
|
||||||
|
function getOption() {
|
||||||
|
return {
|
||||||
|
tooltip: {
|
||||||
|
trigger: "item",
|
||||||
|
},
|
||||||
|
series: {
|
||||||
|
type: "sankey",
|
||||||
|
left: 40,
|
||||||
|
top: 20,
|
||||||
|
right: 300,
|
||||||
|
bottom: 40,
|
||||||
|
emphasis: { focus: "adjacency" },
|
||||||
|
data: topologyStore.nodes,
|
||||||
|
links: topologyStore.calls,
|
||||||
|
label: {
|
||||||
|
color: "#fff",
|
||||||
|
formatter: (param: any) => param.data.name,
|
||||||
|
},
|
||||||
|
color: [
|
||||||
|
"#3fe1da",
|
||||||
|
"#6be6c1",
|
||||||
|
"#3fcfdc",
|
||||||
|
"#626c91",
|
||||||
|
"#3fbcde",
|
||||||
|
"#a0a7e6",
|
||||||
|
"#3fa9e1",
|
||||||
|
"#96dee8",
|
||||||
|
"#bf99f8",
|
||||||
|
],
|
||||||
|
itemStyle: {
|
||||||
|
borderWidth: 0,
|
||||||
|
},
|
||||||
|
lineStyle: {
|
||||||
|
color: "source",
|
||||||
|
opacity: 0.12,
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
position: "bottom",
|
||||||
|
formatter: (param: { data: any; dataType: string }) => {
|
||||||
|
if (param.dataType === "edge") {
|
||||||
|
return linkTooltip(param.data);
|
||||||
|
}
|
||||||
|
return nodeTooltip(param.data);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
function linkTooltip(data: Call) {
|
||||||
|
const clientMetrics: string[] = Object.keys(topologyStore.linkClientMetrics);
|
||||||
|
const serverMetrics: string[] = Object.keys(topologyStore.linkServerMetrics);
|
||||||
|
const htmlServer = serverMetrics.map((m) => {
|
||||||
|
const metric = topologyStore.linkServerMetrics[m].values.filter(
|
||||||
|
(val: { id: string; value: unknown }) => val.id === data.id
|
||||||
|
)[0];
|
||||||
|
if (metric) {
|
||||||
|
const val = m.includes("_sla") ? metric.value / 100 : metric.value;
|
||||||
|
return ` <div><span>${m}: </span>${val}</div>`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const htmlClient = clientMetrics.map((m) => {
|
||||||
|
const metric = topologyStore.linkClientMetrics[m].values.filter(
|
||||||
|
(val: { id: string; value: unknown }) => val.id === data.id
|
||||||
|
)[0];
|
||||||
|
if (metric) {
|
||||||
|
const val = m.includes("_sla") ? metric.value / 100 : metric.value;
|
||||||
|
return ` <div><span>${m}: </span>${val}</div>`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const html = [
|
||||||
|
`<div>${data.sourceObj.serviceName} -> ${data.targetObj.serviceName}</div>`,
|
||||||
|
...htmlServer,
|
||||||
|
...htmlClient,
|
||||||
|
].join(" ");
|
||||||
|
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
|
||||||
|
function nodeTooltip(data: Node) {
|
||||||
|
const nodeMetrics: string[] = Object.keys(topologyStore.nodeMetrics);
|
||||||
|
const html = nodeMetrics.map((m) => {
|
||||||
|
const metric =
|
||||||
|
topologyStore.nodeMetrics[m].values.filter(
|
||||||
|
(val: { id: string; value: unknown }) => val.id === data.id
|
||||||
|
)[0] || {};
|
||||||
|
const val = m.includes("_sla") ? metric.value / 100 : metric.value;
|
||||||
|
return ` <div><span>${m}: </span>${val}</div>`;
|
||||||
|
});
|
||||||
|
return [` <div><span>name: </span>${data.serviceName}</div>`, ...html].join(
|
||||||
|
" "
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function clickChart(param: any) {
|
||||||
|
emit("click", param);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.sankey {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
402
src/views/dashboard/related/topology/components/Settings.vue
Normal file
@ -0,0 +1,402 @@
|
|||||||
|
<!-- Licensed to the 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.
|
||||||
|
The 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. -->
|
||||||
|
<template>
|
||||||
|
<div class="link-settings">
|
||||||
|
<h5 class="title">{{ t("callSettings") }}</h5>
|
||||||
|
<div class="label">{{ t("linkDashboard") }}</div>
|
||||||
|
<el-input
|
||||||
|
v-model="states.linkDashboard"
|
||||||
|
placeholder="Please input a dashboard name for calls"
|
||||||
|
@change="updateSettings"
|
||||||
|
size="small"
|
||||||
|
class="inputs"
|
||||||
|
/>
|
||||||
|
<div class="label">{{ t("linkServerMetrics") }}</div>
|
||||||
|
<Selector
|
||||||
|
class="inputs"
|
||||||
|
:multiple="true"
|
||||||
|
:value="states.linkServerMetrics"
|
||||||
|
:options="states.linkMetricList"
|
||||||
|
size="small"
|
||||||
|
placeholder="Select metrics"
|
||||||
|
@change="changeLinkServerMetrics"
|
||||||
|
/>
|
||||||
|
<span v-show="dashboardStore.entity !== EntityType[2].value">
|
||||||
|
<div class="label">
|
||||||
|
{{ t("linkClientMetrics") }}
|
||||||
|
</div>
|
||||||
|
<Selector
|
||||||
|
class="inputs"
|
||||||
|
:multiple="true"
|
||||||
|
:value="states.linkClientMetrics"
|
||||||
|
:options="states.linkMetricList"
|
||||||
|
size="small"
|
||||||
|
placeholder="Select metrics"
|
||||||
|
@change="changeLinkClientMetrics"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="node-settings">
|
||||||
|
<h5 class="title">{{ t("nodeSettings") }}</h5>
|
||||||
|
<div class="label">{{ t("nodeDashboard") }}</div>
|
||||||
|
<el-input
|
||||||
|
v-show="!isServer"
|
||||||
|
v-model="states.nodeDashboard"
|
||||||
|
placeholder="Please input a dashboard name for nodes"
|
||||||
|
@change="updateSettings"
|
||||||
|
size="small"
|
||||||
|
class="inputs"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
v-show="isServer"
|
||||||
|
v-for="(item, index) in items"
|
||||||
|
:key="index"
|
||||||
|
class="metric-item"
|
||||||
|
>
|
||||||
|
<Selector
|
||||||
|
:value="item.scope"
|
||||||
|
:options="ScopeType"
|
||||||
|
size="small"
|
||||||
|
placeholder="Select a scope"
|
||||||
|
@change="changeScope(index, $event)"
|
||||||
|
class="item mr-5"
|
||||||
|
/>
|
||||||
|
<el-input
|
||||||
|
v-model="item.dashboard"
|
||||||
|
placeholder="Please input a dashboard name for nodes"
|
||||||
|
@change="updateNodeDashboards(index, $event)"
|
||||||
|
size="small"
|
||||||
|
class="item mr-5"
|
||||||
|
/>
|
||||||
|
<span>
|
||||||
|
<Icon
|
||||||
|
class="cp mr-5"
|
||||||
|
v-show="items.length > 1"
|
||||||
|
iconName="remove_circle_outline"
|
||||||
|
size="middle"
|
||||||
|
@click="deleteItem(index)"
|
||||||
|
/>
|
||||||
|
<Icon
|
||||||
|
class="cp"
|
||||||
|
v-show="index === items.length - 1 && items.length < 5"
|
||||||
|
iconName="add_circle_outlinecontrol_point"
|
||||||
|
size="middle"
|
||||||
|
@click="addItem"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="label">{{ t("nodeMetrics") }}</div>
|
||||||
|
<Selector
|
||||||
|
class="inputs"
|
||||||
|
:multiple="true"
|
||||||
|
:value="states.nodeMetrics"
|
||||||
|
:options="states.nodeMetricList"
|
||||||
|
size="small"
|
||||||
|
placeholder="Select metrics"
|
||||||
|
@change="changeNodeMetrics"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="legend-settings" v-show="isServer">
|
||||||
|
<h5 class="title">{{ t("legendSettings") }}</h5>
|
||||||
|
<div class="label">{{ t("conditions") }}</div>
|
||||||
|
<div v-for="(metric, index) of legend.metric" :key="metric.name + index">
|
||||||
|
<Selector
|
||||||
|
class="item"
|
||||||
|
:value="metric.name"
|
||||||
|
:options="states.nodeMetricList"
|
||||||
|
size="small"
|
||||||
|
placeholder="Select a metric"
|
||||||
|
@change="changeLegend(LegendOpt.NAME, $event, index)"
|
||||||
|
/>
|
||||||
|
<Selector
|
||||||
|
class="input-small"
|
||||||
|
:value="metric.condition"
|
||||||
|
:options="MetricConditions"
|
||||||
|
size="small"
|
||||||
|
placeholder="Select a condition"
|
||||||
|
@change="changeLegend(LegendOpt.CONDITION, $event, index)"
|
||||||
|
/>
|
||||||
|
<el-input
|
||||||
|
v-model="metric.value"
|
||||||
|
placeholder="Please input a value"
|
||||||
|
@change="changeLegend(LegendOpt.VALUE, $event, index)"
|
||||||
|
size="small"
|
||||||
|
class="item"
|
||||||
|
/>
|
||||||
|
<span>
|
||||||
|
<Icon
|
||||||
|
class="cp delete"
|
||||||
|
iconName="remove_circle_outline"
|
||||||
|
size="middle"
|
||||||
|
@click="deleteMetric(index)"
|
||||||
|
v-show="legend.metric.length > 1"
|
||||||
|
/>
|
||||||
|
<Icon
|
||||||
|
class="cp"
|
||||||
|
iconName="add_circle_outlinecontrol_point"
|
||||||
|
size="middle"
|
||||||
|
v-show="
|
||||||
|
index === legend.metric.length - 1 && legend.metric.length < 5
|
||||||
|
"
|
||||||
|
@click="addMetric"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<div v-show="index !== legend.metric.length - 1">&&</div>
|
||||||
|
</div>
|
||||||
|
<!-- <div class="label">{{ t("conditions") }}</div>
|
||||||
|
<Selector
|
||||||
|
class="inputs"
|
||||||
|
:value="legend.condition"
|
||||||
|
:options="LegendConditions"
|
||||||
|
size="small"
|
||||||
|
placeholder="Select a condition"
|
||||||
|
@change="changeCondition"
|
||||||
|
/> -->
|
||||||
|
<el-button
|
||||||
|
@click="setLegend"
|
||||||
|
class="legend-btn"
|
||||||
|
size="small"
|
||||||
|
type="primary"
|
||||||
|
>
|
||||||
|
{{ t("setLegend") }}
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { reactive } from "vue";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
import { useDashboardStore } from "@/store/modules/dashboard";
|
||||||
|
import { useTopologyStore } from "@/store/modules/topology";
|
||||||
|
import { ElMessage } from "element-plus";
|
||||||
|
import { MetricCatalog, ScopeType, MetricConditions } from "../../../data";
|
||||||
|
import { Option } from "@/types/app";
|
||||||
|
import { useQueryTopologyMetrics } from "@/hooks/useProcessor";
|
||||||
|
import { Node, Call } from "@/types/topology";
|
||||||
|
import { EntityType, LegendOpt } from "../../../data";
|
||||||
|
|
||||||
|
/*global defineEmits */
|
||||||
|
const emit = defineEmits(["update", "updateNodes"]);
|
||||||
|
const { t } = useI18n();
|
||||||
|
const dashboardStore = useDashboardStore();
|
||||||
|
const topologyStore = useTopologyStore();
|
||||||
|
const items = reactive<
|
||||||
|
{
|
||||||
|
scope: string;
|
||||||
|
dashboard: string;
|
||||||
|
}[]
|
||||||
|
>([{ scope: "", dashboard: "" }]);
|
||||||
|
const states = reactive<{
|
||||||
|
linkDashboard: string;
|
||||||
|
nodeDashboard: {
|
||||||
|
scope: string;
|
||||||
|
dashboard: string;
|
||||||
|
}[];
|
||||||
|
linkServerMetrics: string[];
|
||||||
|
linkClientMetrics: string[];
|
||||||
|
nodeMetrics: string[];
|
||||||
|
nodeMetricList: Option[];
|
||||||
|
linkMetricList: Option[];
|
||||||
|
}>({
|
||||||
|
linkDashboard: "",
|
||||||
|
nodeDashboard: [],
|
||||||
|
linkServerMetrics: [],
|
||||||
|
linkClientMetrics: [],
|
||||||
|
nodeMetrics: [],
|
||||||
|
nodeMetricList: [],
|
||||||
|
linkMetricList: [],
|
||||||
|
});
|
||||||
|
const isServer = [EntityType[0].value, EntityType[1].value].includes(
|
||||||
|
dashboardStore.entity
|
||||||
|
);
|
||||||
|
const legend = reactive<{
|
||||||
|
metric: { name: string; condition: string; value: string }[];
|
||||||
|
}>({ metric: [{ name: "", condition: "", value: "" }] });
|
||||||
|
|
||||||
|
getMetricList();
|
||||||
|
async function getMetricList() {
|
||||||
|
const json = await dashboardStore.fetchMetricList();
|
||||||
|
if (json.errors) {
|
||||||
|
ElMessage.error(json.errors);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const entity =
|
||||||
|
dashboardStore.entity === EntityType[1].value
|
||||||
|
? EntityType[0].value
|
||||||
|
: dashboardStore.entity === EntityType[4].value
|
||||||
|
? EntityType[3].value
|
||||||
|
: dashboardStore.entity;
|
||||||
|
states.nodeMetricList = (json.data.metrics || []).filter(
|
||||||
|
(d: { catalog: string }) => entity === (MetricCatalog as any)[d.catalog]
|
||||||
|
);
|
||||||
|
const e =
|
||||||
|
dashboardStore.entity === EntityType[1].value
|
||||||
|
? EntityType[0].value
|
||||||
|
: dashboardStore.entity === EntityType[4].value
|
||||||
|
? EntityType[3].value
|
||||||
|
: dashboardStore.entity;
|
||||||
|
states.linkMetricList = (json.data.metrics || []).filter(
|
||||||
|
(d: { catalog: string }) =>
|
||||||
|
e + "Relation" === (MetricCatalog as any)[d.catalog]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
async function setLegend() {
|
||||||
|
const metrics = legend.metric.filter(
|
||||||
|
(d: any) => d.name && d.value && d.condition
|
||||||
|
);
|
||||||
|
const names = metrics.map((d: any) => d.name);
|
||||||
|
|
||||||
|
emit("update", {
|
||||||
|
linkDashboard: states.linkDashboard,
|
||||||
|
nodeDashboard: isServer
|
||||||
|
? items.filter((d: { scope: string; dashboard: string }) => d.dashboard)
|
||||||
|
: states.nodeDashboard,
|
||||||
|
linkServerMetrics: states.linkServerMetrics,
|
||||||
|
linkClientMetrics: states.linkClientMetrics,
|
||||||
|
nodeMetrics: states.nodeMetrics,
|
||||||
|
legend: metrics,
|
||||||
|
});
|
||||||
|
const ids = topologyStore.nodes.map((d: Node) => d.id);
|
||||||
|
const param = await useQueryTopologyMetrics(names, ids);
|
||||||
|
const res = await topologyStore.getLegendMetrics(param);
|
||||||
|
|
||||||
|
if (res.errors) {
|
||||||
|
ElMessage.error(res.errors);
|
||||||
|
}
|
||||||
|
emit("updateNodes");
|
||||||
|
}
|
||||||
|
function changeLegend(type: string, opt: any, index: number) {
|
||||||
|
(legend.metric[index] as any)[type] = opt[0].value || opt;
|
||||||
|
}
|
||||||
|
function changeScope(index: number, opt: Option[]) {
|
||||||
|
items[index].scope = opt[0].value;
|
||||||
|
items[index].dashboard = "";
|
||||||
|
}
|
||||||
|
function updateNodeDashboards(index: number, content: string) {
|
||||||
|
items[index].dashboard = content;
|
||||||
|
updateSettings();
|
||||||
|
}
|
||||||
|
function addItem() {
|
||||||
|
items.push({ scope: "", dashboard: "" });
|
||||||
|
}
|
||||||
|
function deleteItem(index: number) {
|
||||||
|
items.splice(index, 1);
|
||||||
|
updateSettings();
|
||||||
|
}
|
||||||
|
function updateSettings() {
|
||||||
|
emit("update", {
|
||||||
|
linkDashboard: states.linkDashboard,
|
||||||
|
nodeDashboard: isServer
|
||||||
|
? items.filter((d: { scope: string; dashboard: string }) => d.dashboard)
|
||||||
|
: states.nodeDashboard,
|
||||||
|
linkServerMetrics: states.linkServerMetrics,
|
||||||
|
linkClientMetrics: states.linkClientMetrics,
|
||||||
|
nodeMetrics: states.nodeMetrics,
|
||||||
|
legend: legend.metric,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async function changeLinkServerMetrics(options: Option[]) {
|
||||||
|
states.linkServerMetrics = options.map((d: Option) => d.value);
|
||||||
|
updateSettings();
|
||||||
|
if (!states.linkServerMetrics.length) {
|
||||||
|
topologyStore.setLinkServerMetrics({});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const idsS = topologyStore.calls
|
||||||
|
.filter((i: Call) => i.detectPoints.includes("SERVER"))
|
||||||
|
.map((b: Call) => b.id);
|
||||||
|
const param = await useQueryTopologyMetrics(states.linkServerMetrics, idsS);
|
||||||
|
const res = await topologyStore.getCallServerMetrics(param);
|
||||||
|
|
||||||
|
if (res.errors) {
|
||||||
|
ElMessage.error(res.errors);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async function changeLinkClientMetrics(options: Option[]) {
|
||||||
|
states.linkClientMetrics = options.map((d: Option) => d.value);
|
||||||
|
updateSettings();
|
||||||
|
if (!states.linkClientMetrics.length) {
|
||||||
|
topologyStore.setLinkClientMetrics({});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const idsC = topologyStore.calls
|
||||||
|
.filter((i: Call) => i.detectPoints.includes("CLIENT"))
|
||||||
|
.map((b: Call) => b.id);
|
||||||
|
const param = await useQueryTopologyMetrics(states.linkClientMetrics, idsC);
|
||||||
|
const res = await topologyStore.getCallClientMetrics(param);
|
||||||
|
|
||||||
|
if (res.errors) {
|
||||||
|
ElMessage.error(res.errors);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async function changeNodeMetrics(options: Option[]) {
|
||||||
|
states.nodeMetrics = options.map((d: Option) => d.value);
|
||||||
|
updateSettings();
|
||||||
|
if (!states.nodeMetrics.length) {
|
||||||
|
topologyStore.setNodeMetrics({});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const ids = topologyStore.nodes.map((d: Node) => d.id);
|
||||||
|
const param = await useQueryTopologyMetrics(states.nodeMetrics, ids);
|
||||||
|
const res = await topologyStore.getNodeMetrics(param);
|
||||||
|
|
||||||
|
if (res.errors) {
|
||||||
|
ElMessage.error(res.errors);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function deleteMetric(index: number) {
|
||||||
|
legend.metric.splice(index, 1);
|
||||||
|
}
|
||||||
|
function addMetric() {
|
||||||
|
legend.metric.push({ name: "", condition: "", value: "" });
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.link-settings {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inputs {
|
||||||
|
margin-top: 8px;
|
||||||
|
width: 370px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item {
|
||||||
|
width: 130px;
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-small {
|
||||||
|
width: 45px;
|
||||||
|
margin: 0 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
font-size: 12px;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-btn {
|
||||||
|
margin: 20px 0;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete {
|
||||||
|
margin: 0 3px;
|
||||||
|
}
|
||||||
|
</style>
|
53
src/views/dashboard/related/topology/utils/legend.ts
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the 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.
|
||||||
|
* The 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.
|
||||||
|
*/
|
||||||
|
import icons from "@/assets/img/icons";
|
||||||
|
|
||||||
|
export default function topoLegend(
|
||||||
|
graph: any,
|
||||||
|
clientHeight: number,
|
||||||
|
clientWidth: number,
|
||||||
|
config: any
|
||||||
|
) {
|
||||||
|
for (const item of ["CUBE", "CUBEERROR"]) {
|
||||||
|
graph
|
||||||
|
.append("image")
|
||||||
|
.attr("width", 30)
|
||||||
|
.attr("height", 30)
|
||||||
|
.attr("x", clientWidth - (item === "CUBEERROR" ? 340 : 440))
|
||||||
|
.attr("y", clientHeight - 50)
|
||||||
|
.attr("xlink:href", () =>
|
||||||
|
item === "CUBEERROR" ? icons.CUBEERROR : icons.CUBE
|
||||||
|
);
|
||||||
|
graph
|
||||||
|
.append("text")
|
||||||
|
.attr("x", clientWidth - (item === "CUBEERROR" ? 310 : 410))
|
||||||
|
.attr("y", clientHeight - 30)
|
||||||
|
.text(() => {
|
||||||
|
const l = config || [];
|
||||||
|
const str = l
|
||||||
|
.map((d: any) => `${d.name} ${d.condition} ${d.value}`)
|
||||||
|
.join(" and ");
|
||||||
|
return item === "CUBEERROR"
|
||||||
|
? config
|
||||||
|
? `Unhealthy (${str})`
|
||||||
|
: "Unhealthy"
|
||||||
|
: "Healthy";
|
||||||
|
})
|
||||||
|
.style("fill", "#efeff1")
|
||||||
|
.style("font-size", "11px");
|
||||||
|
}
|
||||||
|
}
|
60
src/views/dashboard/related/topology/utils/linkElement.ts
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the 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.
|
||||||
|
* The 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const linkElement = (graph: any) => {
|
||||||
|
const linkEnter = graph
|
||||||
|
.append("path")
|
||||||
|
.attr("class", "topo-line")
|
||||||
|
.attr("marker-end", "url(#arrow)")
|
||||||
|
.attr("stroke", "#217EF25f");
|
||||||
|
return linkEnter;
|
||||||
|
};
|
||||||
|
export const anchorElement = (graph: any, funcs: any, tip: any) => {
|
||||||
|
const linkEnter = graph
|
||||||
|
.append("circle")
|
||||||
|
.attr("class", "topo-line-anchor")
|
||||||
|
.attr("r", 5)
|
||||||
|
.attr("fill", "#217EF25f")
|
||||||
|
.on("mouseover", function (event: unknown, d: unknown) {
|
||||||
|
tip.html(funcs.tipHtml).show(d, this);
|
||||||
|
})
|
||||||
|
.on("mouseout", function () {
|
||||||
|
tip.hide(this);
|
||||||
|
})
|
||||||
|
.on("click", (event: unknown, d: unknown) => {
|
||||||
|
funcs.handleLinkClick(event, d);
|
||||||
|
});
|
||||||
|
return linkEnter;
|
||||||
|
};
|
||||||
|
export const arrowMarker = (graph: any) => {
|
||||||
|
const defs = graph.append("defs");
|
||||||
|
const arrow = defs
|
||||||
|
.append("marker")
|
||||||
|
.attr("id", "arrow")
|
||||||
|
.attr("class", "topo-line-arrow")
|
||||||
|
.attr("markerUnits", "strokeWidth")
|
||||||
|
.attr("markerWidth", "6")
|
||||||
|
.attr("markerHeight", "6")
|
||||||
|
.attr("viewBox", "0 0 12 12")
|
||||||
|
.attr("refX", "5")
|
||||||
|
.attr("refY", "6")
|
||||||
|
.attr("orient", "auto");
|
||||||
|
const arrowPath = "M2,2 L10,6 L2,10 L6,6 L2,2";
|
||||||
|
|
||||||
|
arrow.append("path").attr("d", arrowPath).attr("fill", "#217EF25f");
|
||||||
|
return arrow;
|
||||||
|
};
|
96
src/views/dashboard/related/topology/utils/nodeElement.ts
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the 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.
|
||||||
|
* The 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.
|
||||||
|
*/
|
||||||
|
import icons from "@/assets/img/icons";
|
||||||
|
import { Node } from "@/types/topology";
|
||||||
|
|
||||||
|
icons["KAFKA-CONSUMER"] = icons.KAFKA;
|
||||||
|
export default (d3: any, graph: any, funcs: any, tip: any, legend: any) => {
|
||||||
|
const nodeEnter = graph
|
||||||
|
.append("g")
|
||||||
|
.call(
|
||||||
|
d3
|
||||||
|
.drag()
|
||||||
|
.on("start", funcs.dragstart)
|
||||||
|
.on("drag", funcs.dragged)
|
||||||
|
.on("end", funcs.dragended)
|
||||||
|
)
|
||||||
|
.on("mouseover", function (event: any, d: Node) {
|
||||||
|
tip.html(funcs.tipHtml).show(d, this);
|
||||||
|
})
|
||||||
|
.on("mouseout", function () {
|
||||||
|
tip.hide(this);
|
||||||
|
})
|
||||||
|
.on("click", (event: any, d: Node | any) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
event.preventDefault();
|
||||||
|
funcs.handleNodeClick(d);
|
||||||
|
});
|
||||||
|
nodeEnter
|
||||||
|
.append("image")
|
||||||
|
.attr("width", 49)
|
||||||
|
.attr("height", 49)
|
||||||
|
.attr("x", 2)
|
||||||
|
.attr("y", 10)
|
||||||
|
.attr("style", "cursor: move;")
|
||||||
|
.attr("xlink:href", (d: { [key: string]: number }) => {
|
||||||
|
if (!legend) {
|
||||||
|
return icons.CUBE;
|
||||||
|
}
|
||||||
|
if (!legend.length) {
|
||||||
|
return icons.CUBE;
|
||||||
|
}
|
||||||
|
let c = true;
|
||||||
|
for (const l of legend) {
|
||||||
|
const val = l.name.includes("_sla") ? d[l.name] / 100 : d[l.name];
|
||||||
|
if (l.condition === "<") {
|
||||||
|
c = c && val < Number(l.value);
|
||||||
|
} else {
|
||||||
|
c = c && val > Number(l.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return c && d.isReal ? icons.CUBEERROR : icons.CUBE;
|
||||||
|
});
|
||||||
|
nodeEnter
|
||||||
|
.append("image")
|
||||||
|
.attr("width", 32)
|
||||||
|
.attr("height", 32)
|
||||||
|
.attr("x", 6)
|
||||||
|
.attr("y", -10)
|
||||||
|
.attr("style", "opacity: 0.5;")
|
||||||
|
.attr("xlink:href", icons.LOCAL);
|
||||||
|
nodeEnter
|
||||||
|
.append("image")
|
||||||
|
.attr("width", 18)
|
||||||
|
.attr("height", 18)
|
||||||
|
.attr("x", 13)
|
||||||
|
.attr("y", -7)
|
||||||
|
.attr("xlink:href", (d: { type: string }) =>
|
||||||
|
!d.type || d.type === "N/A"
|
||||||
|
? icons.UNDEFINED
|
||||||
|
: icons[d.type.toUpperCase().replace("-", "")]
|
||||||
|
);
|
||||||
|
nodeEnter
|
||||||
|
.append("text")
|
||||||
|
.attr("class", "topo-text")
|
||||||
|
.attr("text-anchor", "middle")
|
||||||
|
.attr("x", 22)
|
||||||
|
.attr("y", 70)
|
||||||
|
.text((d: { name: string }) =>
|
||||||
|
d.name.length > 20 ? `${d.name.substring(0, 20)}...` : d.name
|
||||||
|
);
|
||||||
|
return nodeEnter;
|
||||||
|
};
|
56
src/views/dashboard/related/topology/utils/simulation.ts
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the 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.
|
||||||
|
* The 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.
|
||||||
|
*/
|
||||||
|
export const simulationInit = (
|
||||||
|
d3: any,
|
||||||
|
dataNodes: any,
|
||||||
|
dataLinks: any,
|
||||||
|
ticked: any
|
||||||
|
) => {
|
||||||
|
const simulation = d3
|
||||||
|
.forceSimulation(dataNodes)
|
||||||
|
.force(
|
||||||
|
"collide",
|
||||||
|
d3.forceCollide().radius(() => 60)
|
||||||
|
)
|
||||||
|
.force("yPos", d3.forceY().strength(1))
|
||||||
|
.force("xPos", d3.forceX().strength(1))
|
||||||
|
.force("charge", d3.forceManyBody().strength(-520))
|
||||||
|
.force(
|
||||||
|
"link",
|
||||||
|
d3.forceLink(dataLinks).id((d: { id: string }) => d.id)
|
||||||
|
)
|
||||||
|
.force(
|
||||||
|
"center",
|
||||||
|
d3.forceCenter(window.innerWidth / 2, window.innerHeight / 2 - 20)
|
||||||
|
)
|
||||||
|
.on("tick", ticked)
|
||||||
|
.stop();
|
||||||
|
simulationSkip(d3, simulation, ticked);
|
||||||
|
return simulation;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const simulationSkip = (d3: any, simulation: any, ticked: any) => {
|
||||||
|
d3.timeout(() => {
|
||||||
|
const n = Math.ceil(
|
||||||
|
Math.log(simulation.alphaMin()) / Math.log(1 - simulation.alphaDecay())
|
||||||
|
);
|
||||||
|
for (let i = 0; i < n; i += 1) {
|
||||||
|
simulation.tick();
|
||||||
|
ticked();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
28
src/views/dashboard/related/topology/utils/zoom.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the 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.
|
||||||
|
* The 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export default (d3: any, graph: any) =>
|
||||||
|
d3
|
||||||
|
.zoom()
|
||||||
|
.scaleExtent([0.3, 10])
|
||||||
|
.on("zoom", (d: any) => {
|
||||||
|
graph
|
||||||
|
.attr("transform", d3.zoomTransform(graph.node()))
|
||||||
|
.attr(
|
||||||
|
`translate(${d.transform.x},${d.transform.y})scale(${d.transform.k})`
|
||||||
|
);
|
||||||
|
});
|
@ -30,6 +30,7 @@
|
|||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
|
"noImplicitThis": false,
|
||||||
"types": [
|
"types": [
|
||||||
"webpack-env",
|
"webpack-env",
|
||||||
"jest"
|
"jest"
|
||||||
|