feat: add Trace in dashboards (#18)
* feat: add trace tool * feat: add trace * feat: add trace filters * feat: add trace list * feat: add trace detail * fix: update trace detail * feat: add trace list * fix: update trace list * feat: add trace tree * fix: update trace tree * feat: add trace table * feat: add trace statistics * fix: update trace statistics * fix: update resize * feat: add trace log * feat: add related logs * feat: add loading * fix: update name * feat: watch selectors * fix: view span on table * fix ci * fix: update file name * fix: update file name * fix: update file name * fix: update filters * build: add package
16
package-lock.json
generated
@ -13,6 +13,7 @@
|
||||
"d3-tip": "^0.9.1",
|
||||
"echarts": "^5.2.2",
|
||||
"element-plus": "^2.0.2",
|
||||
"lodash": "^4.17.21",
|
||||
"pinia": "^2.0.5",
|
||||
"three": "^0.131.3",
|
||||
"three-orbit-controls": "^82.1.0",
|
||||
@ -28,6 +29,7 @@
|
||||
"@types/d3-tip": "^3.5.5",
|
||||
"@types/echarts": "^4.9.12",
|
||||
"@types/jest": "^24.0.19",
|
||||
"@types/lodash": "^4.14.179",
|
||||
"@types/three": "^0.131.0",
|
||||
"@typescript-eslint/eslint-plugin": "^4.18.0",
|
||||
"@typescript-eslint/parser": "^4.18.0",
|
||||
@ -3325,10 +3327,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/lodash": {
|
||||
"version": "4.14.178",
|
||||
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.178.tgz",
|
||||
"integrity": "sha512-0d5Wd09ItQWH1qFbEyQ7oTQ3GZrMfth5JkbN3EvTKLXcHLRDSXeLnlvlOn0wvxVIwK5o2M8JzP/OWz7T3NRsbw==",
|
||||
"peer": true
|
||||
"version": "4.14.179",
|
||||
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.179.tgz",
|
||||
"integrity": "sha512-uwc1x90yCKqGcIOAT6DwOSuxnrAbpkdPsUOZtwrXb4D/6wZs+6qG7QnIawDuZWg0sWpxl+ltIKCaLoMlna678w=="
|
||||
},
|
||||
"node_modules/@types/lodash-es": {
|
||||
"version": "4.17.6",
|
||||
@ -31306,10 +31307,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"@types/lodash": {
|
||||
"version": "4.14.178",
|
||||
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.178.tgz",
|
||||
"integrity": "sha512-0d5Wd09ItQWH1qFbEyQ7oTQ3GZrMfth5JkbN3EvTKLXcHLRDSXeLnlvlOn0wvxVIwK5o2M8JzP/OWz7T3NRsbw==",
|
||||
"peer": true
|
||||
"version": "4.14.179",
|
||||
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.179.tgz",
|
||||
"integrity": "sha512-uwc1x90yCKqGcIOAT6DwOSuxnrAbpkdPsUOZtwrXb4D/6wZs+6qG7QnIawDuZWg0sWpxl+ltIKCaLoMlna678w=="
|
||||
},
|
||||
"@types/lodash-es": {
|
||||
"version": "4.17.6",
|
||||
|
@ -15,6 +15,7 @@
|
||||
"d3-tip": "^0.9.1",
|
||||
"echarts": "^5.2.2",
|
||||
"element-plus": "^2.0.2",
|
||||
"lodash": "^4.17.21",
|
||||
"pinia": "^2.0.5",
|
||||
"three": "^0.131.3",
|
||||
"three-orbit-controls": "^82.1.0",
|
||||
@ -30,6 +31,7 @@
|
||||
"@types/d3-tip": "^3.5.5",
|
||||
"@types/echarts": "^4.9.12",
|
||||
"@types/jest": "^24.0.19",
|
||||
"@types/lodash": "^4.14.179",
|
||||
"@types/three": "^0.131.0",
|
||||
"@typescript-eslint/eslint-plugin": "^4.18.0",
|
||||
"@typescript-eslint/parser": "^4.18.0",
|
||||
@ -88,7 +90,8 @@
|
||||
"@typescript-eslint/explicit-function-return-type": "off",
|
||||
"no-use-before-define": "off",
|
||||
"@typescript-eslint/no-use-before-define": "off",
|
||||
"@typescript-eslint/no-non-null-assertion": "off"
|
||||
"@typescript-eslint/no-non-null-assertion": "off",
|
||||
"@typescript-eslint/no-this-alias": "off"
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
|
17
src/assets/icons/help.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="M15.047 11.25q0.938-0.938 0.938-2.25 0-1.641-1.172-2.813t-2.813-1.172-2.813 1.172-1.172 2.813h1.969q0-0.797 0.609-1.406t1.406-0.609 1.406 0.609 0.609 1.406-0.609 1.406l-1.219 1.266q-1.172 1.266-1.172 2.813v0.516h1.969q0-1.547 1.172-2.813zM12.984 18.984v-1.969h-1.969v1.969h1.969zM12 2.016q4.125 0 7.055 2.93t2.93 7.055-2.93 7.055-7.055 2.93-7.055-2.93-2.93-7.055 2.93-7.055 7.055-2.93z"></path>
|
||||
</svg>
|
After Width: | Height: | Size: 1.2 KiB |
16
src/assets/icons/issue-child.svg
Executable file
@ -0,0 +1,16 @@
|
||||
<!-- 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 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/issue-child</title><path d="M11 8H5v1h.7A1.3 1.3 0 0 1 7 10.3v2.4A1.3 1.3 0 0 1 5.7 14H3.3A1.3 1.3 0 0 1 2 12.7v-2.4a1.3 1.3 0 0 1 1-1.265V7a1 1 0 0 1 1-1h3V5H4.5a.5.5 0 0 1-.5-.5v-2a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-.5.5H9v1h3a1 1 0 0 1 1 1v2.035a1.3 1.3 0 0 1 1 1.265v2.4a1.3 1.3 0 0 1-1.3 1.3h-2.4A1.3 1.3 0 0 1 9 12.7v-2.4A1.3 1.3 0 0 1 10.3 9h.7V8zm0 3v1h1v-1h-1zm-7 0v1h1v-1H4z" id="a"/></svg>
|
After Width: | Height: | Size: 1.3 KiB |
16
src/assets/icons/issue-open-m.svg
Executable file
@ -0,0 +1,16 @@
|
||||
<!-- 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 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/issue-open-m</title><path d="M8 13A5 5 0 1 0 8 3a5 5 0 0 0 0 10zm0 2A7 7 0 1 1 8 1a7 7 0 0 1 0 14z" id="a"/></svg>
|
After Width: | Height: | Size: 1017 B |
16
src/assets/icons/list-bulleted.svg
Executable file
@ -0,0 +1,16 @@
|
||||
<!-- 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 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/list-bulleted</title><path d="M3 4a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm0 5a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm0 5a1 1 0 1 1 0-2 1 1 0 0 1 0 2zM5.818 2h7.364c.452 0 .818.448.818 1s-.366 1-.818 1H5.818C5.366 4 5 3.552 5 3s.366-1 .818-1zm0 5h7.364c.452 0 .818.448.818 1s-.366 1-.818 1H5.818C5.366 9 5 8.552 5 8s.366-1 .818-1zm0 5h7.364c.452 0 .818.448.818 1s-.366 1-.818 1H5.818C5.366 14 5 13.552 5 13s.366-1 .818-1z" id="a"/></svg>
|
After Width: | Height: | Size: 1.3 KiB |
18
src/assets/icons/merge.svg
Executable file
@ -0,0 +1,18 @@
|
||||
<!-- 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 xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<path d="M5.08964366,4.67738245 C5.53724055,6.35901626 7.61507769,7.75687802 10.2843944,7.97145461 C10.6340823,7.38943035 11.271563,7 12,7 C13.1045695,7 14,7.8954305 14,9 C14,10.1045695 13.1045695,11 12,11 C11.2491882,11 10.5950062,10.5862786 10.2529915,9.97437331 C8.18114287,9.83159096 6.31872952,9.10035115 5,7.98669933 L5,11.2675644 C5.59780137,11.6133738 6,12.2597176 6,13 C6,14.1045695 5.1045695,15 4,15 C2.8954305,15 2,14.1045695 2,13 C2,12.2597176 2.40219863,11.6133738 3,11.2675644 L3,4.73243561 C2.40219863,4.38662619 2,3.74028236 2,3 C2,1.8954305 2.8954305,1 4,1 C5.1045695,1 6,1.8954305 6,3 C6,3.70264175 5.63766215,4.32065497 5.08964366,4.67738245 Z M4,4 C4.55228475,4 5,3.55228475 5,3 C5,2.44771525 4.55228475,2 4,2 C3.44771525,2 3,2.44771525 3,3 C3,3.55228475 3.44771525,4 4,4 Z M4,14 C4.55228475,14 5,13.5522847 5,13 C5,12.4477153 4.55228475,12 4,12 C3.44771525,12 3,12.4477153 3,13 C3,13.5522847 3.44771525,14 4,14 Z M12,10 C12.5522847,10 13,9.55228475 13,9 C13,8.44771525 12.5522847,8 12,8 C11.4477153,8 11,8.44771525 11,9 C11,9.55228475 11.4477153,10 12,10 Z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.9 KiB |
17
src/assets/icons/settings_ethernet.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.766 5.484l5.438 6.516-5.438 6.516-1.547-1.266 4.359-5.25-4.359-5.25zM11.016 12.984v-1.969h1.969v1.969h-1.969zM17.016 11.016v1.969h-2.016v-1.969h2.016zM6.984 12.984v-1.969h2.016v1.969h-2.016zM7.781 6.75l-4.359 5.25 4.359 5.25-1.547 1.266-5.438-6.516 5.438-6.516z"></path>
|
||||
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
15
src/assets/icons/sort.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="1620798822338" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1847" width="200" height="200"><path d="M679.765333 597.333333a21.333333 21.333333 0 0 1 16.426667 34.986667l-167.808 201.386667a21.333333 21.333333 0 0 1-32.768 0l-167.808-201.386667a21.333333 21.333333 0 0 1 16.426667-34.986667zM525.653333 187.605333a21.333333 21.333333 0 0 1 2.730667 2.730667l167.808 201.344a21.333333 21.333333 0 0 1-16.426667 34.986667H344.234667a21.333333 21.333333 0 0 1-16.426667-34.986667l167.808-201.386667a21.333333 21.333333 0 0 1 30.037333-2.688z" fill="#999999" p-id="1848"></path></svg>
|
After Width: | Height: | Size: 1.4 KiB |
16
src/assets/icons/spinner.svg
Executable file
@ -0,0 +1,16 @@
|
||||
<!-- 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 xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14"><g fill="none" fill-rule="evenodd"><circle cx="7" cy="7" r="6" stroke="#000" stroke-opacity=".1" stroke-width="2"/><path fill="#000" fill-opacity=".1" fill-rule="nonzero" d="M7 0a7 7 0 0 1 7 7h-2a5 5 0 0 0-5-5V0z"/></g></svg>
|
After Width: | Height: | Size: 1.0 KiB |
15
src/assets/icons/statistics-bulleted.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="1619507658599" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2073" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M331.840623 793.484755 331.840623 387.450978c0-16.191531-12.457456-28.645338-27.399836-28.645338L222.235197 358.80564c-14.94238 0-27.399836 13.698094-27.399836 28.645338L194.835361 793.484755 331.840623 793.484755 331.840623 793.484755zM506.210956 793.484755 506.210956 213.081861c0-16.192747-12.453808-29.89449-27.401052-29.89449l-82.20559 0c-14.94238 0-27.399836 13.701743-27.399836 29.89449L369.204478 793.484755 506.210956 793.484755 506.210956 793.484755zM680.580073 793.484755 680.580073 536.910048c0-16.191531-12.452591-29.889625-27.399836-29.889625L570.979512 507.020423c-14.947245 0-27.405918 13.698094-27.405918 29.889625L543.573595 793.484755 680.580073 793.484755 680.580073 793.484755zM854.94919 793.484755 854.94919 387.450978c0-16.191531-12.452591-28.645338-27.399836-28.645338l-82.200725 0c-14.947245 0-27.399836 13.698094-27.399836 28.645338L717.948794 793.484755 854.94919 793.484755 854.94919 793.484755zM879.860454 830.84861" p-id="2074" fill="#ffffff"></path></svg>
|
After Width: | Height: | Size: 1.9 KiB |
23
src/assets/icons/table.svg
Executable file
@ -0,0 +1,23 @@
|
||||
<!-- 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 width="12px" height="12px" viewBox="0 0 12 12" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="table" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="list-bulleted" fill="#FFFFFF" fill-rule="nonzero">
|
||||
<path d="M1,2 C0.44771525,2 0,1.55228475 0,1 C0,0.44771525 0.44771525,0 1,0 C1.55228475,0 2,0.44771525 2,1 C2,1.55228475 1.55228475,2 1,2 Z M11,2 C10.4477153,2 10,1.55228475 10,1 C10,0.44771525 10.4477153,0 11,0 C11.5522847,0 12,0.44771525 12,1 C12,1.55228475 11.5522847,2 11,2 Z M8,2 C7.44771525,2 7,1.55228475 7,1 C7,0.44771525 7.44771525,0 8,0 C8.55228475,0 9,0.44771525 9,1 C9,1.55228475 8.55228475,2 8,2 Z M4,2 C3.44771525,2 3,1.55228475 3,1 C3,0.44771525 3.44771525,0 4,0 C4.55228475,0 5,0.44771525 5,1 C5,1.55228475 4.55228475,2 4,2 Z M1.09066667,5 L10.9093333,5 C11.512,5 12,5.448 12,6 C12,6.552 11.512,7 10.9093333,7 L1.09066667,7 C0.488,7 -1.77635684e-15,6.552 -1.77635684e-15,6 C-1.77635684e-15,5.448 0.488,5 1.09066667,5 Z M1.09066667,10 L10.9093333,10 C11.512,10 12,10.448 12,11 C12,11.552 11.512,12 10.9093333,12 L1.09066667,12 C0.488,12 -1.77635684e-15,11.552 -1.77635684e-15,11 C-1.77635684e-15,10.448 0.488,10 1.09066667,10 Z" id="a"></path>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.0 KiB |
@ -35,7 +35,6 @@ limitations under the License. -->
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch } from "vue";
|
||||
import type { PropType } from "vue";
|
||||
import { ElSelect, ElOption } from "element-plus";
|
||||
|
||||
interface Option {
|
||||
label: string;
|
||||
|
65
src/graphql/fragments/log.ts
Normal file
@ -0,0 +1,65 @@
|
||||
/**
|
||||
* 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 QueryBrowserErrorLogs = {
|
||||
variable: "$condition: BrowserErrorLogQueryCondition",
|
||||
query: `
|
||||
queryBrowserErrorLogs(condition: $condition) {
|
||||
logs {
|
||||
message
|
||||
service
|
||||
serviceVersion
|
||||
time
|
||||
pagePath
|
||||
category
|
||||
errorUrl
|
||||
stack
|
||||
grade
|
||||
}
|
||||
total
|
||||
}`,
|
||||
};
|
||||
|
||||
export const QueryServiceLogs = {
|
||||
variable: "$condition: LogQueryCondition",
|
||||
query: `
|
||||
queryLogs(condition: $condition) {
|
||||
logs {
|
||||
serviceName
|
||||
serviceId
|
||||
serviceInstanceName
|
||||
serviceInstanceId
|
||||
endpointName
|
||||
endpointId
|
||||
traceId
|
||||
timestamp
|
||||
contentType
|
||||
content
|
||||
tags {
|
||||
key
|
||||
value
|
||||
}
|
||||
}
|
||||
total
|
||||
}`,
|
||||
};
|
||||
|
||||
export const QueryLogsByKeywords = {
|
||||
variable: "",
|
||||
query: `
|
||||
support: supportQueryLogsByKeywords`,
|
||||
};
|
76
src/graphql/fragments/trace.ts
Normal file
@ -0,0 +1,76 @@
|
||||
/**
|
||||
* 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 Traces = {
|
||||
variable: "$condition: TraceQueryCondition",
|
||||
query: `
|
||||
data: queryBasicTraces(condition: $condition) {
|
||||
traces {
|
||||
key: segmentId
|
||||
endpointNames
|
||||
duration
|
||||
start
|
||||
isError
|
||||
traceIds
|
||||
}
|
||||
total
|
||||
}`,
|
||||
};
|
||||
|
||||
/**
|
||||
* @param { traceId } { string }
|
||||
*/
|
||||
export const TraceSpans = {
|
||||
variable: "$traceId: ID!",
|
||||
query: `
|
||||
trace: queryTrace(traceId: $traceId) {
|
||||
spans {
|
||||
traceId
|
||||
segmentId
|
||||
spanId
|
||||
parentSpanId
|
||||
refs {
|
||||
traceId
|
||||
parentSegmentId
|
||||
parentSpanId
|
||||
type
|
||||
}
|
||||
serviceCode
|
||||
serviceInstanceName
|
||||
startTime
|
||||
endTime
|
||||
endpointName
|
||||
type
|
||||
peer
|
||||
component
|
||||
isError
|
||||
layer
|
||||
tags {
|
||||
key
|
||||
value
|
||||
}
|
||||
logs {
|
||||
time
|
||||
data {
|
||||
key
|
||||
value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
};
|
@ -20,12 +20,16 @@ import * as app from "./query/app";
|
||||
import * as selector from "./query/selector";
|
||||
import * as dashboard from "./query/dashboard";
|
||||
import * as topology from "./query/topology";
|
||||
import * as trace from "./query/trace";
|
||||
import * as log from "./query/log";
|
||||
|
||||
const query: { [key: string]: string } = {
|
||||
...app,
|
||||
...selector,
|
||||
...dashboard,
|
||||
...topology,
|
||||
...trace,
|
||||
...log,
|
||||
};
|
||||
class Graphql {
|
||||
private queryData = "";
|
||||
|
27
src/graphql/query/log.ts
Normal file
@ -0,0 +1,27 @@
|
||||
/**
|
||||
* 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 {
|
||||
QueryBrowserErrorLogs,
|
||||
QueryServiceLogs,
|
||||
QueryLogsByKeywords,
|
||||
} from "../fragments/log";
|
||||
|
||||
export const queryBrowserErrorLogs = `query queryBrowserErrorLogs(${QueryBrowserErrorLogs.variable}) {
|
||||
${QueryBrowserErrorLogs.query}}`;
|
||||
export const queryServiceLogs = `query queryLogs(${QueryServiceLogs.variable}) {${QueryServiceLogs.query}}`;
|
||||
export const queryLogsByKeywords = `query queryLogsByKeywords {${QueryLogsByKeywords.query}}`;
|
22
src/graphql/query/trace.ts
Normal file
@ -0,0 +1,22 @@
|
||||
/**
|
||||
* 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 { Traces, TraceSpans } from "../fragments/trace";
|
||||
|
||||
export const queryTraces = `query queryTraces(${Traces.variable}) {${Traces.query}}`;
|
||||
|
||||
export const queryTrace = `query queryTrace(${TraceSpans.variable}) {${TraceSpans.query}}`;
|
@ -90,6 +90,9 @@ const msg = {
|
||||
backgroundColors: "Background Colors",
|
||||
fontColors: "Font Colors",
|
||||
iconTheme: "Icon Theme",
|
||||
default: "Default",
|
||||
topSlow: "Top 5 of slow",
|
||||
topChildren: "Top 5 of children",
|
||||
hourTip: "Select Hour",
|
||||
minuteTip: "Select Minute",
|
||||
secondTip: "Select Second",
|
||||
@ -320,7 +323,7 @@ const msg = {
|
||||
addTraceID: "Please input a trace ID",
|
||||
addKeywordsOfContent: "Please input a keyword of content",
|
||||
addExcludingKeywordsOfContent: "Please input a keyword of excluding content",
|
||||
noticeTag: "Please press enter after inputting a tag.",
|
||||
noticeTag: "Please press Enter after inputting a tag(key=value).",
|
||||
conditionNotice:
|
||||
"Notice: Please press enter after inputting a tag, key of content, exclude key of content.",
|
||||
cacheModalTitle: "Clear cache reminder",
|
||||
|
@ -89,6 +89,9 @@ const msg = {
|
||||
backgroundColors: "背景颜色",
|
||||
fontColors: "字体颜色",
|
||||
iconTheme: "图标主题",
|
||||
default: "默认",
|
||||
topSlow: "迟缓的前5名",
|
||||
topChildren: "小孩数量的前5名",
|
||||
showDepth: "展示深度选择器",
|
||||
hourTip: "选择小时",
|
||||
minuteTip: "选择分钟",
|
||||
@ -321,7 +324,7 @@ const msg = {
|
||||
addTraceID: "请输入一个Trace ID",
|
||||
addKeywordsOfContent: "请输入一个内容关键词",
|
||||
addExcludingKeywordsOfContent: "请输入一个内容不包含的关键词",
|
||||
noticeTag: "请输入一个标签之后回车",
|
||||
noticeTag: "请输入一个标签(key=value)之后回车",
|
||||
conditionNotice: "请输入一个标签、内容关键词或者内容不包含的关键词之后回车",
|
||||
cacheModalTitle: "清除缓存提醒",
|
||||
yes: "是的",
|
||||
|
@ -21,8 +21,7 @@ import { store } from "./store";
|
||||
import components from "@/components";
|
||||
import i18n from "./locales";
|
||||
import "element-plus/dist/index.css";
|
||||
import "./styles/lib.scss";
|
||||
import "./styles/reset.scss";
|
||||
import "./styles/index.scss";
|
||||
import ElementPlus from "element-plus";
|
||||
|
||||
const app = createApp(App);
|
||||
|
@ -63,7 +63,7 @@ export const dashboardStore = defineStore({
|
||||
this.layout = data;
|
||||
},
|
||||
addControl(type: string) {
|
||||
const newWidget: LayoutConfig = {
|
||||
const newItem: LayoutConfig = {
|
||||
...NewControl,
|
||||
i: String(this.layout.length),
|
||||
type,
|
||||
@ -71,8 +71,8 @@ export const dashboardStore = defineStore({
|
||||
metrics: [""],
|
||||
};
|
||||
if (type === "Tab") {
|
||||
newWidget.h = 24;
|
||||
newWidget.children = [
|
||||
newItem.h = 24;
|
||||
newItem.children = [
|
||||
{
|
||||
name: "Tab1",
|
||||
children: [],
|
||||
@ -84,9 +84,9 @@ export const dashboardStore = defineStore({
|
||||
];
|
||||
}
|
||||
if (type === "Topology") {
|
||||
newWidget.w = 4;
|
||||
newWidget.h = 6;
|
||||
newWidget.graph = {
|
||||
newItem.w = 4;
|
||||
newItem.h = 6;
|
||||
newItem.graph = {
|
||||
fontColor: "white",
|
||||
backgroundColor: "green",
|
||||
iconTheme: true,
|
||||
@ -95,12 +95,15 @@ export const dashboardStore = defineStore({
|
||||
showDepth: true,
|
||||
};
|
||||
}
|
||||
if (type === "Trace") {
|
||||
newItem.h = 24;
|
||||
}
|
||||
this.layout = this.layout.map((d: LayoutConfig) => {
|
||||
d.y = d.y + newWidget.h;
|
||||
d.y = d.y + newItem.h;
|
||||
return d;
|
||||
});
|
||||
this.layout.push(newWidget);
|
||||
this.activedGridItem = newWidget.i;
|
||||
this.layout.push(newItem);
|
||||
this.activedGridItem = newItem.i;
|
||||
},
|
||||
addTabItem(item: LayoutConfig) {
|
||||
const idx = this.layout.findIndex((d: LayoutConfig) => d.i === item.i);
|
||||
|
172
src/store/modules/trace.ts
Normal file
@ -0,0 +1,172 @@
|
||||
/**
|
||||
* 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 { Duration } from "@/types/app";
|
||||
import { Instance, Endpoint, Service } from "@/types/selector";
|
||||
import { Trace, Span } from "@/types/trace";
|
||||
import { store } from "@/store";
|
||||
import graphql from "@/graphql";
|
||||
import { AxiosResponse } from "axios";
|
||||
import { useAppStoreWithOut } from "@/store/modules/app";
|
||||
import { useSelectorStore } from "@/store/modules/selectors";
|
||||
|
||||
interface TraceState {
|
||||
services: Service[];
|
||||
instances: Instance[];
|
||||
endpoints: Endpoint[];
|
||||
traceList: Trace[];
|
||||
traceTotal: number;
|
||||
traceSpans: Span[];
|
||||
currentTrace: Trace | any;
|
||||
conditions: any;
|
||||
traceSpanLogs: any[];
|
||||
traceSpanLogsTotal: number;
|
||||
// traceListErrors: string;
|
||||
// traceSpanErrors: string;
|
||||
// traceSpanLogErrors: string;
|
||||
durationTime: Duration;
|
||||
selectorStore: any;
|
||||
}
|
||||
|
||||
export const traceStore = defineStore({
|
||||
id: "trace",
|
||||
state: (): TraceState => ({
|
||||
services: [{ value: "0", label: "All" }],
|
||||
instances: [{ value: "0", label: "All" }],
|
||||
endpoints: [{ value: "0", label: "All" }],
|
||||
traceList: [],
|
||||
traceSpans: [],
|
||||
traceTotal: 0,
|
||||
currentTrace: {},
|
||||
conditions: {
|
||||
queryDuration: useAppStoreWithOut().durationTime,
|
||||
traceState: "ALL",
|
||||
queryOrder: "BY_START_TIME",
|
||||
paging: { pageNum: 1, pageSize: 15, needTotal: true },
|
||||
},
|
||||
traceSpanLogs: [],
|
||||
traceSpanLogsTotal: 0,
|
||||
durationTime: useAppStoreWithOut().durationTime,
|
||||
selectorStore: useSelectorStore(),
|
||||
}),
|
||||
actions: {
|
||||
setTraceCondition(data: any) {
|
||||
this.condition = { ...this.condition, ...data };
|
||||
},
|
||||
setCurrentTrace(trace: Trace) {
|
||||
this.currentTrace = trace;
|
||||
},
|
||||
setTraceSpans(spans: Span) {
|
||||
this.traceSpans = spans;
|
||||
},
|
||||
async getServices(layer: string) {
|
||||
const res: AxiosResponse = await graphql.query("queryServices").params({
|
||||
layer,
|
||||
});
|
||||
if (res.data.errors) {
|
||||
return res.data;
|
||||
}
|
||||
this.services = [
|
||||
{ value: "0", label: "All" },
|
||||
...res.data.data.services,
|
||||
] || [{ value: "0", label: "All" }];
|
||||
return res.data;
|
||||
},
|
||||
async getInstances() {
|
||||
const res: AxiosResponse = await graphql.query("queryInstances").params({
|
||||
serviceId: this.selectorStore.currentService.id,
|
||||
duration: this.durationTime,
|
||||
});
|
||||
|
||||
if (res.data.errors) {
|
||||
return res.data;
|
||||
}
|
||||
this.instances = [
|
||||
{ value: "0", label: "All" },
|
||||
...res.data.data.pods,
|
||||
] || [{ value: " 0", label: "All" }];
|
||||
return res.data;
|
||||
},
|
||||
async getEndpoints() {
|
||||
const res: AxiosResponse = await graphql.query("queryEndpoints").params({
|
||||
serviceId: this.selectorStore.currentService.id,
|
||||
duration: this.durationTime,
|
||||
keyword: "",
|
||||
});
|
||||
if (res.data.errors) {
|
||||
return res.data;
|
||||
}
|
||||
this.endpoints = [
|
||||
{ value: "0", label: "All" },
|
||||
...res.data.data.pods,
|
||||
] || [{ value: "0", label: "All" }];
|
||||
return res.data;
|
||||
},
|
||||
async getTraces() {
|
||||
const res: AxiosResponse = await graphql
|
||||
.query("queryTraces")
|
||||
.params({ condition: this.condition });
|
||||
if (res.data.errors) {
|
||||
return res.data;
|
||||
}
|
||||
if (!res.data.data.data.traces.length) {
|
||||
this.traceTotal = 0;
|
||||
this.traceList = [];
|
||||
this.setCurrentTrace({});
|
||||
this.setTraceSpans([]);
|
||||
return res.data;
|
||||
}
|
||||
this.getTraceSpans({ traceId: res.data.data.data.traces[0].traceIds[0] });
|
||||
this.traceList = res.data.data.data.traces.map((d: Trace) => {
|
||||
d.traceIds = d.traceIds.map((id: string) => {
|
||||
return { value: id, label: id };
|
||||
});
|
||||
return d;
|
||||
});
|
||||
this.traceTotal = res.data.data.data.total;
|
||||
this.setCurrentTrace(res.data.data.data.traces[0] || {});
|
||||
return res.data;
|
||||
},
|
||||
async getTraceSpans(params: { traceId: string }) {
|
||||
const res: AxiosResponse = await graphql
|
||||
.query("queryTrace")
|
||||
.params(params);
|
||||
if (res.data.errors) {
|
||||
return res.data;
|
||||
}
|
||||
this.setTraceSpans(res.data.data.trace.spans || []);
|
||||
return res.data;
|
||||
},
|
||||
async getSpanLogs(params: any) {
|
||||
const res: AxiosResponse = await graphql
|
||||
.query("queryServiceLogs")
|
||||
.params(params);
|
||||
if (res.data.errors) {
|
||||
this.traceSpanLogs = [];
|
||||
this.traceSpanLogsTotal = 0;
|
||||
return res.data;
|
||||
}
|
||||
this.traceSpanLogs = res.data.data.queryLogs.logs || [];
|
||||
this.traceSpanLogsTotal = res.data.data.queryLogs.total;
|
||||
return res.data;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export function useTraceStore(): any {
|
||||
return traceStore(store);
|
||||
}
|
510
src/styles/grid.scss
Normal file
@ -0,0 +1,510 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
.show-xs,
|
||||
.show-sm,
|
||||
.show-md,
|
||||
.show-lg {
|
||||
display: none !important;
|
||||
}
|
||||
@media (max-width: 767px) {
|
||||
.show-xs {
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
.hide-xs {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
@media (min-width: 768px) and (max-width: 1023px) {
|
||||
.show-sm {
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
.hide-sm {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1024px) and (max-width: 1279px) {
|
||||
.show-md {
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
.hide-md {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1280px) {
|
||||
.show-lg {
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
.hide-lg {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
.g-xs-1,
|
||||
.g-xs-2,
|
||||
.g-xs-3,
|
||||
.g-xs-4,
|
||||
.g-xs-5,
|
||||
.g-xs-6,
|
||||
.g-xs-7,
|
||||
.g-xs-8,
|
||||
.g-xs-9,
|
||||
.g-xs-10,
|
||||
.g-xs-11,
|
||||
.g-xs-12 {
|
||||
float: left;
|
||||
min-height: 1px;
|
||||
}
|
||||
|
||||
.g-xs-12 {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.g-xs-11 {
|
||||
width: 91.666%;
|
||||
}
|
||||
|
||||
.g-xs-10 {
|
||||
width: 83.333%;
|
||||
}
|
||||
|
||||
.g-xs-9 {
|
||||
width: 75%;
|
||||
}
|
||||
|
||||
.g-xs-8 {
|
||||
width: 66.666%;
|
||||
}
|
||||
|
||||
.g-xs-7 {
|
||||
width: 58.333%;
|
||||
}
|
||||
|
||||
.g-xs-6 {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.g-xs-5 {
|
||||
width: 41.666%;
|
||||
}
|
||||
|
||||
.g-xs-4 {
|
||||
width: 33.333%;
|
||||
}
|
||||
|
||||
.g-xs-3 {
|
||||
width: 25%;
|
||||
}
|
||||
|
||||
.g-xs-2 {
|
||||
width: 16.666%;
|
||||
}
|
||||
|
||||
.g-xs-1 {
|
||||
width: 8.333%;
|
||||
}
|
||||
|
||||
.g-xs-space-12 {
|
||||
margin-left: 100%;
|
||||
}
|
||||
|
||||
.g-xs-space-11 {
|
||||
margin-left: 91.666%;
|
||||
}
|
||||
|
||||
.g-xs-space-10 {
|
||||
margin-left: 83.333%;
|
||||
}
|
||||
|
||||
.g-xs-space-9 {
|
||||
margin-left: 75%;
|
||||
}
|
||||
|
||||
.g-xs-space-8 {
|
||||
margin-left: 66.666%;
|
||||
}
|
||||
|
||||
.g-xs-space-7 {
|
||||
margin-left: 58.333%;
|
||||
}
|
||||
|
||||
.g-xs-space-6 {
|
||||
margin-left: 50%;
|
||||
}
|
||||
|
||||
.g-xs-space-5 {
|
||||
margin-left: 41.666%;
|
||||
}
|
||||
|
||||
.g-xs-space-4 {
|
||||
margin-left: 33.333%;
|
||||
}
|
||||
|
||||
.g-xs-space-3 {
|
||||
margin-left: 25%;
|
||||
}
|
||||
|
||||
.g-xs-space-2 {
|
||||
margin-left: 16.666%;
|
||||
}
|
||||
|
||||
.g-xs-space-1 {
|
||||
margin-left: 8.333%;
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.g-sm-1,
|
||||
.g-sm-2,
|
||||
.g-sm-3,
|
||||
.g-sm-4,
|
||||
.g-sm-5,
|
||||
.g-sm-6,
|
||||
.g-sm-7,
|
||||
.g-sm-8,
|
||||
.g-sm-9,
|
||||
.g-sm-10,
|
||||
.g-sm-11,
|
||||
.g-sm-12 {
|
||||
float: left;
|
||||
min-height: 1px;
|
||||
}
|
||||
|
||||
.g-sm-12 {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.g-sm-11 {
|
||||
width: 91.666%;
|
||||
}
|
||||
|
||||
.g-sm-10 {
|
||||
width: 83.333%;
|
||||
}
|
||||
|
||||
.g-sm-9 {
|
||||
width: 75%;
|
||||
}
|
||||
|
||||
.g-sm-8 {
|
||||
width: 66.666%;
|
||||
}
|
||||
|
||||
.g-sm-7 {
|
||||
width: 58.333%;
|
||||
}
|
||||
|
||||
.g-sm-6 {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.g-sm-5 {
|
||||
width: 41.666%;
|
||||
}
|
||||
|
||||
.g-sm-4 {
|
||||
width: 33.333%;
|
||||
}
|
||||
|
||||
.g-sm-3 {
|
||||
width: 25%;
|
||||
}
|
||||
|
||||
.g-sm-2 {
|
||||
width: 16.666%;
|
||||
}
|
||||
|
||||
.g-sm-1 {
|
||||
width: 8.333%;
|
||||
}
|
||||
|
||||
.g-sm-space-12 {
|
||||
margin-left: 100%;
|
||||
}
|
||||
|
||||
.g-sm-space-11 {
|
||||
margin-left: 91.666%;
|
||||
}
|
||||
|
||||
.g-sm-space-10 {
|
||||
margin-left: 83.333%;
|
||||
}
|
||||
|
||||
.g-sm-space-9 {
|
||||
margin-left: 75%;
|
||||
}
|
||||
|
||||
.g-sm-space-8 {
|
||||
margin-left: 66.666%;
|
||||
}
|
||||
|
||||
.g-sm-space-7 {
|
||||
margin-left: 58.333%;
|
||||
}
|
||||
|
||||
.g-sm-space-6 {
|
||||
margin-left: 50%;
|
||||
}
|
||||
|
||||
.g-sm-space-5 {
|
||||
margin-left: 41.666%;
|
||||
}
|
||||
|
||||
.g-sm-space-4 {
|
||||
margin-left: 33.333%;
|
||||
}
|
||||
|
||||
.g-sm-space-3 {
|
||||
margin-left: 25%;
|
||||
}
|
||||
|
||||
.g-sm-space-2 {
|
||||
margin-left: 16.666%;
|
||||
}
|
||||
|
||||
.g-sm-space-1 {
|
||||
margin-left: 8.333%;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1024px) {
|
||||
.g-md-1,
|
||||
.g-md-2,
|
||||
.g-md-3,
|
||||
.g-md-4,
|
||||
.g-md-5,
|
||||
.g-md-6,
|
||||
.g-md-7,
|
||||
.g-md-8,
|
||||
.g-md-9,
|
||||
.g-md-10,
|
||||
.g-md-11,
|
||||
.g-md-12 {
|
||||
float: left;
|
||||
min-height: 1px;
|
||||
}
|
||||
|
||||
.g-md-12 {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.g-md-11 {
|
||||
width: 91.666%;
|
||||
}
|
||||
|
||||
.g-md-10 {
|
||||
width: 83.333%;
|
||||
}
|
||||
|
||||
.g-md-9 {
|
||||
width: 75%;
|
||||
}
|
||||
|
||||
.g-md-8 {
|
||||
width: 66.666%;
|
||||
}
|
||||
|
||||
.g-md-7 {
|
||||
width: 58.333%;
|
||||
}
|
||||
|
||||
.g-md-6 {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.g-md-5 {
|
||||
width: 41.666%;
|
||||
}
|
||||
|
||||
.g-md-4 {
|
||||
width: 33.333%;
|
||||
}
|
||||
|
||||
.g-md-3 {
|
||||
width: 25%;
|
||||
}
|
||||
|
||||
.g-md-2 {
|
||||
width: 16.666%;
|
||||
}
|
||||
|
||||
.g-md-1 {
|
||||
width: 8.333%;
|
||||
}
|
||||
|
||||
.g-md-space-12 {
|
||||
margin-left: 100%;
|
||||
}
|
||||
|
||||
.g-md-space-11 {
|
||||
margin-left: 91.666%;
|
||||
}
|
||||
|
||||
.g-md-space-10 {
|
||||
margin-left: 83.333%;
|
||||
}
|
||||
|
||||
.g-md-space-9 {
|
||||
margin-left: 75%;
|
||||
}
|
||||
|
||||
.g-md-space-8 {
|
||||
margin-left: 66.666%;
|
||||
}
|
||||
|
||||
.g-md-space-7 {
|
||||
margin-left: 58.333%;
|
||||
}
|
||||
|
||||
.g-md-space-6 {
|
||||
margin-left: 50%;
|
||||
}
|
||||
|
||||
.g-md-space-5 {
|
||||
margin-left: 41.666%;
|
||||
}
|
||||
|
||||
.g-md-space-4 {
|
||||
margin-left: 33.333%;
|
||||
}
|
||||
|
||||
.g-md-space-3 {
|
||||
margin-left: 25%;
|
||||
}
|
||||
|
||||
.g-md-space-2 {
|
||||
margin-left: 16.666%;
|
||||
}
|
||||
|
||||
.g-md-space-1 {
|
||||
margin-left: 8.333%;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1280px) {
|
||||
.g-lg-1,
|
||||
.g-lg-2,
|
||||
.g-lg-3,
|
||||
.g-lg-4,
|
||||
.g-lg-5,
|
||||
.g-lg-6,
|
||||
.g-lg-7,
|
||||
.g-lg-8,
|
||||
.g-lg-9,
|
||||
.g-lg-10,
|
||||
.g-lg-11,
|
||||
.g-lg-12 {
|
||||
float: left;
|
||||
min-height: 1px;
|
||||
}
|
||||
|
||||
.g-lg-12 {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.g-lg-11 {
|
||||
width: 91.666%;
|
||||
}
|
||||
|
||||
.g-lg-10 {
|
||||
width: 83.333%;
|
||||
}
|
||||
|
||||
.g-lg-9 {
|
||||
width: 75%;
|
||||
}
|
||||
|
||||
.g-lg-8 {
|
||||
width: 66.666%;
|
||||
}
|
||||
|
||||
.g-lg-7 {
|
||||
width: 58.333%;
|
||||
}
|
||||
|
||||
.g-lg-6 {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.g-lg-5 {
|
||||
width: 41.666%;
|
||||
}
|
||||
|
||||
.g-lg-4 {
|
||||
width: 33.333%;
|
||||
}
|
||||
|
||||
.g-lg-3 {
|
||||
width: 25%;
|
||||
}
|
||||
|
||||
.g-lg-2 {
|
||||
width: 16.666%;
|
||||
}
|
||||
|
||||
.g-lg-1 {
|
||||
width: 8.333%;
|
||||
}
|
||||
|
||||
.g-lg-space-12 {
|
||||
margin-left: 100%;
|
||||
}
|
||||
|
||||
.g-lg-space-11 {
|
||||
margin-left: 91.666%;
|
||||
}
|
||||
|
||||
.g-lg-space-10 {
|
||||
margin-left: 83.333%;
|
||||
}
|
||||
|
||||
.g-lg-space-9 {
|
||||
margin-left: 75%;
|
||||
}
|
||||
|
||||
.g-lg-space-8 {
|
||||
margin-left: 66.666%;
|
||||
}
|
||||
|
||||
.g-lg-space-7 {
|
||||
margin-left: 58.333%;
|
||||
}
|
||||
|
||||
.g-lg-space-6 {
|
||||
margin-left: 50%;
|
||||
}
|
||||
|
||||
.g-lg-space-5 {
|
||||
margin-left: 41.666%;
|
||||
}
|
||||
|
||||
.g-lg-space-4 {
|
||||
margin-left: 33.333%;
|
||||
}
|
||||
|
||||
.g-lg-space-3 {
|
||||
margin-left: 25%;
|
||||
}
|
||||
|
||||
.g-lg-space-2 {
|
||||
margin-left: 16.666%;
|
||||
}
|
||||
|
||||
.g-lg-space-1 {
|
||||
margin-left: 8.333%;
|
||||
}
|
||||
}
|
19
src/styles/index.scss
Normal file
@ -0,0 +1,19 @@
|
||||
/**
|
||||
* 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 "./grid.scss";
|
||||
@import "./lib.scss";
|
||||
@import "./reset.scss";
|
@ -95,6 +95,10 @@
|
||||
background-color: #a7aebb;
|
||||
}
|
||||
|
||||
.mt-0 {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.mb-5 {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
18
src/types/selector.d.ts
vendored
@ -15,25 +15,25 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
export type Service = {
|
||||
id: string;
|
||||
id?: string;
|
||||
label: string;
|
||||
value: string;
|
||||
layers: string[];
|
||||
normal: boolean;
|
||||
group: string;
|
||||
layers?: string[];
|
||||
normal?: boolean;
|
||||
group?: string;
|
||||
};
|
||||
|
||||
export type Instance = {
|
||||
value: string;
|
||||
label: string;
|
||||
layer: string;
|
||||
language: string;
|
||||
instanceUUID: string;
|
||||
attributes: { name: string; value: string }[];
|
||||
layer?: string;
|
||||
language?: string;
|
||||
instanceUUID?: string;
|
||||
attributes?: { name: string; value: string }[];
|
||||
};
|
||||
|
||||
export type Endpoint = {
|
||||
id: string;
|
||||
id?: string;
|
||||
label: string;
|
||||
value: string;
|
||||
};
|
||||
|
79
src/types/trace.d.ts
vendored
Normal file
@ -0,0 +1,79 @@
|
||||
/**
|
||||
* 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 Trace {
|
||||
duration: number;
|
||||
isError: boolean;
|
||||
key: string;
|
||||
operationNames: string[];
|
||||
start: string;
|
||||
traceIds: Array<string | any>;
|
||||
}
|
||||
|
||||
export interface Span {
|
||||
endpointName: string;
|
||||
serviceCode: string;
|
||||
parentSpanId: number;
|
||||
segmentId: string;
|
||||
label?: string;
|
||||
layer: string;
|
||||
spanId: number;
|
||||
traceId: string;
|
||||
type: string;
|
||||
peer: string;
|
||||
component: string;
|
||||
isError: boolean;
|
||||
isBroken?: boolean;
|
||||
refs: Array<Ref>;
|
||||
startTime: number;
|
||||
endTime: number;
|
||||
dur?: number;
|
||||
children?: Span[];
|
||||
tags?: Array<Map<string, string>>;
|
||||
logs?: log[];
|
||||
}
|
||||
|
||||
export interface log {
|
||||
time: number;
|
||||
data: Map<string, string>;
|
||||
}
|
||||
|
||||
export interface Ref {
|
||||
traceId: string;
|
||||
parentSegmentId: string;
|
||||
parentSpanId: number;
|
||||
type: string;
|
||||
}
|
||||
|
||||
export interface StatisticsSpan {
|
||||
groupRef: StatisticsGroupRef;
|
||||
maxTime: number;
|
||||
minTime: number;
|
||||
sumTime: number;
|
||||
avgTime: number;
|
||||
count: number;
|
||||
}
|
||||
|
||||
export interface StatisticsGroupRef {
|
||||
endpointName: string;
|
||||
type: string;
|
||||
}
|
||||
|
||||
export class TraceTreeRef {
|
||||
segmentMap: Map<string, Span>;
|
||||
segmentIdGroup: string[];
|
||||
}
|
25
src/utils/formatJson.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.
|
||||
*/
|
||||
const censor = (key: any, value: any) => {
|
||||
if (typeof value === "function") {
|
||||
return Function.prototype.toString.call(value);
|
||||
}
|
||||
return value;
|
||||
};
|
||||
export const formatJson = (data: JSON) => {
|
||||
return JSON.stringify(data, censor, 2);
|
||||
};
|
138
src/views/components/ConditionTags.vue
Normal file
@ -0,0 +1,138 @@
|
||||
<!-- 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="flex-h" :class="{ light: theme === 'light' }">
|
||||
<div class="mr-10 pt-5">
|
||||
<span class="sm grey" v-show="theme === 'dark'">{{ t("tags") }}: </span>
|
||||
<span
|
||||
class="trace-tags"
|
||||
:style="type === 'LOG' ? `min-width: 122px;` : ''"
|
||||
>
|
||||
<span class="selected" v-for="(item, index) in tagsList" :key="index">
|
||||
<span>{{ item }}</span>
|
||||
<span class="remove-icon" @click="removeTags(index)">×</span>
|
||||
</span>
|
||||
</span>
|
||||
<el-input v-model="tags" class="trace-new-tag" @change="addLabels" />
|
||||
<span class="tags-tip">
|
||||
<a
|
||||
target="blank"
|
||||
href="https://github.com/apache/skywalking/blob/master/docs/en/setup/backend/configuration-vocabulary.md"
|
||||
>
|
||||
{{ t("tagsLink") }}
|
||||
</a>
|
||||
<el-tooltip :content="t('traceTagsTip')">
|
||||
<span>
|
||||
<Icon class="icon-help mr-5" iconName="help" size="middle" />
|
||||
</span>
|
||||
</el-tooltip>
|
||||
<b>{{ t("noticeTag") }}</b>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
/*global defineEmits, defineProps */
|
||||
const emit = defineEmits(["update"]);
|
||||
defineProps({
|
||||
type: { type: String, default: "TRACE" },
|
||||
});
|
||||
const { t } = useI18n();
|
||||
const theme = ref<string>("dark");
|
||||
const type = ref<string>("");
|
||||
const tags = ref<string>("");
|
||||
const tagsList = ref<string[]>([]);
|
||||
|
||||
function removeTags(index: number) {
|
||||
tagsList.value.splice(index, 1);
|
||||
updateTags();
|
||||
localStorage.setItem("traceTags", JSON.stringify(this.tagsList));
|
||||
}
|
||||
function addLabels() {
|
||||
if (!tags.value) {
|
||||
return;
|
||||
}
|
||||
tagsList.value.push(tags.value);
|
||||
tags.value = "";
|
||||
updateTags();
|
||||
}
|
||||
function updateTags() {
|
||||
const tagsMap = tagsList.value.map((item: string) => {
|
||||
const key = item.substring(0, item.indexOf("="));
|
||||
return {
|
||||
key,
|
||||
value: item.substring(item.indexOf("=") + 1, item.length),
|
||||
};
|
||||
});
|
||||
emit("update", { tagsMap, tagsList: tagsList.value });
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.trace-tags {
|
||||
padding: 1px 5px 0 0;
|
||||
border-radius: 3px;
|
||||
height: 24px;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.selected {
|
||||
display: inline-block;
|
||||
padding: 0 3px;
|
||||
border-radius: 3px;
|
||||
overflow: hidden;
|
||||
border: 1px dashed #aaa;
|
||||
font-size: 12px;
|
||||
margin: 3px 2px 0 2px;
|
||||
}
|
||||
|
||||
.trace-new-tag {
|
||||
border-style: unset;
|
||||
outline: 0;
|
||||
padding: 2px 5px;
|
||||
border-radius: 3px;
|
||||
width: 250px;
|
||||
margin-right: 3px;
|
||||
}
|
||||
|
||||
.remove-icon {
|
||||
display: inline-block;
|
||||
margin-left: 3px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.tags-tip {
|
||||
color: #a7aebb;
|
||||
}
|
||||
|
||||
.light {
|
||||
color: #3d444f;
|
||||
|
||||
input {
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
|
||||
.selected {
|
||||
color: #3d444f;
|
||||
}
|
||||
}
|
||||
|
||||
.icon-help {
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
136
src/views/components/LogTable/Index.vue
Normal file
@ -0,0 +1,136 @@
|
||||
<!-- 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="log">
|
||||
<div class="log-header">
|
||||
<template v-for="(item, index) in columns">
|
||||
<div
|
||||
class="method"
|
||||
:style="`width: ${item.method}px`"
|
||||
v-if="item.drag"
|
||||
:key="index"
|
||||
>
|
||||
<span class="r cp" ref="dragger" :data-index="index">
|
||||
<Icon iconName="settings_ethernet" size="sm" />
|
||||
</span>
|
||||
{{ t(item.value) }}
|
||||
</div>
|
||||
<div v-else :class="item.label" :key="`col${index}`">
|
||||
{{ t(item.value) }}
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div v-if="type === 'browser'">
|
||||
<LogBrowser
|
||||
v-for="(item, index) in tableData"
|
||||
:data="item"
|
||||
:key="'browser' + index"
|
||||
@select="setCurrentLog"
|
||||
/>
|
||||
</div>
|
||||
<div v-else>
|
||||
<LogService
|
||||
v-for="(item, index) in tableData"
|
||||
:data="item"
|
||||
:key="'service' + index"
|
||||
:noLink="noLink"
|
||||
@select="setCurrentLog"
|
||||
/>
|
||||
</div>
|
||||
<slot></slot>
|
||||
<el-dialog
|
||||
v-model="showDetail"
|
||||
:destroy-on-close="true"
|
||||
fullscreen
|
||||
@closed="showDetail = false"
|
||||
:title="t('logDetail')"
|
||||
>
|
||||
<LogDetail :currentLog="currentLog" />
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { ServiceLogConstants, BrowserLogConstants } from "./data";
|
||||
import LogBrowser from "./LogBrowser.vue";
|
||||
import LogService from "./LogService.vue";
|
||||
import LogDetail from "./LogDetail.vue";
|
||||
|
||||
/*global defineProps, Nullable */
|
||||
const props = defineProps({
|
||||
type: { type: String, default: "service" },
|
||||
tableData: { type: Array, default: () => [] },
|
||||
noLink: { type: Boolean, default: true },
|
||||
});
|
||||
const { t } = useI18n();
|
||||
const currentLog = ref<any>({});
|
||||
const showDetail = ref<boolean>(false);
|
||||
const dragger = ref<Nullable<HTMLSpanElement>>(null);
|
||||
// const method = ref<number>(380);
|
||||
const columns: any[] =
|
||||
props.type === "browser" ? BrowserLogConstants : ServiceLogConstants;
|
||||
|
||||
function setCurrentLog(log: any) {
|
||||
showDetail.value = true;
|
||||
currentLog.value = log;
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.log {
|
||||
font-size: 12px;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.log-header {
|
||||
/*display: flex;*/
|
||||
white-space: nowrap;
|
||||
user-select: none;
|
||||
border-left: 0;
|
||||
border-right: 0;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
|
||||
/*background-color: #f3f4f9;*/
|
||||
.traceId {
|
||||
width: 390px;
|
||||
}
|
||||
|
||||
.content,
|
||||
.tags {
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
.serviceInstanceName,
|
||||
.serviceName {
|
||||
width: 200px;
|
||||
}
|
||||
}
|
||||
|
||||
.log-header div {
|
||||
/*min-width: 140px;*/
|
||||
width: 140px;
|
||||
/*flex-grow: 1;*/
|
||||
display: inline-block;
|
||||
padding: 0 4px;
|
||||
border: 1px solid transparent;
|
||||
border-right: 1px dotted silver;
|
||||
line-height: 30px;
|
||||
background-color: #f3f4f9;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
107
src/views/components/LogTable/LogBrowser.vue
Normal file
@ -0,0 +1,107 @@
|
||||
<!-- 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 @click="showSelectSpan" :class="['log-item', 'clearfix']" ref="logItem">
|
||||
<div
|
||||
v-for="(item, index) in columns"
|
||||
:key="index"
|
||||
:class="[
|
||||
'method',
|
||||
['message', 'stack'].includes(item.label) ? 'autoHeight' : '',
|
||||
]"
|
||||
:style="{
|
||||
lineHeight: 1.3,
|
||||
width: `${item.drag ? item.method : ''}px`,
|
||||
}"
|
||||
>
|
||||
<span v-if="item.label === 'time'">{{ dateFormat(data.time) }}</span>
|
||||
<span v-else-if="item.label === 'errorUrl'">{{ data.pagePath }}</span>
|
||||
<span v-else v-tooltip:bottom="data[item.label] || '-'">{{
|
||||
data[item.label] || "-"
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import dayjs from "dayjs";
|
||||
import { BrowserLogConstants } from "./data";
|
||||
|
||||
/*global defineProps, defineEmits, NodeListOf */
|
||||
const props = defineProps({
|
||||
data: { type: Array as any, default: () => [] },
|
||||
});
|
||||
const columns = BrowserLogConstants;
|
||||
const emit = defineEmits(["select"]);
|
||||
|
||||
const dateFormat = (date: number, pattern = "YYYY-MM-DD HH:mm:ss") =>
|
||||
dayjs(date).format(pattern);
|
||||
|
||||
function showSelectSpan() {
|
||||
const items: NodeListOf<any> = document.querySelectorAll(".log-item");
|
||||
|
||||
for (const item of items) {
|
||||
item.style.background = "#fff";
|
||||
}
|
||||
const logItem: any = this.$refs.logItem;
|
||||
|
||||
logItem.style.background = "rgba(0, 0, 0, 0.1)";
|
||||
emit("select", props.data);
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.log-item {
|
||||
white-space: nowrap;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.log-item.selected {
|
||||
background: rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.log-item:not(.level0):hover {
|
||||
background: rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.log-item:hover {
|
||||
background: rgba(0, 0, 0, 0.04) !important;
|
||||
}
|
||||
|
||||
.log-item > div {
|
||||
width: 140px;
|
||||
padding: 0 5px;
|
||||
display: inline-block;
|
||||
border: 1px solid transparent;
|
||||
border-right: 1px dotted silver;
|
||||
overflow: hidden;
|
||||
line-height: 30px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.log-item .text {
|
||||
width: 100% !important;
|
||||
display: inline-block;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.log-item > div.method {
|
||||
padding: 7px 5px;
|
||||
line-height: 30px;
|
||||
}
|
||||
</style>
|
75
src/views/components/LogTable/LogDetail.vue
Normal file
@ -0,0 +1,75 @@
|
||||
<!-- 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="log-detail">
|
||||
<div
|
||||
class="mb-10 clear rk-flex"
|
||||
v-for="(item, index) in columns"
|
||||
:key="index"
|
||||
>
|
||||
<template>
|
||||
<span class="g-sm-4 grey">{{ t(item.value) }}:</span>
|
||||
<span v-if="item.label === 'timestamp'" class="g-sm-8">
|
||||
{{ dateFormat(currentLog[item.label]) }}
|
||||
</span>
|
||||
<textarea
|
||||
class="content"
|
||||
:readonly="true"
|
||||
v-else-if="item.label === 'content'"
|
||||
:value="currentLog[item.label]"
|
||||
/>
|
||||
<span v-else-if="item.label === 'tags'" class="g-sm-8">
|
||||
<div v-for="(d, index) in logTags" :key="index">{{ d }}</div>
|
||||
</span>
|
||||
<span v-else class="g-sm-8">{{ currentLog[item.label] }}</span>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { computed } from "vue";
|
||||
import type { PropType } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import dayjs from "dayjs";
|
||||
import { ServiceLogDetail } from "@/views/components/LogTable/data";
|
||||
|
||||
/*global defineProps */
|
||||
const props = defineProps({
|
||||
currentLog: { type: Object as PropType<any>, default: () => ({}) },
|
||||
});
|
||||
const { t } = useI18n();
|
||||
const columns = ServiceLogDetail;
|
||||
const logTags = computed(() => {
|
||||
if (!props.currentLog.tags) {
|
||||
return [];
|
||||
}
|
||||
return props.currentLog.tags.map((d: { key: string; value: string }) => {
|
||||
return `${d.key} = ${d.value}`;
|
||||
});
|
||||
});
|
||||
const dateFormat = (date: number, pattern = "YYYY-MM-DD HH:mm:ss") =>
|
||||
dayjs(date).format(pattern);
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.content {
|
||||
max-width: 700px;
|
||||
min-width: 500px;
|
||||
min-height: 500px;
|
||||
border: none;
|
||||
outline: none;
|
||||
color: #3d444f;
|
||||
overflow: auto;
|
||||
}
|
||||
</style>
|
115
src/views/components/LogTable/LogService.vue
Normal file
@ -0,0 +1,115 @@
|
||||
<!-- 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 @click="showSelectSpan" class="log-item">
|
||||
<div v-for="(item, index) in columns" :key="index" :class="item.label">
|
||||
<span v-if="item.label === 'timestamp'">
|
||||
{{ dateFormat(data.timestamp) }}
|
||||
</span>
|
||||
<span v-else-if="item.label === 'tags'">
|
||||
{{ tags }}
|
||||
</span>
|
||||
<router-link
|
||||
v-else-if="item.label === 'traceId' && !noLink"
|
||||
:to="{ name: 'trace', query: { traceid: data[item.label] } }"
|
||||
>
|
||||
<span>{{ data[item.label] }}</span>
|
||||
</router-link>
|
||||
<span v-else>{{ data[item.label] }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { computed } from "vue";
|
||||
import dayjs from "dayjs";
|
||||
import { ServiceLogConstants } from "./data";
|
||||
/*global defineProps, defineEmits */
|
||||
const props = defineProps({
|
||||
data: { type: Array as any, default: () => [] },
|
||||
noLink: { type: Boolean, default: true },
|
||||
});
|
||||
const emit = defineEmits(["select"]);
|
||||
const columns = ServiceLogConstants;
|
||||
const tags = computed(() => {
|
||||
if (!props.data.tags) {
|
||||
return "";
|
||||
}
|
||||
return String(props.data.tags.map((d: any) => `${d.key}=${d.value}`));
|
||||
});
|
||||
const dateFormat = (date: number, pattern = "YYYY-MM-DD HH:mm:ss") =>
|
||||
dayjs(date).format(pattern);
|
||||
function showSelectSpan() {
|
||||
emit("select", props.data);
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.log-item {
|
||||
white-space: nowrap;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
|
||||
.traceId {
|
||||
width: 390px;
|
||||
color: #448dfe;
|
||||
cursor: pointer;
|
||||
|
||||
span {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
line-height: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
.content,
|
||||
.tags {
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
.serviceInstanceName,
|
||||
.serviceName {
|
||||
width: 200px;
|
||||
}
|
||||
}
|
||||
|
||||
.log-item:hover {
|
||||
background: rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.log-item > div {
|
||||
width: 140px;
|
||||
padding: 0 5px;
|
||||
display: inline-block;
|
||||
border: 1px solid transparent;
|
||||
border-right: 1px dotted silver;
|
||||
overflow: hidden;
|
||||
line-height: 30px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.log-item .text {
|
||||
width: 100%;
|
||||
display: inline-block;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.log-item > div.method {
|
||||
height: 100%;
|
||||
padding: 3px 8px;
|
||||
}
|
||||
</style>
|
120
src/views/components/LogTable/data.ts
Normal file
@ -0,0 +1,120 @@
|
||||
/**
|
||||
* 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 ServiceLogConstants = [
|
||||
{
|
||||
label: "serviceName",
|
||||
value: "service",
|
||||
},
|
||||
{
|
||||
label: "serviceInstanceName",
|
||||
value: "instance",
|
||||
},
|
||||
{
|
||||
label: "timestamp",
|
||||
value: "time",
|
||||
},
|
||||
{
|
||||
label: "contentType",
|
||||
value: "contentType",
|
||||
},
|
||||
{
|
||||
label: "tags",
|
||||
value: "tags",
|
||||
},
|
||||
{
|
||||
label: "content",
|
||||
value: "content",
|
||||
},
|
||||
{
|
||||
label: "traceId",
|
||||
value: "traceID",
|
||||
},
|
||||
];
|
||||
export const ServiceLogDetail = [
|
||||
{
|
||||
label: "serviceName",
|
||||
value: "currentService",
|
||||
},
|
||||
{
|
||||
label: "serviceInstanceName",
|
||||
value: "currentInstance",
|
||||
},
|
||||
{
|
||||
label: "timestamp",
|
||||
value: "time",
|
||||
},
|
||||
{
|
||||
label: "contentType",
|
||||
value: "contentType",
|
||||
},
|
||||
{
|
||||
label: "traceId",
|
||||
value: "traceID",
|
||||
},
|
||||
{
|
||||
label: "tags",
|
||||
value: "tags",
|
||||
},
|
||||
{
|
||||
label: "content",
|
||||
value: "content",
|
||||
},
|
||||
];
|
||||
// The order of columns should be time, service, error, stack, version, url, catalog, and grade.
|
||||
export const BrowserLogConstants = [
|
||||
{
|
||||
label: "service",
|
||||
value: "service",
|
||||
},
|
||||
{
|
||||
label: "serviceVersion",
|
||||
value: "serviceVersion",
|
||||
},
|
||||
{
|
||||
label: "errorUrl",
|
||||
value: "errorPage",
|
||||
},
|
||||
{
|
||||
label: "time",
|
||||
value: "time",
|
||||
},
|
||||
{
|
||||
label: "message",
|
||||
value: "message",
|
||||
drag: true,
|
||||
method: 350,
|
||||
},
|
||||
{
|
||||
label: "stack",
|
||||
value: "stack",
|
||||
drag: true,
|
||||
method: 350,
|
||||
},
|
||||
// {
|
||||
// label: 'pagePath',
|
||||
// value: 'Page Path',
|
||||
// },
|
||||
{
|
||||
label: "category",
|
||||
value: "category",
|
||||
},
|
||||
{
|
||||
label: "grade",
|
||||
value: "grade",
|
||||
},
|
||||
];
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License. -->
|
||||
<template>
|
||||
<WidgetOptions />
|
||||
<TopologyOptions />
|
||||
<StyleOptions />
|
||||
<div class="footer">
|
||||
<el-button size="small">
|
||||
{{ t("cancel") }}
|
||||
@ -26,8 +26,8 @@ limitations under the License. -->
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { useI18n } from "vue-i18n";
|
||||
import WidgetOptions from "./WidgetOptions.vue";
|
||||
import TopologyOptions from "./graph-styles/TopologyItem.vue";
|
||||
import WidgetOptions from "./components/WidgetOptions.vue";
|
||||
import StyleOptions from "./topology/StyleOptions.vue";
|
||||
import { useDashboardStore } from "@/store/modules/dashboard";
|
||||
|
||||
const { t } = useI18n();
|
||||
|
@ -76,10 +76,10 @@ import { useDashboardStore } from "@/store/modules/dashboard";
|
||||
import { useAppStoreWithOut } from "@/store/modules/app";
|
||||
import { Option } from "@/types/app";
|
||||
import graphs from "../graphs";
|
||||
import configs from "./graph-styles";
|
||||
import WidgetOptions from "./WidgetOptions.vue";
|
||||
import StandardOptions from "./StandardOptions.vue";
|
||||
import MetricOptions from "./MetricOptions.vue";
|
||||
import configs from "./widget/graph-styles";
|
||||
import WidgetOptions from "./components/WidgetOptions.vue";
|
||||
import StandardOptions from "./widget/StandardOptions.vue";
|
||||
import MetricOptions from "./widget/MetricOptions.vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: "ConfigEdit",
|
||||
|
@ -95,7 +95,7 @@ import {
|
||||
ChartTypes,
|
||||
PodsChartTypes,
|
||||
TableEntity,
|
||||
} from "../data";
|
||||
} from "../../data";
|
||||
import { ElMessage } from "element-plus";
|
||||
import Icon from "@/components/Icon.vue";
|
||||
import { useQueryProcessor, useSourceProcessor } from "@/hooks/useProcessor";
|
@ -118,7 +118,7 @@ limitations under the License. -->
|
||||
<script lang="ts" setup>
|
||||
import { reactive } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { SortOrder } from "../data";
|
||||
import { SortOrder } from "../../data";
|
||||
import { useDashboardStore } from "@/store/modules/dashboard";
|
||||
|
||||
const dashboardStore = useDashboardStore();
|
95
src/views/dashboard/controls/Trace.vue
Normal file
@ -0,0 +1,95 @@
|
||||
<!-- 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="trace-wrapper flex-v">
|
||||
<el-popover placement="bottom" trigger="click" :width="100">
|
||||
<template #reference>
|
||||
<span class="delete cp">
|
||||
<Icon iconName="ellipsis_v" size="middle" class="operation" />
|
||||
</span>
|
||||
</template>
|
||||
<div class="tools">
|
||||
<span @click="removeWidget">{{ t("delete") }}</span>
|
||||
</div>
|
||||
</el-popover>
|
||||
<div class="header">
|
||||
<Filter />
|
||||
</div>
|
||||
<div class="trace flex-h">
|
||||
<TraceList />
|
||||
<TraceDetail />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import type { PropType } from "vue";
|
||||
import Filter from "../related/trace/Filter.vue";
|
||||
import TraceList from "../related/trace/TraceList.vue";
|
||||
import TraceDetail from "../related/trace/Detail.vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useDashboardStore } from "@/store/modules/dashboard";
|
||||
|
||||
/*global defineProps */
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Object as PropType<any>,
|
||||
default: () => ({ graph: {} }),
|
||||
},
|
||||
activeIndex: { type: String, default: "" },
|
||||
});
|
||||
const { t } = useI18n();
|
||||
const dashboardStore = useDashboardStore();
|
||||
function removeWidget() {
|
||||
dashboardStore.removeControls(props.data);
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.trace-wrapper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
font-size: 12px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.delete {
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
right: 3px;
|
||||
}
|
||||
|
||||
.header {
|
||||
padding: 10px;
|
||||
font-size: 12px;
|
||||
border-bottom: 1px solid #dcdfe6;
|
||||
}
|
||||
|
||||
.tools {
|
||||
padding: 5px 0;
|
||||
color: #999;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
|
||||
&:hover {
|
||||
color: #409eff;
|
||||
background-color: #eee;
|
||||
}
|
||||
}
|
||||
|
||||
.trace {
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
</style>
|
22
src/views/dashboard/controls/index.ts
Normal file
@ -0,0 +1,22 @@
|
||||
/**
|
||||
* 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 Topology from "./Topology.vue";
|
||||
import Tab from "./Tab.vue";
|
||||
import Widget from "./Widget.vue";
|
||||
import Trace from "./Trace.vue";
|
||||
|
||||
export default { Tab, Widget, Trace, Topology };
|
@ -165,11 +165,11 @@ export const SortOrder = [
|
||||
export const ToolIcons = [
|
||||
{ name: "playlist_add", content: "Add Widget", id: "addWidget" },
|
||||
{ name: "all_inbox", content: "Add Tab", id: "addTab" },
|
||||
// { name: "insert_image", content: "Add Image", id: "addImage" },
|
||||
{ name: "device_hub", content: "Add Topology", id: "topology" },
|
||||
{ name: "merge", content: "Add Trace", id: "trace" },
|
||||
// { name: "save_alt", content: "Export", id: "export" },
|
||||
// { name: "folder_open", content: "Import", id: "import" },
|
||||
// { name: "settings", content: "Settings", id: "settings" },
|
||||
{ name: "device_hub", content: "Add Topology", id: "topology" },
|
||||
// { name: "save", content: "Apply", id: "apply" },
|
||||
];
|
||||
export const ScopeType = [
|
||||
@ -203,3 +203,13 @@ export const Colors: any = {
|
||||
black: "#000",
|
||||
orange: "#E6A23C",
|
||||
};
|
||||
export const Status = [
|
||||
{ label: "All", value: "ALL" },
|
||||
{ label: "Success", value: "SUCCESS" },
|
||||
{ label: "Error", value: "ERROR" },
|
||||
];
|
||||
export const QueryOrders = [
|
||||
{ label: "startTime", value: "BY_START_TIME" },
|
||||
{ label: "duration", value: "BY_DURATION" },
|
||||
];
|
||||
export const TraceEntitys = ["All", "Service", "ServiceInstance", "Endpoint"];
|
||||
|
@ -31,6 +31,7 @@ limitations under the License. -->
|
||||
:key="item.i"
|
||||
@click="clickGrid(item)"
|
||||
:class="{ active: dashboardStore.activedGridItem === item.i }"
|
||||
drag-ignore-from="svg.d3-trace-tree"
|
||||
>
|
||||
<component :is="item.type" :data="item" />
|
||||
</grid-item>
|
||||
@ -40,13 +41,11 @@ limitations under the License. -->
|
||||
import { defineComponent } from "vue";
|
||||
import { useDashboardStore } from "@/store/modules/dashboard";
|
||||
import { LayoutConfig } from "@/types/dashboard";
|
||||
import Widget from "../controls/Widget.vue";
|
||||
import Tab from "../controls/Tab.vue";
|
||||
import Topology from "../controls/Topology.vue";
|
||||
import controls from "../controls/index";
|
||||
|
||||
export default defineComponent({
|
||||
name: "Layout",
|
||||
components: { Widget, Tab, Topology },
|
||||
components: { ...controls },
|
||||
setup() {
|
||||
const dashboardStore = useDashboardStore();
|
||||
function layoutUpdatedEvent(newLayout: LayoutConfig[]) {
|
||||
|
@ -84,8 +84,10 @@ limitations under the License. -->
|
||||
size="sm"
|
||||
:iconName="t.name"
|
||||
v-if="
|
||||
t.id !== 'topology' ||
|
||||
(t.id === 'topology' && hasTopology.includes(dashboardStore.entity))
|
||||
!['topology', 'trace'].includes(t.id) ||
|
||||
(t.id === 'topology' &&
|
||||
hasTopology.includes(dashboardStore.entity)) ||
|
||||
(t.id === 'trace' && TraceEntitys.includes(dashboardStore.entity))
|
||||
"
|
||||
/>
|
||||
</span>
|
||||
@ -98,7 +100,7 @@ import { reactive, watch } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
import { useDashboardStore } from "@/store/modules/dashboard";
|
||||
import { useAppStoreWithOut } from "@/store/modules/app";
|
||||
import { EntityType, ToolIcons, hasTopology } from "../data";
|
||||
import { EntityType, ToolIcons, hasTopology, TraceEntitys } from "../data";
|
||||
import { useSelectorStore } from "@/store/modules/selectors";
|
||||
import { ElMessage } from "element-plus";
|
||||
import { Option } from "@/types/app";
|
||||
@ -290,8 +292,8 @@ function clickIcons(t: { id: string; content: string; name: string }) {
|
||||
case "addTab":
|
||||
dashboardStore.addControl("Tab");
|
||||
break;
|
||||
case "addImage":
|
||||
dashboardStore.addControl("Image");
|
||||
case "trace":
|
||||
dashboardStore.addControl("Trace");
|
||||
break;
|
||||
case "topology":
|
||||
dashboardStore.addControl("Topology");
|
||||
@ -351,7 +353,6 @@ async function fetchPods(type: string, serviceId: string, setPod: boolean) {
|
||||
}
|
||||
if (resp.errors) {
|
||||
ElMessage.error(resp.errors);
|
||||
return;
|
||||
}
|
||||
}
|
||||
watch(
|
||||
|
279
src/views/dashboard/related/trace/Detail.vue
Normal file
@ -0,0 +1,279 @@
|
||||
<!-- 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="trace-detail" v-loading="loading">
|
||||
<div
|
||||
class="trace-detail-wrapper clear"
|
||||
v-if="traceStore.currentTrace.endpointNames"
|
||||
>
|
||||
<h5 class="mb-5 mt-0">
|
||||
<Icon
|
||||
icon="clear"
|
||||
v-if="traceStore.currentTrace.isError"
|
||||
class="red mr-5 sm"
|
||||
/>
|
||||
<span class="vm">{{ traceStore.currentTrace.endpointNames[0] }}</span>
|
||||
<div class="trace-log-btn">
|
||||
<el-button class="mr-10" type="primary" @click="searchTraceLogs">
|
||||
{{ t("viewLogs") }}
|
||||
</el-button>
|
||||
</div>
|
||||
<el-dialog
|
||||
v-model="showTraceLogs"
|
||||
:destroy-on-close="true"
|
||||
fullscreen
|
||||
@closed="showTraceLogs = false"
|
||||
>
|
||||
<div>
|
||||
<el-pagination
|
||||
v-model:currentPage="pageNum"
|
||||
v-model:page-size="pageSize"
|
||||
:small="true"
|
||||
:total="traceStore.traceSpanLogsTotal"
|
||||
@current-change="turnLogsPage"
|
||||
/>
|
||||
<LogTable
|
||||
:tableData="traceStore.traceSpanLogs || []"
|
||||
:type="`service`"
|
||||
:noLink="true"
|
||||
>
|
||||
<div class="log-tips" v-if="!traceStore.traceSpanLogs.length">
|
||||
{{ t("noData") }}
|
||||
</div>
|
||||
</LogTable>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</h5>
|
||||
<div class="mb-5 blue sm">
|
||||
<Selector
|
||||
size="small"
|
||||
:value="
|
||||
traceStore.currentTrace.traceIds &&
|
||||
traceStore.currentTrace.traceIds[0] &&
|
||||
traceStore.currentTrace.traceIds[0].value
|
||||
"
|
||||
:options="traceStore.currentTrace.traceIds"
|
||||
@change="changeTraceId"
|
||||
class="trace-detail-ids"
|
||||
/>
|
||||
<Icon
|
||||
size="sm"
|
||||
class="icon grey link-hover cp ml-5"
|
||||
iconName="review-list"
|
||||
@click="handleClick"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex-h item">
|
||||
<div>
|
||||
<div class="tag mr-5">{{ t("start") }}</div>
|
||||
<span class="mr-15 sm">
|
||||
{{ dateFormat(parseInt(traceStore.currentTrace.start)) }}
|
||||
</span>
|
||||
<div class="tag mr-5">{{ t("duration") }}</div>
|
||||
<span class="mr-15 sm"
|
||||
>{{ traceStore.currentTrace.duration }} ms</span
|
||||
>
|
||||
<div class="tag mr-5">{{ t("spans") }}</div>
|
||||
<span class="sm">{{ traceStore.traceSpans.length }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<el-button
|
||||
class="grey"
|
||||
:class="{ ghost: displayMode !== 'List' }"
|
||||
@click="displayMode = 'List'"
|
||||
>
|
||||
<Icon class="mr-5" size="sm" iconName="list-bulleted" />
|
||||
{{ t("list") }}
|
||||
</el-button>
|
||||
<el-button
|
||||
class="grey"
|
||||
:class="{ ghost: displayMode !== 'Tree' }"
|
||||
@click="displayMode = 'Tree'"
|
||||
>
|
||||
<Icon class="mr-5" size="sm" iconName="issue-child" />
|
||||
{{ t("tree") }}
|
||||
</el-button>
|
||||
<el-button
|
||||
class="grey"
|
||||
:class="{ ghost: displayMode !== 'Table' }"
|
||||
@click="displayMode = 'Table'"
|
||||
>
|
||||
<Icon class="mr-5" size="sm" iconName="table" />
|
||||
{{ t("table") }}
|
||||
</el-button>
|
||||
<el-button
|
||||
class="grey"
|
||||
:class="{ ghost: displayMode !== 'Statistics' }"
|
||||
@click="displayMode = 'Statistics'"
|
||||
>
|
||||
<Icon class="mr-5" size="sm" iconName="statistics-bulleted" />
|
||||
{{ t("statistics") }}
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="no-data" v-else>{{ t("noData") }}</div>
|
||||
<component
|
||||
v-if="traceStore.currentTrace.endpointNames"
|
||||
:is="displayMode"
|
||||
:data="traceStore.traceSpans"
|
||||
:traceId="traceStore.currentTrace.traceIds[0].value"
|
||||
:showBtnDetail="false"
|
||||
HeaderType="trace"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import dayjs from "dayjs";
|
||||
import { ref, defineComponent } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useTraceStore } from "@/store/modules/trace";
|
||||
import { Option } from "@/types/app";
|
||||
import copy from "@/utils/copy";
|
||||
import List from "./components/List.vue";
|
||||
import graphs from "./components/index";
|
||||
import LogTable from "@/views/components/LogTable/Index.vue";
|
||||
import { ElMessage } from "element-plus";
|
||||
|
||||
export default defineComponent({
|
||||
name: "TraceDetail",
|
||||
components: {
|
||||
...graphs,
|
||||
List,
|
||||
LogTable,
|
||||
},
|
||||
setup() {
|
||||
const { t } = useI18n();
|
||||
const traceStore = useTraceStore();
|
||||
const loading = ref<boolean>(false);
|
||||
const traceId = ref<string>("");
|
||||
const displayMode = ref<string>("List");
|
||||
const pageNum = ref<number>(1);
|
||||
const pageSize = 10;
|
||||
const dateFormat = (date: number, pattern = "YYYY-MM-DD HH:mm:ss") =>
|
||||
dayjs(date).format(pattern);
|
||||
const showTraceLogs = ref<boolean>(false);
|
||||
|
||||
function handleClick(ids: string[]) {
|
||||
let copyValue = null;
|
||||
if (ids.length === 1) {
|
||||
copyValue = ids[0];
|
||||
} else {
|
||||
copyValue = ids.join(",");
|
||||
}
|
||||
copy(copyValue);
|
||||
}
|
||||
|
||||
async function changeTraceId(opt: Option[]) {
|
||||
traceId.value = opt[0].value;
|
||||
loading.value = true;
|
||||
const res = await traceStore.getTraceSpans({ traceId: opt[0].value });
|
||||
if (res.errors) {
|
||||
ElMessage.error(res.errors);
|
||||
}
|
||||
loading.value = false;
|
||||
}
|
||||
|
||||
async function searchTraceLogs() {
|
||||
showTraceLogs.value = true;
|
||||
const res = await traceStore.getSpanLogs({
|
||||
condition: {
|
||||
relatedTrace: {
|
||||
traceId: traceId.value || traceStore.currentTrace.traceIds[0],
|
||||
},
|
||||
paging: { pageNum: pageNum.value, pageSize, needTotal: true },
|
||||
},
|
||||
});
|
||||
if (res.errors) {
|
||||
ElMessage.error(res.errors);
|
||||
}
|
||||
}
|
||||
|
||||
function turnLogsPage(page: number) {
|
||||
pageNum.value = page;
|
||||
searchTraceLogs();
|
||||
}
|
||||
return {
|
||||
traceStore,
|
||||
displayMode,
|
||||
dateFormat,
|
||||
changeTraceId,
|
||||
handleClick,
|
||||
t,
|
||||
searchTraceLogs,
|
||||
showTraceLogs,
|
||||
turnLogsPage,
|
||||
pageSize,
|
||||
pageNum,
|
||||
loading,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.trace-detail {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.trace-detail-wrapper {
|
||||
font-size: 12px;
|
||||
padding: 5px 10px;
|
||||
border-bottom: 1px solid #eee;
|
||||
width: 100%;
|
||||
height: 95px;
|
||||
|
||||
.grey {
|
||||
color: #fff;
|
||||
background-color: #448dfe;
|
||||
}
|
||||
|
||||
.ghost {
|
||||
cursor: pointer;
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
}
|
||||
|
||||
.item {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.trace-detail-ids {
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
outline: 0;
|
||||
border-style: unset;
|
||||
color: inherit;
|
||||
border: 1px solid;
|
||||
border-radius: 4px;
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
.trace-log-btn {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.tag {
|
||||
display: inline-block;
|
||||
border-radius: 4px;
|
||||
padding: 0px 7px;
|
||||
background-color: #40454e;
|
||||
color: #eee;
|
||||
}
|
||||
|
||||
.no-data {
|
||||
padding-top: 50px;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
239
src/views/dashboard/related/trace/Filter.vue
Normal file
@ -0,0 +1,239 @@
|
||||
<!-- 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="flex-h row">
|
||||
<div class="mr-5" v-if="dashboardStore.entity === EntityType[1].value">
|
||||
<span class="grey mr-5">{{ t("service") }}:</span>
|
||||
<Selector
|
||||
size="small"
|
||||
:value="state.service.value"
|
||||
:options="traceStore.services"
|
||||
placeholder="Select a service"
|
||||
@change="changeField('service', $event)"
|
||||
/>
|
||||
</div>
|
||||
<div class="mr-5" v-if="dashboardStore.entity !== EntityType[3].value">
|
||||
<span class="grey mr-5">{{ t("instance") }}:</span>
|
||||
<Selector
|
||||
size="small"
|
||||
:value="state.instance.value"
|
||||
:options="traceStore.instances"
|
||||
placeholder="Select a instance"
|
||||
@change="changeField('instance', $event)"
|
||||
/>
|
||||
</div>
|
||||
<div class="mr-5" v-if="dashboardStore.entity !== EntityType[2].value">
|
||||
<span class="grey mr-5">{{ t("endpoint") }}:</span>
|
||||
<Selector
|
||||
size="small"
|
||||
:value="state.endpoint.value"
|
||||
:options="traceStore.endpoints"
|
||||
placeholder="Select a endpoint"
|
||||
@change="changeField('endpoint', $event)"
|
||||
/>
|
||||
</div>
|
||||
<div class="mr-5">
|
||||
<span class="grey mr-5">{{ t("status") }}:</span>
|
||||
<Selector
|
||||
size="small"
|
||||
:value="state.status.value"
|
||||
:options="Status"
|
||||
placeholder="Select a status"
|
||||
@change="changeField('status', $event)"
|
||||
/>
|
||||
</div>
|
||||
<div class="mr-5">
|
||||
<span class="grey mr-5">{{ t("traceID") }}:</span>
|
||||
<el-input v-model="traceId" class="traceId" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-h">
|
||||
<!-- <div class="mr-5">
|
||||
<span class="grey mr-5">{{ t("timeRange") }}:</span>
|
||||
<TimePicker
|
||||
:value="dateTime"
|
||||
position="bottom"
|
||||
format="YYYY-MM-DD HH:mm"
|
||||
@input="changeTimeRange"
|
||||
/>
|
||||
</div> -->
|
||||
<div class="mr-5">
|
||||
<span class="sm b grey mr-5">{{ t("duration") }}:</span>
|
||||
<el-input class="inputs mr-5" v-model="minTraceDuration" />
|
||||
<span class="grey mr-5">-</span>
|
||||
<el-input class="inputs" v-model="maxTraceDuration" />
|
||||
</div>
|
||||
<ConditionTags :type="'TRACE'" @update="updateTags" />
|
||||
<el-button
|
||||
class="search-btn"
|
||||
size="small"
|
||||
type="primary"
|
||||
@click="searchTraces"
|
||||
>
|
||||
{{ t("search") }}
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, watch } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { Option } from "@/types/app";
|
||||
import { Status } from "../../data";
|
||||
import { useTraceStore } from "@/store/modules/trace";
|
||||
import { useDashboardStore } from "@/store/modules/dashboard";
|
||||
import { useAppStoreWithOut } from "@/store/modules/app";
|
||||
import { useSelectorStore } from "@/store/modules/selectors";
|
||||
import ConditionTags from "@/views/components/ConditionTags.vue";
|
||||
import { ElMessage } from "element-plus";
|
||||
import { EntityType } from "../../data";
|
||||
|
||||
const { t } = useI18n();
|
||||
const appStore = useAppStoreWithOut();
|
||||
const selectorStore = useSelectorStore();
|
||||
const dashboardStore = useDashboardStore();
|
||||
const traceStore = useTraceStore();
|
||||
const traceId = ref<string>("");
|
||||
const minTraceDuration = ref<string>("");
|
||||
const maxTraceDuration = ref<string>("");
|
||||
const tagsList = ref<string[]>([]);
|
||||
const tagsMap = ref<Option[]>([]);
|
||||
const state = reactive<any>({
|
||||
status: { label: "All", value: "ALL" },
|
||||
instance: { value: "0", label: "All" },
|
||||
endpoint: { value: "0", label: "All" },
|
||||
service: { value: "0", label: "All" },
|
||||
});
|
||||
|
||||
// const dateTime = computed(() => [
|
||||
// appStore.durationRow.start,
|
||||
// appStore.durationRow.end,
|
||||
// ]);
|
||||
init();
|
||||
function init() {
|
||||
searchTraces();
|
||||
if (dashboardStore.entity === EntityType[1].value) {
|
||||
getServices();
|
||||
return;
|
||||
}
|
||||
if (dashboardStore.entity === EntityType[2].value) {
|
||||
getInstances();
|
||||
return;
|
||||
}
|
||||
if (dashboardStore.entity === EntityType[3].value) {
|
||||
getEndpoints();
|
||||
return;
|
||||
}
|
||||
if (dashboardStore.entity === EntityType[0].value) {
|
||||
getInstances();
|
||||
getEndpoints();
|
||||
}
|
||||
}
|
||||
|
||||
async function getServices() {
|
||||
const resp = await traceStore.getServices(dashboardStore.layerId);
|
||||
if (resp.errors) {
|
||||
ElMessage.error(resp.errors);
|
||||
return;
|
||||
}
|
||||
state.service = traceStore.services[0];
|
||||
}
|
||||
|
||||
async function getEndpoints() {
|
||||
const resp = await traceStore.getEndpoints();
|
||||
if (resp.errors) {
|
||||
ElMessage.error(resp.errors);
|
||||
return;
|
||||
}
|
||||
state.endpoint = traceStore.endpoints[0];
|
||||
}
|
||||
async function getInstances() {
|
||||
const resp = await traceStore.getInstances();
|
||||
if (resp.errors) {
|
||||
ElMessage.error(resp.errors);
|
||||
return;
|
||||
}
|
||||
state.instance = traceStore.instances[0];
|
||||
}
|
||||
function searchTraces() {
|
||||
let endpoint = "",
|
||||
instance = "";
|
||||
if (dashboardStore.entity === EntityType[2].value) {
|
||||
endpoint = selectorStore.currentPod.id;
|
||||
}
|
||||
if (dashboardStore.entity === EntityType[3].value) {
|
||||
instance = selectorStore.currentPod.id;
|
||||
}
|
||||
traceStore.setTraceCondition({
|
||||
serviceId: selectorStore.currentService
|
||||
? selectorStore.currentService.id
|
||||
: state.service.id,
|
||||
traceId: traceId.value || undefined,
|
||||
endpointId: endpoint || state.endpoint.id || undefined,
|
||||
serviceInstanceId: instance || state.instance.id || undefined,
|
||||
traceState: state.status.value || "ALL",
|
||||
queryDuration: appStore.durationTime,
|
||||
minTraceDuration: appStore.minTraceDuration || undefined,
|
||||
maxTraceDuration: appStore.maxTraceDuration || undefined,
|
||||
queryOrder: "BY_DURATION",
|
||||
tags: tagsMap.value.length ? tagsMap.value : undefined,
|
||||
paging: { pageNum: 1, pageSize: 15, needTotal: true },
|
||||
});
|
||||
queryTraces();
|
||||
}
|
||||
async function queryTraces() {
|
||||
const res = await traceStore.getTraces();
|
||||
if (res && res.errors) {
|
||||
ElMessage.error(res.errors);
|
||||
}
|
||||
}
|
||||
function changeField(type: string, opt: any[]) {
|
||||
state[type] = opt[0];
|
||||
if (type === "service") {
|
||||
getEndpoints();
|
||||
getInstances();
|
||||
}
|
||||
}
|
||||
function updateTags(data: { tagsMap: Array<Option>; tagsList: string[] }) {
|
||||
tagsList.value = data.tagsList;
|
||||
tagsMap.value = data.tagsMap;
|
||||
}
|
||||
watch(
|
||||
() => selectorStore.currentService,
|
||||
() => {
|
||||
if (dashboardStore.entity !== EntityType[0].value) {
|
||||
return;
|
||||
}
|
||||
init();
|
||||
}
|
||||
);
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.inputs {
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
.row {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.traceId {
|
||||
width: 270px;
|
||||
}
|
||||
|
||||
.search-btn {
|
||||
margin-left: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
213
src/views/dashboard/related/trace/TraceList.vue
Normal file
@ -0,0 +1,213 @@
|
||||
<!-- 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="trace-t flex-v">
|
||||
<div class="trace-t-tool flex-h">
|
||||
<el-pagination
|
||||
v-model:currentPage="traceStore.conditions.paging.pageNum"
|
||||
v-model:page-size="pageSize"
|
||||
:small="true"
|
||||
layout="prev, pager, next, jumper"
|
||||
:total="traceStore.traceTotal"
|
||||
v-model:pager-count="pageCount"
|
||||
@current-change="updatePage"
|
||||
/>
|
||||
<div class="selectors">
|
||||
<Selector
|
||||
size="small"
|
||||
:value="traceStore.conditions.queryOrder"
|
||||
:options="QueryOrders"
|
||||
placeholder="Select a option"
|
||||
@change="changeSort"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="trace-t-wrapper" v-loading="loading">
|
||||
<table class="list" v-if="traceStore.traceList.length">
|
||||
<tr
|
||||
class="trace-tr cp"
|
||||
v-for="(i, index) in traceStore.traceList"
|
||||
@click="selectTrace(i)"
|
||||
:key="index"
|
||||
>
|
||||
<td
|
||||
class="trace-td"
|
||||
:class="{
|
||||
'trace-success': !i.isError,
|
||||
'trace-error': i.isError,
|
||||
selected: selectedKey == i.key,
|
||||
}"
|
||||
>
|
||||
<div
|
||||
class="ell mb-5"
|
||||
:class="{
|
||||
blue: !i.isError,
|
||||
red: i.isError,
|
||||
}"
|
||||
>
|
||||
<span class="b">{{ i.endpointNames[0] }}</span>
|
||||
</div>
|
||||
<div class="grey ell sm">
|
||||
<span class="tag mr-10 sm">{{ i.duration }} ms</span
|
||||
>{{ dateFormat(parseInt(i.start, 10)) }}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<div class="no-data" v-else>{{ t("noData") }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import dayjs from "dayjs";
|
||||
import { ref } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useTraceStore } from "@/store/modules/trace";
|
||||
import { ElMessage } from "element-plus";
|
||||
import { QueryOrders } from "../../data";
|
||||
import { Option } from "@/types/app";
|
||||
import { Trace } from "@/types/trace";
|
||||
|
||||
const { t } = useI18n();
|
||||
const traceStore = useTraceStore();
|
||||
const loading = ref<boolean>(false);
|
||||
const selectedKey = ref<string>("");
|
||||
const pageSize = ref<number>(15);
|
||||
const pageCount = ref<number>(5);
|
||||
const dateFormat = (date: number, pattern = "YYYY-MM-DD HH:mm:ss") =>
|
||||
dayjs(date).format(pattern);
|
||||
|
||||
function searchTrace() {
|
||||
loading.value = true;
|
||||
queryTraces();
|
||||
loading.value = false;
|
||||
}
|
||||
|
||||
function updatePage(p: number) {
|
||||
traceStore.setTraceCondition({
|
||||
paging: { pageNum: p, pageSize: pageSize.value, needTotal: true },
|
||||
});
|
||||
searchTrace();
|
||||
}
|
||||
|
||||
function changeSort(opt: Option[]) {
|
||||
traceStore.setTraceCondition({
|
||||
queryOrder: opt[0].value,
|
||||
paging: { pageNum: 1, pageSize: pageSize.value, needTotal: true },
|
||||
});
|
||||
searchTrace();
|
||||
}
|
||||
|
||||
async function selectTrace(i: Trace) {
|
||||
traceStore.setCurrentTrace(i);
|
||||
selectedKey.value = i.key;
|
||||
if (i.traceIds.length) {
|
||||
const res = await traceStore.getTraceSpans({
|
||||
traceId: i.traceIds[0].value,
|
||||
});
|
||||
if (res.errors) {
|
||||
ElMessage.error(res.errors);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function queryTraces() {
|
||||
const res = await traceStore.getTraces();
|
||||
if (res.errors) {
|
||||
ElMessage.error(res.errors);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.trace-t-tool {
|
||||
background-color: rgba(196, 200, 225, 0.2);
|
||||
justify-content: space-between;
|
||||
border-bottom: 1px solid #c1c5ca41;
|
||||
border-right: 1px solid #c1c5ca41;
|
||||
height: 35px;
|
||||
}
|
||||
|
||||
.selectors {
|
||||
margin: 2px 2px 0 0;
|
||||
}
|
||||
|
||||
.trace-t-wrapper {
|
||||
overflow: auto;
|
||||
border-right: 1px solid rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.trace-t-loading {
|
||||
text-align: center;
|
||||
position: absolute;
|
||||
width: 420px;
|
||||
height: 70px;
|
||||
margin-top: 40px;
|
||||
line-height: 88px;
|
||||
overflow: hidden;
|
||||
|
||||
.icon {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
.trace-t {
|
||||
width: 420px;
|
||||
}
|
||||
|
||||
.list {
|
||||
width: 400px;
|
||||
}
|
||||
|
||||
.trace-tr {
|
||||
&:hover {
|
||||
background-color: rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
}
|
||||
|
||||
.trace-td {
|
||||
padding: 5px;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.07);
|
||||
|
||||
&.selected {
|
||||
background-color: #ededed;
|
||||
}
|
||||
}
|
||||
|
||||
.trace-success {
|
||||
border-left: 4px solid rgba(46, 47, 51, 0.1);
|
||||
}
|
||||
|
||||
.trace-warning {
|
||||
border-left: 4px solid #fbb03b;
|
||||
}
|
||||
|
||||
.trace-error {
|
||||
border-left: 4px solid #e54c17;
|
||||
}
|
||||
|
||||
.tag {
|
||||
border-radius: 4px;
|
||||
padding-right: 5px;
|
||||
padding-left: 5px;
|
||||
background-color: #40454e;
|
||||
color: #eee;
|
||||
}
|
||||
|
||||
.no-data {
|
||||
padding-top: 50px;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
354
src/views/dashboard/related/trace/components/D3Graph/Index.vue
Normal file
@ -0,0 +1,354 @@
|
||||
<!-- 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="trace-t-loading" v-show="loading">
|
||||
<Icon iconName="spinner" size="sm" />
|
||||
</div>
|
||||
<div ref="traceGraph" class="d3-graph"></div>
|
||||
<el-dialog
|
||||
v-model="showDetail"
|
||||
:destroy-on-close="true"
|
||||
fullscreen
|
||||
@closed="showDetail = false"
|
||||
>
|
||||
<SpanDetail :currentSpan="currentSpan" />
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch, onBeforeUnmount, onMounted } from "vue";
|
||||
import type { PropType } from "vue";
|
||||
import _ from "lodash";
|
||||
import * as d3 from "d3";
|
||||
import ListGraph from "../../utils/d3-trace-list";
|
||||
import TreeGraph from "../../utils/d3-trace-tree";
|
||||
import { Span } from "@/types/trace";
|
||||
import SpanDetail from "./SpanDetail.vue";
|
||||
|
||||
/* global defineProps, Nullable, defineExpose*/
|
||||
const props = defineProps({
|
||||
data: { type: Array as PropType<Span[]>, default: () => [] },
|
||||
traceId: { type: String, default: "" },
|
||||
type: { type: String, default: "List" },
|
||||
});
|
||||
const loading = ref<boolean>(false);
|
||||
const showDetail = ref<boolean>(false);
|
||||
const fixSpansSize = ref<number>(0);
|
||||
const segmentId = ref<any>([]);
|
||||
const currentSpan = ref<Array<Span>>([]);
|
||||
const tree = ref<any>(null);
|
||||
const traceGraph = ref<Nullable<HTMLDivElement>>(null);
|
||||
defineExpose({
|
||||
tree,
|
||||
});
|
||||
onMounted(() => {
|
||||
loading.value = true;
|
||||
changeTree();
|
||||
if (!traceGraph.value) {
|
||||
loading.value = false;
|
||||
return;
|
||||
}
|
||||
if (props.type === "List") {
|
||||
tree.value = new ListGraph(traceGraph.value, handleSelectSpan);
|
||||
tree.value.init(
|
||||
{ label: "TRACE_ROOT", children: segmentId.value },
|
||||
props.data,
|
||||
fixSpansSize.value
|
||||
);
|
||||
tree.value.draw();
|
||||
} else {
|
||||
tree.value = new TreeGraph(traceGraph.value, handleSelectSpan);
|
||||
tree.value.init(
|
||||
{ label: `${props.traceId}`, children: segmentId.value },
|
||||
props.data
|
||||
);
|
||||
}
|
||||
loading.value = false;
|
||||
window.addEventListener("resize", resize);
|
||||
});
|
||||
function resize() {
|
||||
tree.value.resize();
|
||||
}
|
||||
function handleSelectSpan(i: any) {
|
||||
currentSpan.value = i.data;
|
||||
showDetail.value = true;
|
||||
}
|
||||
function traverseTree(node: any, spanId: string, segmentId: string, data: any) {
|
||||
if (!node || node.isBroken) {
|
||||
return;
|
||||
}
|
||||
if (node.spanId === spanId && node.segmentId === segmentId) {
|
||||
node.children.push(data);
|
||||
return;
|
||||
}
|
||||
if (node.children && node.children.length > 0) {
|
||||
node.children.forEach((nodeItem: any) => {
|
||||
traverseTree(nodeItem, spanId, segmentId, data);
|
||||
});
|
||||
}
|
||||
}
|
||||
function changeTree() {
|
||||
if (props.data.length === 0) {
|
||||
return [];
|
||||
}
|
||||
segmentId.value = [];
|
||||
const segmentGroup: any = {};
|
||||
const segmentIdGroup: any = [];
|
||||
const fixSpans: any[] = [];
|
||||
const segmentHeaders: any = [];
|
||||
for (const span of props.data) {
|
||||
if (span.parentSpanId === -1) {
|
||||
segmentHeaders.push(span);
|
||||
} else {
|
||||
const index = props.data.findIndex(
|
||||
(i: any) =>
|
||||
i.segmentId === span.segmentId && i.spanId === span.spanId - 1
|
||||
);
|
||||
const fixSpanKeyContent = {
|
||||
traceId: span.traceId,
|
||||
segmentId: span.segmentId,
|
||||
spanId: span.spanId - 1,
|
||||
parentSpanId: span.spanId - 2,
|
||||
};
|
||||
if (index === -1 && !_.find(fixSpans, fixSpanKeyContent)) {
|
||||
fixSpans.push({
|
||||
...fixSpanKeyContent,
|
||||
refs: [],
|
||||
endpointName: `VNode: ${span.segmentId}`,
|
||||
serviceCode: "VirtualNode",
|
||||
type: `[Broken] ${span.type}`,
|
||||
peer: "",
|
||||
component: `VirtualNode: #${span.spanId - 1}`,
|
||||
isError: true,
|
||||
isBroken: true,
|
||||
layer: "Broken",
|
||||
tags: [],
|
||||
logs: [],
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
segmentHeaders.forEach((span: Span) => {
|
||||
if (span.refs.length) {
|
||||
span.refs.forEach((ref) => {
|
||||
const index = props.data.findIndex(
|
||||
(i: any) =>
|
||||
ref.parentSegmentId === i.segmentId && ref.parentSpanId === i.spanId
|
||||
);
|
||||
if (index === -1) {
|
||||
// create a known broken node.
|
||||
const i = ref.parentSpanId;
|
||||
const fixSpanKeyContent = {
|
||||
traceId: ref.traceId,
|
||||
segmentId: ref.parentSegmentId,
|
||||
spanId: i,
|
||||
parentSpanId: i > -1 ? 0 : -1,
|
||||
};
|
||||
if (!_.find(fixSpans, fixSpanKeyContent)) {
|
||||
fixSpans.push({
|
||||
...fixSpanKeyContent,
|
||||
refs: [],
|
||||
endpointName: `VNode: ${ref.parentSegmentId}`,
|
||||
serviceCode: "VirtualNode",
|
||||
type: `[Broken] ${ref.type}`,
|
||||
peer: "",
|
||||
component: `VirtualNode: #${i}`,
|
||||
isError: true,
|
||||
isBroken: true,
|
||||
layer: "Broken",
|
||||
tags: [],
|
||||
logs: [],
|
||||
});
|
||||
}
|
||||
// if root broken node is not exist, create a root broken node.
|
||||
if (fixSpanKeyContent.parentSpanId > -1) {
|
||||
const fixRootSpanKeyContent = {
|
||||
traceId: ref.traceId,
|
||||
segmentId: ref.parentSegmentId,
|
||||
spanId: 0,
|
||||
parentSpanId: -1,
|
||||
};
|
||||
if (!_.find(fixSpans, fixRootSpanKeyContent)) {
|
||||
fixSpans.push({
|
||||
...fixRootSpanKeyContent,
|
||||
refs: [],
|
||||
endpointName: `VNode: ${ref.parentSegmentId}`,
|
||||
serviceCode: "VirtualNode",
|
||||
type: `[Broken] ${ref.type}`,
|
||||
peer: "",
|
||||
component: `VirtualNode: #0`,
|
||||
isError: true,
|
||||
isBroken: true,
|
||||
layer: "Broken",
|
||||
tags: [],
|
||||
logs: [],
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
[...fixSpans, ...props.data].forEach((i) => {
|
||||
i.label = i.endpointName || "no operation name";
|
||||
i.children = [];
|
||||
if (segmentGroup[i.segmentId] === undefined) {
|
||||
segmentIdGroup.push(i.segmentId);
|
||||
segmentGroup[i.segmentId] = [];
|
||||
segmentGroup[i.segmentId].push(i);
|
||||
} else {
|
||||
segmentGroup[i.segmentId].push(i);
|
||||
}
|
||||
});
|
||||
fixSpansSize.value = fixSpans.length;
|
||||
segmentIdGroup.forEach((id: string) => {
|
||||
const currentSegment = segmentGroup[id].sort(
|
||||
(a: Span, b: Span) => b.parentSpanId - a.parentSpanId
|
||||
);
|
||||
currentSegment.forEach((s: any) => {
|
||||
const index = currentSegment.findIndex(
|
||||
(i: Span) => i.spanId === s.parentSpanId
|
||||
);
|
||||
if (index !== -1) {
|
||||
if (
|
||||
(currentSegment[index].isBroken &&
|
||||
currentSegment[index].parentSpanId === -1) ||
|
||||
!currentSegment[index].isBroken
|
||||
) {
|
||||
currentSegment[index].children.push(s);
|
||||
currentSegment[index].children.sort(
|
||||
(a: Span, b: Span) => a.spanId - b.spanId
|
||||
);
|
||||
}
|
||||
}
|
||||
if (s.isBroken) {
|
||||
const children = _.filter(props.data, (span: Span) => {
|
||||
return _.find(span.refs, {
|
||||
traceId: s.traceId,
|
||||
parentSegmentId: s.segmentId,
|
||||
parentSpanId: s.spanId,
|
||||
});
|
||||
});
|
||||
if (children.length > 0) {
|
||||
s.children.push(...children);
|
||||
}
|
||||
}
|
||||
});
|
||||
segmentGroup[id] = currentSegment[currentSegment.length - 1];
|
||||
});
|
||||
segmentIdGroup.forEach((id: string) => {
|
||||
segmentGroup[id].refs.forEach((ref: any) => {
|
||||
if (ref.traceId === props.traceId) {
|
||||
traverseTree(
|
||||
segmentGroup[ref.parentSegmentId],
|
||||
ref.parentSpanId,
|
||||
ref.parentSegmentId,
|
||||
segmentGroup[id]
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
for (const i in segmentGroup) {
|
||||
if (segmentGroup[i].refs.length === 0) {
|
||||
segmentId.value.push(segmentGroup[i]);
|
||||
}
|
||||
}
|
||||
segmentId.value.forEach((i: any) => {
|
||||
collapse(i);
|
||||
});
|
||||
}
|
||||
function collapse(d: Span) {
|
||||
if (d.children) {
|
||||
let dur = d.endTime - d.startTime;
|
||||
d.children.forEach((i: Span) => {
|
||||
dur -= i.endTime - i.startTime;
|
||||
});
|
||||
d.dur = dur < 0 ? 0 : dur;
|
||||
d.children.forEach((i: Span) => collapse(i));
|
||||
}
|
||||
}
|
||||
onBeforeUnmount(() => {
|
||||
d3.selectAll(".d3-tip").remove();
|
||||
window.removeEventListener("resize", resize);
|
||||
});
|
||||
watch(
|
||||
() => props.data,
|
||||
() => {
|
||||
if (!props.data.length) {
|
||||
return;
|
||||
}
|
||||
loading.value = true;
|
||||
changeTree();
|
||||
tree.value.init(
|
||||
{ label: "TRACE_ROOT", children: segmentId.value },
|
||||
props.data,
|
||||
fixSpansSize.value
|
||||
);
|
||||
tree.value.draw(() => {
|
||||
setTimeout(() => {
|
||||
loading.value = false;
|
||||
}, 200);
|
||||
});
|
||||
}
|
||||
);
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.d3-graph {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.trace-node .group {
|
||||
cursor: pointer;
|
||||
fill-opacity: 0;
|
||||
}
|
||||
|
||||
.trace-node-container {
|
||||
fill: rgba(0, 0, 0, 0);
|
||||
stroke-width: 5px;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
fill: rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
}
|
||||
|
||||
.trace-node .node-text {
|
||||
font: 12.5px sans-serif;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.domain {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.time-charts-item {
|
||||
display: inline-block;
|
||||
padding: 2px 8px;
|
||||
border: 1px solid;
|
||||
font-size: 11px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.trace-list .trace-node rect {
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
fill: rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
}
|
||||
|
||||
.dialog-c-text {
|
||||
white-space: pre;
|
||||
overflow: auto;
|
||||
font-family: monospace;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,164 @@
|
||||
<!-- 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>
|
||||
<h5 class="mb-15">{{ t("tags") }}.</h5>
|
||||
<div class="mb-10 clear">
|
||||
<span class="g-sm-4 grey">{{ t("service") }}:</span>
|
||||
<span class="g-sm-8 wba">{{ currentSpan.serviceCode }}</span>
|
||||
</div>
|
||||
<div class="mb-10 clear">
|
||||
<span class="g-sm-4 grey">{{ t("instance") }}:</span>
|
||||
<span class="g-sm-8 wba">{{ currentSpan.serviceInstanceName }}</span>
|
||||
</div>
|
||||
<div class="mb-10 clear">
|
||||
<span class="g-sm-4 grey">{{ t("endpoint") }}:</span>
|
||||
<span class="g-sm-8 wba">{{ currentSpan.label }}</span>
|
||||
</div>
|
||||
<div class="mb-10 clear">
|
||||
<span class="g-sm-4 grey">{{ t("spanType") }}:</span>
|
||||
<span class="g-sm-8 wba">{{ currentSpan.type }}</span>
|
||||
</div>
|
||||
<div class="mb-10 clear">
|
||||
<span class="g-sm-4 grey">{{ t("component") }}:</span>
|
||||
<span class="g-sm-8 wba">{{ currentSpan.component }}</span>
|
||||
</div>
|
||||
<div class="mb-10 clear">
|
||||
<span class="g-sm-4 grey">Peer:</span>
|
||||
<span class="g-sm-8 wba">{{ currentSpan.peer || "No Peer" }}</span>
|
||||
</div>
|
||||
<div class="mb-10 clear">
|
||||
<span class="g-sm-4 grey">{{ t("error") }}:</span>
|
||||
<span class="g-sm-8 wba">{{ currentSpan.isError }}</span>
|
||||
</div>
|
||||
<div class="mb-10 clear" v-for="i in currentSpan.tags" :key="i.key">
|
||||
<span class="g-sm-4 grey">{{ i.key }}:</span>
|
||||
<span class="g-sm-8 wba">
|
||||
{{ i.value }}
|
||||
<svg
|
||||
v-if="i.key === 'db.statement'"
|
||||
class="icon vm grey link-hover cp ml-5"
|
||||
@click="copy(i.value)"
|
||||
>
|
||||
<use xlink:href="#review-list"></use>
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
<h5 class="mb-10" v-if="currentSpan.logs" v-show="currentSpan.logs.length">
|
||||
{{ t("logs") }}.
|
||||
</h5>
|
||||
<div v-for="(i, index) in currentSpan.logs" :key="index">
|
||||
<div class="mb-10 sm">
|
||||
<span class="mr-10">{{ t("time") }}:</span
|
||||
><span class="grey">{{ dateFormat(i.time) }}</span>
|
||||
</div>
|
||||
<div class="mb-15 clear" v-for="(_i, _index) in i.data" :key="_index">
|
||||
<div class="mb-10">
|
||||
{{ _i.key }}:<span
|
||||
v-if="_i.key === 'stack'"
|
||||
class="r rk-sidebox-magnify"
|
||||
@click="showCurrentSpanDetail(_i.value)"
|
||||
>
|
||||
<svg class="icon">
|
||||
<use xlink:href="#magnify"></use>
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
<pre class="pl-15 mt-0 mb-0 sm oa">{{ _i.value }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
<div @click="getTaceLogs()">
|
||||
<el-button class="popup-btn" type="primary">
|
||||
{{ t("relatedTraceLogs") }}
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<el-dialog
|
||||
v-model="showRelatedLogs"
|
||||
:destroy-on-close="true"
|
||||
fullscreen
|
||||
@closed="showRelatedLogs = false"
|
||||
>
|
||||
<el-pagination
|
||||
v-model:currentPage="pageNum"
|
||||
v-model:page-size="pageSize"
|
||||
:small="true"
|
||||
:total="traceStore.traceSpanLogsTotal"
|
||||
@current-change="turnPage"
|
||||
/>
|
||||
<LogTable
|
||||
:tableData="traceStore.traceSpanLogs || []"
|
||||
:type="`service`"
|
||||
:noLink="true"
|
||||
>
|
||||
<div class="log-tips" v-if="!traceStore.traceSpanLogs.length">
|
||||
{{ t("noData") }}
|
||||
</div>
|
||||
</LogTable>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import type { PropType } from "vue";
|
||||
import dayjs from "dayjs";
|
||||
import { useTraceStore } from "@/store/modules/trace";
|
||||
import copy from "@/utils/copy";
|
||||
import { ElMessage } from "element-plus";
|
||||
import LogTable from "@/views/components/LogTable/Index.vue";
|
||||
|
||||
/* global defineProps */
|
||||
const props = defineProps({
|
||||
currentSpan: { type: Object as PropType<any>, default: () => ({}) },
|
||||
});
|
||||
const { t } = useI18n();
|
||||
const traceStore = useTraceStore();
|
||||
const pageNum = ref<number>(1);
|
||||
const showRelatedLogs = ref<boolean>(false);
|
||||
const pageSize = 10;
|
||||
const dateFormat = (date: number, pattern = "YYYY-MM-DD HH:mm:ss") =>
|
||||
dayjs(date).format(pattern);
|
||||
async function getTaceLogs() {
|
||||
showRelatedLogs.value = true;
|
||||
const res = await traceStore.getSpanLogs({
|
||||
condition: {
|
||||
relatedTrace: {
|
||||
traceId: props.currentSpan.traceId,
|
||||
segmentId: props.currentSpan.segmentId,
|
||||
spanId: props.currentSpan.spanId,
|
||||
},
|
||||
paging: { pageNum: pageNum.value, pageSize, needTotal: true },
|
||||
},
|
||||
});
|
||||
if (res.errors) {
|
||||
ElMessage.error(res.errors);
|
||||
}
|
||||
}
|
||||
function turnPage(p: number) {
|
||||
pageNum.value = p;
|
||||
getTaceLogs();
|
||||
}
|
||||
function showCurrentSpanDetail(text: string) {
|
||||
copy(text);
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.popup-btn {
|
||||
color: #fff;
|
||||
margin-top: 40px;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
106
src/views/dashboard/related/trace/components/List.vue
Normal file
@ -0,0 +1,106 @@
|
||||
<!-- 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="charts">
|
||||
<div>
|
||||
<span
|
||||
class="charts-item mr-5"
|
||||
v-for="(i, index) in list"
|
||||
:key="index"
|
||||
:style="`color:${computedScale(index)}`"
|
||||
>
|
||||
<Icon iconName="issue-open-m" class="mr-5" size="sm" />
|
||||
<span>{{ i }}</span>
|
||||
</span>
|
||||
<el-button class="btn" type="primary" @click="downloadTrace">
|
||||
{{ t("exportImage") }}
|
||||
</el-button>
|
||||
</div>
|
||||
<div>
|
||||
<Graph :data="data" :traceId="traceId" type="List" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { computed } from "vue";
|
||||
import type { PropType } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import * as d3 from "d3";
|
||||
import { Span } from "@/types/trace";
|
||||
import Graph from "./D3Graph/Index.vue";
|
||||
|
||||
/* global defineProps*/
|
||||
const props = defineProps({
|
||||
data: { type: Array as PropType<Span[]>, default: () => [] },
|
||||
traceId: { type: String, default: "" },
|
||||
});
|
||||
const { t } = useI18n();
|
||||
const list = computed(() =>
|
||||
Array.from(new Set(props.data.map((i: Span) => i.serviceCode)))
|
||||
);
|
||||
|
||||
function computedScale(i: number) {
|
||||
const sequentialScale = d3
|
||||
.scaleSequential()
|
||||
.domain([0, list.value.length + 1])
|
||||
.interpolator(d3.interpolateCool);
|
||||
return sequentialScale(i);
|
||||
}
|
||||
|
||||
function downloadTrace() {
|
||||
const serializer = new XMLSerializer();
|
||||
const svgNode: any = d3.select(".trace-list-dowanload").node();
|
||||
const source = `<?xml version="1.0" standalone="no"?>\r\n${serializer.serializeToString(
|
||||
svgNode
|
||||
)}`;
|
||||
const canvas = document.createElement("canvas");
|
||||
const context: any = canvas.getContext("2d");
|
||||
canvas.width = (
|
||||
d3.select(".trace-list-dowanload") as any
|
||||
)._groups[0][0].clientWidth;
|
||||
canvas.height = (
|
||||
d3.select(".trace-list-dowanload") as any
|
||||
)._groups[0][0].clientHeight;
|
||||
context.fillStyle = "#fff";
|
||||
context.fillRect(0, 0, canvas.width, canvas.height);
|
||||
const image = new Image();
|
||||
image.src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(source)}`;
|
||||
image.onload = () => {
|
||||
context.drawImage(image, 0, 0);
|
||||
const tagA = document.createElement("a");
|
||||
tagA.download = "trace-list.png";
|
||||
tagA.href = canvas.toDataURL("image/png");
|
||||
tagA.click();
|
||||
};
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.charts {
|
||||
overflow: auto;
|
||||
padding: 10px;
|
||||
height: calc(100% - 95px);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.charts-item {
|
||||
display: inline-block;
|
||||
padding: 2px 8px;
|
||||
border: 1px solid;
|
||||
font-size: 11px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
float: right;
|
||||
}
|
||||
</style>
|
125
src/views/dashboard/related/trace/components/Statistics.vue
Normal file
@ -0,0 +1,125 @@
|
||||
<!-- 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="trace-statistics">
|
||||
<div class="trace-t-loading" v-show="loading">
|
||||
<Icon iconName="spinner" size="sm" />
|
||||
</div>
|
||||
<TableContainer
|
||||
:tableData="tableData"
|
||||
type="statistics"
|
||||
:HeaderType="HeaderType"
|
||||
>
|
||||
<div class="trace-tips" v-if="!tableData.length">{{ $t("noData") }}</div>
|
||||
</TableContainer>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch, onMounted } from "vue";
|
||||
import type { PropType } from "vue";
|
||||
import TableContainer from "./Table/TableContainer.vue";
|
||||
import traceTable from "../utils/trace-table";
|
||||
import { StatisticsSpan, Span, StatisticsGroupRef } from "@/types/trace";
|
||||
|
||||
/* global defineProps, defineEmits */
|
||||
const props = defineProps({
|
||||
data: { type: Array as PropType<any>, default: () => [] },
|
||||
traceId: { type: String, default: "" },
|
||||
showBtnDetail: { type: Boolean, default: false },
|
||||
HeaderType: { type: String, default: "" },
|
||||
});
|
||||
const emit = defineEmits(["load"]);
|
||||
const loading = ref<boolean>(true);
|
||||
const tableData = ref<any>([]);
|
||||
const list = ref<any[]>([]);
|
||||
|
||||
onMounted(() => {
|
||||
tableData.value = calculationDataforStatistics(props.data);
|
||||
loading.value = false;
|
||||
emit("load", () => {
|
||||
loading.value = true;
|
||||
});
|
||||
});
|
||||
|
||||
function calculationDataforStatistics(data: Span[]): StatisticsSpan[] {
|
||||
list.value = traceTable.buildTraceDataList(data);
|
||||
const result: StatisticsSpan[] = [];
|
||||
const map = traceTable.changeStatisticsTree(data, props.traceId);
|
||||
map.forEach((nodes, nodeKey) => {
|
||||
const nodeKeyData = nodeKey.split(":");
|
||||
result.push(
|
||||
getSpanGroupData(nodes, {
|
||||
endpointName: nodeKeyData[0],
|
||||
type: nodeKeyData[1],
|
||||
})
|
||||
);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
function getSpanGroupData(
|
||||
groupspans: Span[],
|
||||
groupRef: StatisticsGroupRef
|
||||
): StatisticsSpan {
|
||||
let maxTime = 0;
|
||||
let minTime = 0;
|
||||
let sumTime = 0;
|
||||
const count = groupspans.length;
|
||||
groupspans.forEach((groupspan: Span) => {
|
||||
const duration = groupspan.dur || 0;
|
||||
if (duration > maxTime) {
|
||||
maxTime = duration;
|
||||
}
|
||||
if (duration < minTime) {
|
||||
minTime = duration;
|
||||
}
|
||||
sumTime = sumTime + duration;
|
||||
});
|
||||
const avgTime = count === 0 ? 0 : sumTime / count;
|
||||
return {
|
||||
groupRef,
|
||||
maxTime,
|
||||
minTime,
|
||||
sumTime,
|
||||
avgTime,
|
||||
count,
|
||||
};
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.data,
|
||||
() => {
|
||||
if (!props.data.length) {
|
||||
tableData.value = [];
|
||||
return;
|
||||
}
|
||||
tableData.value = calculationDataforStatistics(props.data);
|
||||
loading.value = false;
|
||||
}
|
||||
);
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.trace-tips {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.trace-statistics {
|
||||
padding: 10px;
|
||||
height: calc(100% - 95px);
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
116
src/views/dashboard/related/trace/components/Table/Index.vue
Normal file
@ -0,0 +1,116 @@
|
||||
<!-- 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="trace-table">
|
||||
<div class="trace-t-loading" v-show="loading">
|
||||
<Icon iconName="spinner" size="sm" />
|
||||
</div>
|
||||
<TableContainer
|
||||
:tableData="tableData"
|
||||
type="table"
|
||||
:HeaderType="HeaderType"
|
||||
>
|
||||
<div class="trace-tips" v-if="!tableData.length">{{ $t("noData") }}</div>
|
||||
</TableContainer>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch, onMounted } from "vue";
|
||||
import type { PropType } from "vue";
|
||||
import TableContainer from "./TableContainer.vue";
|
||||
import traceTable from "../../utils/trace-table";
|
||||
|
||||
/* global defineProps, defineEmits */
|
||||
const props = defineProps({
|
||||
data: { type: Array as PropType<any>, default: () => [] },
|
||||
traceId: { type: String, default: "" },
|
||||
showBtnDetail: { type: Boolean, default: false },
|
||||
HeaderType: { type: String, default: "" },
|
||||
});
|
||||
const emit = defineEmits(["select", "view", "load"]);
|
||||
const loading = ref<boolean>(true);
|
||||
const tableData = ref<any>([]);
|
||||
const showDetail = ref<boolean>(false);
|
||||
const currentSpan = ref<any[]>([]);
|
||||
|
||||
onMounted(() => {
|
||||
tableData.value = formatData(
|
||||
traceTable.changeTree(props.data, props.traceId)
|
||||
);
|
||||
loading.value = false;
|
||||
emit("select", handleSelectSpan);
|
||||
emit("view", handleViewSpan);
|
||||
emit("load", () => {
|
||||
loading.value = true;
|
||||
});
|
||||
});
|
||||
|
||||
function formatData(arr: any[], level = 1, totalExec?: number) {
|
||||
for (const item of arr) {
|
||||
item.level = level;
|
||||
totalExec = totalExec || item.endTime - item.startTime;
|
||||
item.totalExec = totalExec;
|
||||
if (item.children && item.children.length > 0) {
|
||||
formatData(item.children, level + 1, totalExec);
|
||||
}
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
function handleSelectSpan(data: any[]) {
|
||||
currentSpan.value = data;
|
||||
if (!props.showBtnDetail) {
|
||||
showDetail.value = true;
|
||||
}
|
||||
emit("select", data);
|
||||
}
|
||||
|
||||
function handleViewSpan() {
|
||||
showDetail.value = true;
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.data,
|
||||
() => {
|
||||
if (!props.data.length) {
|
||||
tableData.value = [];
|
||||
return;
|
||||
}
|
||||
tableData.value = formatData(
|
||||
traceTable.changeTree(props.data, props.traceId)
|
||||
);
|
||||
loading.value = false;
|
||||
}
|
||||
);
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.dialog-c-text {
|
||||
white-space: pre;
|
||||
overflow: auto;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.trace-tips {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.trace-table {
|
||||
padding: 10px;
|
||||
height: calc(100% - 95px);
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,172 @@
|
||||
<!-- 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="trace">
|
||||
<div class="trace-header" v-if="type === 'statistics'">
|
||||
<div :class="item.label" v-for="(item, index) in headerData" :key="index">
|
||||
{{ item.value }}
|
||||
<span
|
||||
class="r cp"
|
||||
@click="sortStatistics(item.key)"
|
||||
:key="componentKey"
|
||||
v-if="item.key !== 'endpointName' && item.key !== 'type'"
|
||||
>
|
||||
<Icon iconName="sort" size="sm" />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="trace-header" v-else>
|
||||
<div class="method" :style="`width: ${method}px`">
|
||||
<span class="r cp" ref="dragger">
|
||||
<Icon iconName="settings_ethernet" size="sm" />
|
||||
</span>
|
||||
{{ headerData[0].value }}
|
||||
</div>
|
||||
<div
|
||||
:class="item.label"
|
||||
v-for="(item, index) in headerData.slice(1)"
|
||||
:key="index"
|
||||
>
|
||||
{{ item.value }}
|
||||
</div>
|
||||
</div>
|
||||
<table-item
|
||||
:method="method"
|
||||
v-for="(item, index) in tableData"
|
||||
:data="item"
|
||||
:key="'key' + index"
|
||||
:type="type"
|
||||
/>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted } from "vue";
|
||||
import type { PropType } from "vue";
|
||||
import TableItem from "./TableItem.vue";
|
||||
import { ProfileConstant, TraceConstant, StatisticsConstant } from "./data";
|
||||
|
||||
/* global defineProps, Nullable */
|
||||
const props = defineProps({
|
||||
tableData: { type: Array as PropType<any>, default: () => [] },
|
||||
type: { type: String, default: "" },
|
||||
HeaderType: { type: String, default: "" },
|
||||
});
|
||||
const method = ref<number>(300);
|
||||
const componentKey = ref<number>(300);
|
||||
const flag = ref<boolean>(true);
|
||||
const dragger = ref<Nullable<HTMLSpanElement>>(null);
|
||||
let headerData: any[] = TraceConstant;
|
||||
if (props.HeaderType === "profile") {
|
||||
headerData = ProfileConstant;
|
||||
}
|
||||
if (props.type === "statistics") {
|
||||
headerData = StatisticsConstant;
|
||||
}
|
||||
onMounted(() => {
|
||||
if (props.type === "statistics") {
|
||||
return;
|
||||
}
|
||||
const drag: any = dragger.value;
|
||||
drag.onmousedown = (event: any) => {
|
||||
const diffX = event.clientX;
|
||||
const copy = method.value;
|
||||
document.onmousemove = (documentEvent) => {
|
||||
const moveX = documentEvent.clientX - diffX;
|
||||
method.value = copy + moveX;
|
||||
};
|
||||
document.onmouseup = () => {
|
||||
document.onmousemove = null;
|
||||
document.onmouseup = null;
|
||||
};
|
||||
};
|
||||
});
|
||||
function sortStatistics(key: string) {
|
||||
const element = props.tableData;
|
||||
for (let i = 0; i < element.length; i++) {
|
||||
for (let j = 0; j < element.length - i - 1; j++) {
|
||||
let val1;
|
||||
let val2;
|
||||
if (key === "maxTime") {
|
||||
val1 = element[j].maxTime;
|
||||
val2 = element[j + 1].maxTime;
|
||||
}
|
||||
if (key === "minTime") {
|
||||
val1 = element[j].minTime;
|
||||
val2 = element[j + 1].minTime;
|
||||
}
|
||||
if (key === "avgTime") {
|
||||
val1 = element[j].avgTime;
|
||||
val2 = element[j + 1].avgTime;
|
||||
}
|
||||
if (key === "sumTime") {
|
||||
val1 = element[j].sumTime;
|
||||
val2 = element[j + 1].sumTime;
|
||||
}
|
||||
if (key === "count") {
|
||||
val1 = element[j].count;
|
||||
val2 = element[j + 1].count;
|
||||
}
|
||||
if (flag.value) {
|
||||
if (val1 < val2) {
|
||||
const tmp = element[j];
|
||||
element[j] = element[j + 1];
|
||||
element[j + 1] = tmp;
|
||||
}
|
||||
} else {
|
||||
if (val1 > val2) {
|
||||
const tmp = element[j];
|
||||
element[j] = element[j + 1];
|
||||
element[j + 1] = tmp;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
this.tableData = element;
|
||||
this.componentKey += 1;
|
||||
this.flag = !this.flag;
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@import "./table.scss";
|
||||
|
||||
.trace {
|
||||
font-size: 12px;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.trace-header {
|
||||
white-space: nowrap;
|
||||
user-select: none;
|
||||
border-left: 0;
|
||||
border-right: 0;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.trace-header div {
|
||||
display: inline-block;
|
||||
background-color: #f3f4f9;
|
||||
padding: 0 4px;
|
||||
border: 1px solid transparent;
|
||||
border-right: 1px dotted silver;
|
||||
overflow: hidden;
|
||||
line-height: 30px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
304
src/views/dashboard/related/trace/components/Table/TableItem.vue
Normal file
@ -0,0 +1,304 @@
|
||||
<!-- 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 v-if="type === 'statistics'">
|
||||
<div :class="['trace-item']" ref="traceItem">
|
||||
<div :class="['method']">
|
||||
<el-tooltip :content="data.groupRef.endpointName" placement="bottom">
|
||||
<span>
|
||||
{{ data.groupRef.endpointName }}
|
||||
</span>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<div :class="['type']">
|
||||
<el-tooltip :content="data.groupRef.type" placement="bottom">
|
||||
<span>
|
||||
{{ data.groupRef.type }}
|
||||
</span>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<div class="max-time">
|
||||
{{ data.maxTime }}
|
||||
</div>
|
||||
<div class="min-time">
|
||||
{{ data.minTime }}
|
||||
</div>
|
||||
<div class="sum-time">
|
||||
{{ data.sumTime }}
|
||||
</div>
|
||||
<div class="avg-time">
|
||||
{{ parseInt(data.avgTime) }}
|
||||
</div>
|
||||
<div class="count">
|
||||
{{ data.count }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<div
|
||||
@click="viewSpanDetail"
|
||||
:class="[
|
||||
'trace-item',
|
||||
'level' + (data.level - 1),
|
||||
{ 'trace-item-error': data.isError },
|
||||
]"
|
||||
ref="traceItem"
|
||||
>
|
||||
<div
|
||||
:class="['method', 'level' + (data.level - 1)]"
|
||||
:style="{
|
||||
'text-indent': (data.level - 1) * 10 + 'px',
|
||||
width: `${method}px`,
|
||||
}"
|
||||
>
|
||||
<Icon
|
||||
:style="!displayChildren ? 'transform: rotate(-90deg);' : ''"
|
||||
@click.stop="toggle"
|
||||
v-if="data.children && data.children.length"
|
||||
iconName="arrow-down"
|
||||
size="sm"
|
||||
/>
|
||||
<el-tooltip :content="data.endpointName" placement="bottom">
|
||||
<span>
|
||||
{{ data.endpointName }}
|
||||
</span>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<div class="start-time">
|
||||
{{ dateFormat(data.startTime) }}
|
||||
</div>
|
||||
<div class="exec-ms">
|
||||
{{
|
||||
data.endTime - data.startTime ? data.endTime - data.startTime : "0"
|
||||
}}
|
||||
</div>
|
||||
<div class="exec-percent">
|
||||
<div class="outer-progress_bar" :style="{ width: outterPercent }">
|
||||
<div
|
||||
class="inner-progress_bar"
|
||||
:style="{ width: innerPercent }"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="self">
|
||||
{{ data.dur ? data.dur + "" : "0" }}
|
||||
</div>
|
||||
<div class="api">
|
||||
<el-tooltip :content="data.component || '-'" placement="bottom">
|
||||
<span>{{ data.component || "-" }}</span>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<div class="application">
|
||||
<el-tooltip :content="data.serviceCode || '-'" placement="bottom">
|
||||
<span>{{ data.serviceCode }}</span>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<div class="application" v-show="type === 'profile'">
|
||||
<span @click="viewSpanDetail">{{ t("view") }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-show="data.children && data.children.length > 0 && displayChildren"
|
||||
class="children-trace"
|
||||
>
|
||||
<table-item
|
||||
:method="method"
|
||||
v-for="(child, index) in data.children"
|
||||
:key="index"
|
||||
:data="child"
|
||||
:type="type"
|
||||
/>
|
||||
</div>
|
||||
<el-dialog
|
||||
v-model="showDetail"
|
||||
:destroy-on-close="true"
|
||||
fullscreen
|
||||
@closed="showDetail = false"
|
||||
>
|
||||
<SpanDetail :currentSpan="data" />
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import dayjs from "dayjs";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { ref, watch, computed, defineComponent } from "vue";
|
||||
import type { PropType } from "vue";
|
||||
import SpanDetail from "../D3Graph/SpanDetail.vue";
|
||||
|
||||
const props = {
|
||||
data: { type: Object as PropType<any>, default: () => ({}) },
|
||||
method: { type: Number, default: 0 },
|
||||
type: { type: String, default: "" },
|
||||
};
|
||||
export default defineComponent({
|
||||
name: "TableItem",
|
||||
props,
|
||||
emits: ["select"],
|
||||
components: { SpanDetail },
|
||||
setup(props, { emit }) {
|
||||
/* global Nullable */
|
||||
const displayChildren = ref<boolean>(true);
|
||||
const showDetail = ref<boolean>(false);
|
||||
const { t } = useI18n();
|
||||
const traceItem = ref<Nullable<HTMLDivElement>>(null);
|
||||
const dateFormat = (date: number, pattern = "YYYY-MM-DD HH:mm:ss") =>
|
||||
dayjs(date).format(pattern);
|
||||
const selfTime = computed(() => (props.data.dur ? props.data.dur : 0));
|
||||
const execTime = computed(() =>
|
||||
props.data.endTime - props.data.startTime
|
||||
? props.data.endTime - props.data.startTime
|
||||
: 0
|
||||
);
|
||||
const outterPercent = computed(() => {
|
||||
if (props.data.level === 1) {
|
||||
return "100%";
|
||||
} else {
|
||||
const data = props.data;
|
||||
const exec =
|
||||
data.endTime - data.startTime ? data.endTime - data.startTime : 0;
|
||||
let result = (exec / data.totalExec) * 100;
|
||||
result = result > 100 ? 100 : result;
|
||||
const resultStr = result.toFixed(4) + "%";
|
||||
return resultStr === "0.0000%" ? "0.9%" : resultStr;
|
||||
}
|
||||
});
|
||||
const innerPercent = computed(() => {
|
||||
const result = (selfTime.value / execTime.value) * 100;
|
||||
const resultStr = result.toFixed(4) + "%";
|
||||
return resultStr === "0.0000%" ? "0.9%" : resultStr;
|
||||
});
|
||||
|
||||
function toggle() {
|
||||
displayChildren.value = !this.displayChildren.value;
|
||||
}
|
||||
function showSelectSpan() {
|
||||
const items: any = document.querySelectorAll(".trace-item");
|
||||
for (const item of items) {
|
||||
item.style.background = "#fff";
|
||||
}
|
||||
if (!traceItem.value) {
|
||||
return;
|
||||
}
|
||||
traceItem.value.style.background = "rgba(0, 0, 0, 0.1)";
|
||||
}
|
||||
function viewSpanDetail() {
|
||||
showDetail.value = true;
|
||||
showSelectSpan();
|
||||
emit("select", props.data);
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.data,
|
||||
() => {
|
||||
showSelectSpan();
|
||||
}
|
||||
);
|
||||
return {
|
||||
displayChildren,
|
||||
outterPercent,
|
||||
innerPercent,
|
||||
viewSpanDetail,
|
||||
toggle,
|
||||
dateFormat,
|
||||
showSelectSpan,
|
||||
showDetail,
|
||||
t,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@import "./table.scss";
|
||||
|
||||
.trace-item.level0 {
|
||||
color: #448dfe;
|
||||
|
||||
&:hover {
|
||||
background: rgba(0, 0, 0, 0.04);
|
||||
color: #448dfe;
|
||||
}
|
||||
|
||||
&::before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
width: 5px;
|
||||
height: 100%;
|
||||
background: #448dfe;
|
||||
left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.trace-item-error {
|
||||
color: #e54c17;
|
||||
}
|
||||
|
||||
.trace-item {
|
||||
// display: flex;
|
||||
white-space: nowrap;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.trace-item.selected {
|
||||
background: rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.trace-item:not(.level0):hover {
|
||||
background: rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.trace-item > div {
|
||||
padding: 0 5px;
|
||||
display: inline-block;
|
||||
border: 1px solid transparent;
|
||||
border-right: 1px dotted silver;
|
||||
overflow: hidden;
|
||||
line-height: 30px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.trace-item > div.method {
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.trace-item div.exec-percent {
|
||||
width: 100px;
|
||||
height: 30px;
|
||||
padding: 0 8px;
|
||||
|
||||
.outer-progress_bar {
|
||||
width: 100%;
|
||||
height: 6px;
|
||||
border-radius: 3px;
|
||||
background: rgb(63, 177, 227);
|
||||
position: relative;
|
||||
margin-top: 11px;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.inner-progress_bar {
|
||||
position: absolute;
|
||||
background: rgb(110, 64, 170);
|
||||
height: 4px;
|
||||
border-radius: 2px;
|
||||
left: 0;
|
||||
border: none;
|
||||
top: 1px;
|
||||
}
|
||||
}
|
||||
</style>
|
120
src/views/dashboard/related/trace/components/Table/data.ts
Normal file
@ -0,0 +1,120 @@
|
||||
/**
|
||||
* 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 ProfileConstant = [
|
||||
{
|
||||
label: "method",
|
||||
value: "Span",
|
||||
},
|
||||
{
|
||||
label: "start-time",
|
||||
value: "Start Time",
|
||||
},
|
||||
{
|
||||
label: "exec-ms",
|
||||
value: "Exec(ms)",
|
||||
},
|
||||
{
|
||||
label: "exec-percent",
|
||||
value: "Exec(%)",
|
||||
},
|
||||
{
|
||||
label: "self",
|
||||
value: "Self(ms)",
|
||||
},
|
||||
{
|
||||
label: "api",
|
||||
value: "API",
|
||||
},
|
||||
{
|
||||
label: "application",
|
||||
value: "Service",
|
||||
},
|
||||
{
|
||||
label: "application",
|
||||
value: "Operation",
|
||||
},
|
||||
];
|
||||
|
||||
export const TraceConstant = [
|
||||
{
|
||||
label: "method",
|
||||
value: "Method",
|
||||
},
|
||||
{
|
||||
label: "start-time",
|
||||
value: "Start Time",
|
||||
},
|
||||
{
|
||||
label: "exec-ms",
|
||||
value: "Exec(ms)",
|
||||
},
|
||||
{
|
||||
label: "exec-percent",
|
||||
value: "Exec(%)",
|
||||
},
|
||||
{
|
||||
label: "self",
|
||||
value: "Self(ms)",
|
||||
},
|
||||
{
|
||||
label: "api",
|
||||
value: "API",
|
||||
},
|
||||
{
|
||||
label: "application",
|
||||
value: "Service",
|
||||
},
|
||||
];
|
||||
|
||||
export const StatisticsConstant = [
|
||||
{
|
||||
label: "method",
|
||||
value: "Endpoint Name",
|
||||
key: "endpointName",
|
||||
},
|
||||
{
|
||||
label: "type",
|
||||
value: "Type",
|
||||
key: "type",
|
||||
},
|
||||
{
|
||||
label: "max-time",
|
||||
value: "Max Time(ms)",
|
||||
key: "maxTime",
|
||||
},
|
||||
{
|
||||
label: "min-time",
|
||||
value: "Min Time(ms)",
|
||||
key: "minTime",
|
||||
},
|
||||
{
|
||||
label: "sum-time",
|
||||
value: "Sum Time(ms)",
|
||||
key: "sumTime",
|
||||
},
|
||||
{
|
||||
label: "avg-time",
|
||||
value: "Avg Time(ms)",
|
||||
key: "avgTime",
|
||||
},
|
||||
{
|
||||
label: "count",
|
||||
value: "Hits",
|
||||
key: "count",
|
||||
},
|
||||
];
|
@ -0,0 +1,76 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
.argument {
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
.start-time {
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
.exec-ms {
|
||||
width: 80px;
|
||||
}
|
||||
|
||||
.exec-percent {
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.self {
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.api {
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
.agent {
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
.application {
|
||||
width: 150px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.max-time {
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
.method {
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
.avg-time {
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
.min-time {
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
.count {
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
.sum-time {
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
.type {
|
||||
width: 60px;
|
||||
}
|
100
src/views/dashboard/related/trace/components/Tree.vue
Normal file
@ -0,0 +1,100 @@
|
||||
<!-- 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="trace-tree-charts flex-v">
|
||||
<div>
|
||||
<span
|
||||
class="time-charts-item mr-5"
|
||||
v-for="(i, index) in list"
|
||||
:key="index"
|
||||
:style="`color:${computedScale(index)}`"
|
||||
>
|
||||
<Icon iconName="issue-open-m" class="mr-5" size="sm" />
|
||||
<span>{{ i }}</span>
|
||||
</span>
|
||||
</div>
|
||||
<div style="padding: 10px 0">
|
||||
<a class="trace-tree-btn mr-10" @click="charts.tree.setDefault()">
|
||||
{{ t("default") }}
|
||||
</a>
|
||||
<a class="trace-tree-btn mr-10" @click="charts.tree.getTopSlow()">
|
||||
{{ t("topSlow") }}
|
||||
</a>
|
||||
<a class="trace-tree-btn mr-10" @click="charts.tree.getTopChild()">
|
||||
{{ t("topChildren") }}
|
||||
</a>
|
||||
</div>
|
||||
<div class="trace-tree">
|
||||
<Graph ref="charts" :data="data" :traceId="traceId" type="Tree" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import * as d3 from "d3";
|
||||
import Graph from "./D3Graph/Index.vue";
|
||||
import type { PropType } from "vue";
|
||||
import { Span } from "@/types/trace";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { ref, onMounted } from "vue";
|
||||
|
||||
/* global defineProps */
|
||||
const props = defineProps({
|
||||
data: { type: Array as PropType<Span[]>, default: () => [] },
|
||||
traceId: { type: String, default: "" },
|
||||
});
|
||||
const { t } = useI18n();
|
||||
const list = ref<string[]>([]);
|
||||
const charts = ref<any>(null);
|
||||
|
||||
onMounted(() => {
|
||||
list.value = Array.from(new Set(props.data.map((i: Span) => i.serviceCode)));
|
||||
});
|
||||
function computedScale(i: number) {
|
||||
const sequentialScale = d3
|
||||
.scaleSequential()
|
||||
.domain([0, list.value.length + 1])
|
||||
.interpolator(d3.interpolateCool);
|
||||
return sequentialScale(i);
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.trace-tree {
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.trace-tree-btn {
|
||||
display: inline-block;
|
||||
border-radius: 4px;
|
||||
padding: 0px 7px;
|
||||
background-color: #40454e;
|
||||
color: #eee;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.trace-tree-charts {
|
||||
overflow: auto;
|
||||
padding: 10px;
|
||||
position: relative;
|
||||
height: calc(100% - 95px);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.time-charts-item {
|
||||
display: inline-block;
|
||||
padding: 2px 8px;
|
||||
border: 1px solid;
|
||||
font-size: 11px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
</style>
|
28
src/views/dashboard/related/trace/components/index.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.
|
||||
*/
|
||||
|
||||
import List from "./List.vue";
|
||||
import Tree from "./Tree.vue";
|
||||
import Table from "./Table/Index.vue";
|
||||
import Statistics from "./Statistics.vue";
|
||||
|
||||
export default {
|
||||
List,
|
||||
Tree,
|
||||
Table,
|
||||
Statistics,
|
||||
};
|
314
src/views/dashboard/related/trace/utils/d3-trace-list.ts
Normal file
@ -0,0 +1,314 @@
|
||||
/**</template>
|
||||
* 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 * as d3 from "d3";
|
||||
import d3tip from "d3-tip";
|
||||
import { Trace } from "@/types/trace";
|
||||
|
||||
export default class ListGraph {
|
||||
private barHeight = 48;
|
||||
private handleSelectSpan: Nullable<(i: Trace) => void> = null;
|
||||
private el: Nullable<HTMLDivElement> = null;
|
||||
private i = 0;
|
||||
private width = 0;
|
||||
private height = 0;
|
||||
private svg: any = null;
|
||||
private tip: any = null;
|
||||
private row: any[] = [];
|
||||
private data: any = [];
|
||||
private min = 0;
|
||||
private max = 0;
|
||||
private list: any[] = [];
|
||||
private xScale: any = null;
|
||||
private xAxis: any = null;
|
||||
private sequentialScale: any = null;
|
||||
private root: any = null;
|
||||
constructor(el: HTMLDivElement, handleSelectSpan: (i: Trace) => void) {
|
||||
this.handleSelectSpan = handleSelectSpan;
|
||||
this.el = el;
|
||||
this.width = el.clientWidth - 20;
|
||||
this.height = el.clientHeight;
|
||||
this.svg = d3
|
||||
.select(this.el)
|
||||
.append("svg")
|
||||
.attr("class", "trace-list-dowanload")
|
||||
.attr("width", this.width)
|
||||
.attr("height", this.height);
|
||||
this.tip = (d3tip as any)()
|
||||
.attr("class", "d3-tip")
|
||||
.offset([-8, 0])
|
||||
.html((d: any) => {
|
||||
return `
|
||||
<div class="mb-5">${d.data.label}</div>
|
||||
${
|
||||
d.data.dur
|
||||
? '<div class="sm">SelfDuration: ' + d.data.dur + "ms</div>"
|
||||
: ""
|
||||
}
|
||||
${
|
||||
d.data.endTime - d.data.startTime
|
||||
? '<div class="sm">TotalDuration: ' +
|
||||
(d.data.endTime - d.data.startTime) +
|
||||
"ms</div>"
|
||||
: ""
|
||||
}
|
||||
`;
|
||||
});
|
||||
this.svg.call(this.tip);
|
||||
}
|
||||
diagonal(d: any) {
|
||||
return `M ${d.source.y} ${d.source.x + 5}
|
||||
L ${d.source.y} ${d.target.x - 30}
|
||||
L${d.target.y} ${d.target.x - 20}
|
||||
L${d.target.y} ${d.target.x - 5}`;
|
||||
}
|
||||
init(data: any, row: any[], fixSpansSize: number) {
|
||||
d3.select(".trace-xaxis").remove();
|
||||
this.row = row;
|
||||
this.data = data;
|
||||
this.min = d3.min(this.row.map((i) => i.startTime));
|
||||
this.max = d3.max(this.row.map((i) => i.endTime - this.min)) || 0;
|
||||
this.list = Array.from(new Set(this.row.map((i) => i.serviceCode)));
|
||||
this.xScale = d3
|
||||
.scaleLinear()
|
||||
.range([0, this.width * 0.387])
|
||||
.domain([0, this.max]);
|
||||
this.xAxis = d3.axisTop(this.xScale).tickFormat((d: any) => {
|
||||
if (d === 0) return 0;
|
||||
if (d >= 1000) return d / 1000 + "s";
|
||||
return d;
|
||||
});
|
||||
this.svg.attr(
|
||||
"height",
|
||||
(this.row.length + fixSpansSize + 1) * this.barHeight
|
||||
);
|
||||
this.svg
|
||||
.append("g")
|
||||
.attr("class", "trace-xaxis")
|
||||
|
||||
.attr("transform", `translate(${this.width * 0.618 - 20},${30})`)
|
||||
.call(this.xAxis);
|
||||
this.sequentialScale = d3
|
||||
.scaleSequential()
|
||||
.domain([0, this.list.length + 1])
|
||||
.interpolator(d3.interpolateCool);
|
||||
this.root = d3.hierarchy(this.data, (d) => d.children);
|
||||
this.root.x0 = 0;
|
||||
this.root.y0 = 0;
|
||||
}
|
||||
draw(callback: any) {
|
||||
this.update(this.root, callback);
|
||||
}
|
||||
click(d: any, scope: any) {
|
||||
if (!d.data.type) return;
|
||||
if (d.children) {
|
||||
d._children = d.children;
|
||||
d.children = null;
|
||||
} else {
|
||||
d.children = d._children;
|
||||
d._children = null;
|
||||
}
|
||||
scope.update(d);
|
||||
}
|
||||
update(source: any, callback: any) {
|
||||
const t = this;
|
||||
const nodes = this.root.descendants();
|
||||
let index = -1;
|
||||
this.root.eachBefore((n: any) => {
|
||||
n.x = ++index * this.barHeight + 24;
|
||||
n.y = n.depth * 12;
|
||||
});
|
||||
const node = this.svg
|
||||
.selectAll(".trace-node")
|
||||
.data(nodes, (d: any) => d.id || (d.id = ++this.i));
|
||||
const nodeEnter = node
|
||||
.enter()
|
||||
.append("g")
|
||||
.attr("transform", `translate(${source.y0},${source.x0})`)
|
||||
.attr("class", "trace-node")
|
||||
.style("opacity", 0)
|
||||
.on("mouseover", function (event: any, d: Trace) {
|
||||
t.tip.show(d, this);
|
||||
})
|
||||
.on("mouseout", function (event: any, d: Trace) {
|
||||
t.tip.hide(d, this);
|
||||
})
|
||||
.on("click", (event: any, d: Trace) => {
|
||||
if (this.handleSelectSpan) {
|
||||
this.handleSelectSpan(d);
|
||||
}
|
||||
});
|
||||
nodeEnter
|
||||
.append("rect")
|
||||
.attr("height", 42)
|
||||
.attr("ry", 2)
|
||||
.attr("rx", 2)
|
||||
.attr("y", -22)
|
||||
.attr("x", 20)
|
||||
.attr("width", "100%")
|
||||
.attr("fill", "rgba(0,0,0,0)");
|
||||
nodeEnter
|
||||
.append("text")
|
||||
.attr("x", 13)
|
||||
.attr("y", 5)
|
||||
.attr("fill", "#E54C17")
|
||||
.html((d: any) => (d.data.isError ? "◉" : ""));
|
||||
nodeEnter
|
||||
.append("text")
|
||||
.attr("class", "node-text")
|
||||
.attr("x", 35)
|
||||
.attr("y", -6)
|
||||
.attr("fill", "#333")
|
||||
.text((d: any) => {
|
||||
if (d.data.label === "TRACE_ROOT") {
|
||||
return "";
|
||||
}
|
||||
return d.data.label.length > 40
|
||||
? `${d.data.label.slice(0, 40)}...`
|
||||
: `${d.data.label}`;
|
||||
});
|
||||
nodeEnter
|
||||
.append("text")
|
||||
.attr("class", "node-text")
|
||||
.attr("x", 35)
|
||||
.attr("y", 12)
|
||||
.attr("fill", "#ccc")
|
||||
.style("font-size", "11px")
|
||||
.text(
|
||||
(d: any) =>
|
||||
`${d.data.layer || ""} ${
|
||||
d.data.component ? "- " + d.data.component : d.data.component || ""
|
||||
}`
|
||||
);
|
||||
nodeEnter
|
||||
.append("rect")
|
||||
.attr("rx", 2)
|
||||
.attr("ry", 2)
|
||||
.attr("height", 4)
|
||||
.attr("width", (d: any) => {
|
||||
if (!d.data.endTime || !d.data.startTime) return 0;
|
||||
return this.xScale(d.data.endTime - d.data.startTime) + 1 || 0;
|
||||
})
|
||||
.attr("x", (d: any) =>
|
||||
!d.data.endTime || !d.data.startTime
|
||||
? 0
|
||||
: this.width * 0.618 -
|
||||
20 -
|
||||
d.y +
|
||||
this.xScale(d.data.startTime - this.min) || 0
|
||||
)
|
||||
.attr("y", -2)
|
||||
.style(
|
||||
"fill",
|
||||
(d: any) =>
|
||||
`${this.sequentialScale(this.list.indexOf(d.data.serviceCode))}`
|
||||
);
|
||||
nodeEnter
|
||||
.transition()
|
||||
.duration(400)
|
||||
.attr("transform", (d: any) => `translate(${d.y},${d.x})`)
|
||||
.style("opacity", 1);
|
||||
nodeEnter
|
||||
.append("circle")
|
||||
.attr("r", 3)
|
||||
.style("cursor", "pointer")
|
||||
.attr("stroke-width", 2.5)
|
||||
.attr("fill", (d: any) =>
|
||||
d._children
|
||||
? `${this.sequentialScale(this.list.indexOf(d.data.serviceCode))}`
|
||||
: "rbga(0,0,0,0)"
|
||||
)
|
||||
.style("stroke", (d: any) =>
|
||||
d.data.label === "TRACE_ROOT"
|
||||
? ""
|
||||
: `${this.sequentialScale(this.list.indexOf(d.data.serviceCode))}`
|
||||
)
|
||||
.on("click", (d: any) => {
|
||||
this.click(d, this);
|
||||
});
|
||||
node
|
||||
.transition()
|
||||
.duration(400)
|
||||
.attr("transform", (d: any) => `translate(${d.y},${d.x})`)
|
||||
.style("opacity", 1)
|
||||
.select("circle")
|
||||
.attr("fill", (d: any) =>
|
||||
d._children
|
||||
? `${this.sequentialScale(this.list.indexOf(d.data.serviceCode))}`
|
||||
: ""
|
||||
);
|
||||
|
||||
// Transition exiting nodes to the parent's new position.
|
||||
node
|
||||
.exit()
|
||||
.transition()
|
||||
.duration(400)
|
||||
.attr("transform", `translate(${source.y},${source.x})`)
|
||||
.style("opacity", 0)
|
||||
.remove();
|
||||
const link = this.svg
|
||||
.selectAll(".trace-link")
|
||||
.data(this.root.links(), function (d: any) {
|
||||
return d.target.id;
|
||||
});
|
||||
|
||||
link
|
||||
.enter()
|
||||
.insert("path", "g")
|
||||
.attr("class", "trace-link")
|
||||
.attr("fill", "rgba(0,0,0,0)")
|
||||
.attr("stroke", "rgba(0, 0, 0, 0.1)")
|
||||
.attr("stroke-width", 2)
|
||||
.attr("d", () => {
|
||||
const o = { x: source.x0 + 35, y: source.y0 };
|
||||
return this.diagonal({ source: o, target: o });
|
||||
})
|
||||
.transition()
|
||||
.duration(400)
|
||||
.attr("d", this.diagonal);
|
||||
|
||||
link.transition().duration(400).attr("d", this.diagonal);
|
||||
|
||||
link
|
||||
.exit()
|
||||
.transition()
|
||||
.duration(400)
|
||||
.attr("d", () => {
|
||||
const o = { x: source.x + 35, y: source.y };
|
||||
return this.diagonal({ source: o, target: o });
|
||||
})
|
||||
.remove();
|
||||
this.root.each(function (d: any) {
|
||||
d.x0 = d.x;
|
||||
d.y0 = d.y;
|
||||
});
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
resize() {
|
||||
if (!this.el) {
|
||||
return;
|
||||
}
|
||||
this.width = this.el.clientWidth - 20;
|
||||
this.height = this.el.clientHeight;
|
||||
this.svg.attr("width", this.width).attr("height", this.height);
|
||||
this.svg.select("g").attr("transform", () => `translate(160, 0)`);
|
||||
const transform = d3.zoomTransform(this.svg).translate(0, 0);
|
||||
d3.zoom().transform(this.svg, transform);
|
||||
}
|
||||
}
|
412
src/views/dashboard/related/trace/utils/d3-trace-tree.ts
Normal file
@ -0,0 +1,412 @@
|
||||
/**
|
||||
* 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 * as d3 from "d3";
|
||||
import d3tip from "d3-tip";
|
||||
import { Trace, Span } from "@/types/trace";
|
||||
|
||||
export default class TraceMap {
|
||||
private i = 0;
|
||||
private el: Nullable<HTMLDivElement> = null;
|
||||
private handleSelectSpan: Nullable<(i: Trace) => void> = null;
|
||||
private topSlow: any = [];
|
||||
private height = 0;
|
||||
private width = 0;
|
||||
private topChild: any[] = [];
|
||||
private body: any = null;
|
||||
private tip: any = null;
|
||||
private svg: any = null;
|
||||
private treemap: any = null;
|
||||
private data: any = null;
|
||||
private row: any = null;
|
||||
private min = 0;
|
||||
private max = 0;
|
||||
private list: string[] = [];
|
||||
private xScale: any = null;
|
||||
private sequentialScale: any = null;
|
||||
private root: any = null;
|
||||
private topSlowMax: number[] = [];
|
||||
private topSlowMin: number[] = [];
|
||||
private topChildMax: number[] = [];
|
||||
private topChildMin: number[] = [];
|
||||
private nodeUpdate: any = null;
|
||||
|
||||
constructor(el: HTMLDivElement, handleSelectSpan: (i: Trace) => void) {
|
||||
this.el = el;
|
||||
this.handleSelectSpan = handleSelectSpan;
|
||||
this.i = 0;
|
||||
this.topSlow = [];
|
||||
this.topChild = [];
|
||||
this.width = el.clientWidth - 20;
|
||||
this.height = el.clientHeight - 30;
|
||||
this.body = d3
|
||||
.select(this.el)
|
||||
.append("svg")
|
||||
.attr("class", "d3-trace-tree")
|
||||
.attr("width", this.width)
|
||||
.attr("height", this.height);
|
||||
this.tip = (d3tip as any)()
|
||||
.attr("class", "d3-tip")
|
||||
.offset([-8, 0])
|
||||
.html(
|
||||
(d: any) => `
|
||||
<div class="mb-5">${d.data.label}</div>
|
||||
${
|
||||
d.data.dur
|
||||
? '<div class="sm">SelfDuration: ' + d.data.dur + "ms</div>"
|
||||
: ""
|
||||
}
|
||||
${
|
||||
d.data.endTime - d.data.startTime
|
||||
? '<div class="sm">TotalDuration: ' +
|
||||
(d.data.endTime - d.data.startTime) +
|
||||
"ms</div>"
|
||||
: ""
|
||||
}
|
||||
`
|
||||
);
|
||||
this.svg = this.body
|
||||
.append("g")
|
||||
.attr("transform", () => `translate(120, 0)`);
|
||||
this.svg.call(this.tip);
|
||||
}
|
||||
resize() {
|
||||
if (!this.el) {
|
||||
return;
|
||||
}
|
||||
this.width = this.el.clientWidth;
|
||||
this.height = this.el.clientHeight + 100;
|
||||
this.body.attr("width", this.width).attr("height", this.height);
|
||||
this.body.select("g").attr("transform", () => `translate(160, 0)`);
|
||||
const transform = d3.zoomTransform(this.body).translate(0, 0);
|
||||
d3.zoom().transform(this.body, transform);
|
||||
}
|
||||
init(data: any, row: any) {
|
||||
this.treemap = d3.tree().size([row.length * 35, this.width]);
|
||||
this.row = row;
|
||||
this.data = data;
|
||||
this.min = Number(d3.min(this.row.map((i: Span) => i.startTime)));
|
||||
this.max = Number(d3.max(this.row.map((i: Span) => i.endTime - this.min)));
|
||||
this.list = Array.from(new Set(this.row.map((i: Span) => i.serviceCode)));
|
||||
this.xScale = d3.scaleLinear().range([0, 100]).domain([0, this.max]);
|
||||
this.sequentialScale = d3
|
||||
.scaleSequential()
|
||||
.domain([0, this.list.length + 1])
|
||||
.interpolator(d3.interpolateCool);
|
||||
|
||||
this.body.call(this.getZoomBehavior(this.svg));
|
||||
this.root = d3.hierarchy(this.data, (d) => d.children);
|
||||
this.root.x0 = this.height / 2;
|
||||
this.root.y0 = 0;
|
||||
this.topSlow = [];
|
||||
this.topChild = [];
|
||||
const that = this;
|
||||
this.root.children.forEach(collapse);
|
||||
this.topSlowMax = this.topSlow.sort((a: number, b: number) => b - a)[0];
|
||||
this.topSlowMin = this.topSlow.sort((a: number, b: number) => b - a)[4];
|
||||
this.topChildMax = this.topChild.sort((a: number, b: number) => b - a)[0];
|
||||
this.topChildMin = this.topChild.sort((a: number, b: number) => b - a)[4];
|
||||
this.update(this.root);
|
||||
// Collapse the node and all it's children
|
||||
function collapse(d: any) {
|
||||
if (d.children) {
|
||||
let dur = d.data.endTime - d.data.startTime;
|
||||
d.children.forEach((i: any) => {
|
||||
dur -= i.data.endTime - i.data.startTime;
|
||||
});
|
||||
d.dur = dur < 0 ? 0 : dur;
|
||||
that.topSlow.push(dur);
|
||||
that.topChild.push(d.children.length);
|
||||
d.childrenLength = d.children.length;
|
||||
d.children.forEach(collapse);
|
||||
}
|
||||
}
|
||||
}
|
||||
draw() {
|
||||
this.update(this.root);
|
||||
}
|
||||
update(source: any) {
|
||||
const that: any = this;
|
||||
const treeData = this.treemap(this.root);
|
||||
const nodes = treeData.descendants(),
|
||||
links = treeData.descendants().slice(1);
|
||||
|
||||
nodes.forEach(function (d: any) {
|
||||
d.y = d.depth * 140;
|
||||
});
|
||||
|
||||
const node = this.svg.selectAll("g.node").data(nodes, (d: any) => {
|
||||
return d.id || (d.id = ++this.i);
|
||||
});
|
||||
|
||||
const nodeEnter = node
|
||||
.enter()
|
||||
.append("g")
|
||||
.attr("class", "node")
|
||||
.attr("cursor", "pointer")
|
||||
.attr("transform", function () {
|
||||
return "translate(" + source.y0 + "," + source.x0 + ")";
|
||||
})
|
||||
.on("mouseover", function (event: any, d: any) {
|
||||
that.tip.show(d, this);
|
||||
if (!that.timeUpdate) {
|
||||
return;
|
||||
}
|
||||
const _node = that.timeUpdate._groups[0].filter(
|
||||
(group: any) => group.__data__.id === that.i + 1
|
||||
);
|
||||
if (_node.length) {
|
||||
that.timeTip.show(d, _node[0].children[1]);
|
||||
}
|
||||
})
|
||||
.on("mouseout", function (event: any, d: any) {
|
||||
that.tip.hide(d, this);
|
||||
if (!that.timeUpdate) {
|
||||
return;
|
||||
}
|
||||
const _node = that.timeUpdate._groups[0].filter(
|
||||
(group: any) => group.__data__.id === that.i + 1
|
||||
);
|
||||
if (_node.length) {
|
||||
that.timeTip.hide(d, _node[0].children[1]);
|
||||
}
|
||||
})
|
||||
.on("click", function (event: any, d: any) {
|
||||
that.handleSelectSpan(d);
|
||||
});
|
||||
|
||||
nodeEnter
|
||||
.append("circle")
|
||||
.attr("class", "node")
|
||||
.attr("r", 1e-6)
|
||||
.style("fill", (d: any) =>
|
||||
d._children
|
||||
? this.sequentialScale(this.list.indexOf(d.data.serviceCode))
|
||||
: "#fff"
|
||||
)
|
||||
.attr("stroke", (d: any) =>
|
||||
this.sequentialScale(this.list.indexOf(d.data.serviceCode))
|
||||
)
|
||||
.attr("stroke-width", 2.5);
|
||||
|
||||
nodeEnter
|
||||
.append("text")
|
||||
.attr("font-size", 11)
|
||||
.attr("dy", "-0.5em")
|
||||
.attr("x", function (d: any) {
|
||||
return d.children || d._children ? -15 : 15;
|
||||
})
|
||||
.attr("text-anchor", function (d: any) {
|
||||
return d.children || d._children ? "end" : "start";
|
||||
})
|
||||
.text((d: any) =>
|
||||
d.data.label.length > 19
|
||||
? (d.data.isError ? "◉ " : "") + d.data.label.slice(0, 19) + "..."
|
||||
: (d.data.isError ? "◉ " : "") + d.data.label
|
||||
)
|
||||
.style("fill", (d: any) => (!d.data.isError ? "#3d444f" : "#E54C17"));
|
||||
nodeEnter
|
||||
.append("text")
|
||||
.attr("class", "node-text")
|
||||
.attr("x", function (d: any) {
|
||||
return d.children || d._children ? -15 : 15;
|
||||
})
|
||||
.attr("dy", "1em")
|
||||
.attr("fill", "#bbb")
|
||||
.attr("text-anchor", function (d: any) {
|
||||
return d.children || d._children ? "end" : "start";
|
||||
})
|
||||
.style("font-size", "10px")
|
||||
.text(
|
||||
(d: any) =>
|
||||
`${d.data.layer || ""}${
|
||||
d.data.component ? "-" + d.data.component : d.data.component || ""
|
||||
}`
|
||||
);
|
||||
nodeEnter
|
||||
.append("rect")
|
||||
.attr("rx", 1)
|
||||
.attr("ry", 1)
|
||||
.attr("height", 2)
|
||||
.attr("width", 100)
|
||||
.attr("x", function (d: any) {
|
||||
return d.children || d._children ? "-110" : "10";
|
||||
})
|
||||
.attr("y", -1)
|
||||
.style("fill", "#00000020");
|
||||
nodeEnter
|
||||
.append("rect")
|
||||
.attr("rx", 1)
|
||||
.attr("ry", 1)
|
||||
.attr("height", 2)
|
||||
.attr("width", (d: any) => {
|
||||
if (!d.data.endTime || !d.data.startTime) return 0;
|
||||
return this.xScale(d.data.endTime - d.data.startTime) + 1 || 0;
|
||||
})
|
||||
.attr("x", (d: any) => {
|
||||
if (!d.data.endTime || !d.data.startTime) {
|
||||
return 0;
|
||||
}
|
||||
if (d.children || d._children) {
|
||||
return -110 + this.xScale(d.data.startTime - this.min);
|
||||
}
|
||||
return 10 + this.xScale(d.data.startTime - this.min);
|
||||
})
|
||||
.attr("y", -1)
|
||||
.style("fill", (d: any) =>
|
||||
this.sequentialScale(this.list.indexOf(d.data.serviceCode))
|
||||
);
|
||||
const nodeUpdate = nodeEnter.merge(node);
|
||||
this.nodeUpdate = nodeUpdate;
|
||||
nodeUpdate
|
||||
.transition()
|
||||
.duration(600)
|
||||
.attr("transform", function (d: any) {
|
||||
return "translate(" + d.y + "," + d.x + ")";
|
||||
});
|
||||
nodeUpdate
|
||||
.select("circle.node")
|
||||
.attr("r", 5)
|
||||
.style("fill", (d: any) =>
|
||||
d._children
|
||||
? this.sequentialScale(this.list.indexOf(d.data.serviceCode))
|
||||
: "#fff"
|
||||
)
|
||||
.attr("cursor", "pointer")
|
||||
.on("click", (d: any) => {
|
||||
click(d);
|
||||
});
|
||||
const nodeExit = node
|
||||
.exit()
|
||||
.transition()
|
||||
.duration(600)
|
||||
.attr("transform", function () {
|
||||
return "translate(" + source.y + "," + source.x + ")";
|
||||
})
|
||||
.remove();
|
||||
|
||||
nodeExit.select("circle").attr("r", 1e-6);
|
||||
|
||||
nodeExit.select("text").style("fill-opacity", 1e-6);
|
||||
|
||||
const link = this.svg
|
||||
.selectAll("path.tree-link")
|
||||
.data(links, function (d: { id: string }) {
|
||||
return d.id;
|
||||
})
|
||||
.style("stroke-width", 1.5);
|
||||
|
||||
const linkEnter = link
|
||||
.enter()
|
||||
.insert("path", "g")
|
||||
.attr("class", "tree-link")
|
||||
.attr("d", function () {
|
||||
const o = { x: source.x0, y: source.y0 };
|
||||
return diagonal(o, o);
|
||||
})
|
||||
.attr("stroke", "rgba(0, 0, 0, 0.1)")
|
||||
.style("stroke-width", 1.5)
|
||||
.style("fill", "none");
|
||||
|
||||
const linkUpdate = linkEnter.merge(link);
|
||||
linkUpdate
|
||||
.transition()
|
||||
.duration(600)
|
||||
.attr("d", function (d: any) {
|
||||
return diagonal(d, d.parent);
|
||||
});
|
||||
link
|
||||
.exit()
|
||||
.transition()
|
||||
.duration(600)
|
||||
.attr("d", function () {
|
||||
const o = { x: source.x, y: source.y };
|
||||
return diagonal(o, o);
|
||||
})
|
||||
.style("stroke-width", 1.5)
|
||||
.remove();
|
||||
|
||||
nodes.forEach(function (d: any) {
|
||||
d.x0 = d.x;
|
||||
d.y0 = d.y;
|
||||
});
|
||||
function diagonal(s: any, d: any) {
|
||||
return `M ${s.y} ${s.x}
|
||||
C ${(s.y + d.y) / 2} ${s.x}, ${(s.y + d.y) / 2} ${d.x},
|
||||
${d.y} ${d.x}`;
|
||||
}
|
||||
function click(d: any) {
|
||||
if (d.children) {
|
||||
d._children = d.children;
|
||||
d.children = null;
|
||||
} else {
|
||||
d.children = d._children;
|
||||
d._children = null;
|
||||
}
|
||||
that.update(d);
|
||||
}
|
||||
}
|
||||
setDefault() {
|
||||
d3.selectAll(".time-inner").style("opacity", 1);
|
||||
d3.selectAll(".time-inner-duration").style("opacity", 0);
|
||||
d3.selectAll(".trace-tree-node-selfdur").style("opacity", 0);
|
||||
d3.selectAll(".trace-tree-node-selfchild").style("opacity", 0);
|
||||
this.nodeUpdate._groups[0].forEach((i: any) => {
|
||||
d3.select(i).style("opacity", 1);
|
||||
});
|
||||
}
|
||||
getTopChild() {
|
||||
d3.selectAll(".time-inner").style("opacity", 1);
|
||||
d3.selectAll(".time-inner-duration").style("opacity", 0);
|
||||
d3.selectAll(".trace-tree-node-selfdur").style("opacity", 0);
|
||||
d3.selectAll(".trace-tree-node-selfchild").style("opacity", 1);
|
||||
this.nodeUpdate._groups[0].forEach((i: any) => {
|
||||
d3.select(i).style("opacity", 0.2);
|
||||
if (
|
||||
i.__data__.data.children.length >= this.topChildMin &&
|
||||
i.__data__.data.children.length <= this.topChildMax
|
||||
) {
|
||||
d3.select(i).style("opacity", 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
getTopSlow() {
|
||||
d3.selectAll(".time-inner").style("opacity", 0);
|
||||
d3.selectAll(".time-inner-duration").style("opacity", 1);
|
||||
d3.selectAll(".trace-tree-node-selfchild").style("opacity", 0);
|
||||
d3.selectAll(".trace-tree-node-selfdur").style("opacity", 1);
|
||||
this.nodeUpdate._groups[0].forEach((i: any) => {
|
||||
d3.select(i).style("opacity", 0.2);
|
||||
if (
|
||||
i.__data__.data.dur >= this.topSlowMin &&
|
||||
i.__data__.data.dur <= this.topSlowMax
|
||||
) {
|
||||
d3.select(i).style("opacity", 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
getZoomBehavior(g: any) {
|
||||
return d3
|
||||
.zoom()
|
||||
.scaleExtent([0.3, 10])
|
||||
.on("zoom", (d: any) => {
|
||||
g.attr("transform", d3.zoomTransform(this.svg.node())).attr(
|
||||
`translate(${d.transform.x},${d.transform.y})scale(${d.transform.k})`
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
332
src/views/dashboard/related/trace/utils/trace-table.ts
Normal file
@ -0,0 +1,332 @@
|
||||
/**
|
||||
* 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 {
|
||||
Ref,
|
||||
Span,
|
||||
StatisticsSpan,
|
||||
StatisticsGroupRef,
|
||||
TraceTreeRef,
|
||||
} from "@/types/trace";
|
||||
import lodash from "lodash";
|
||||
|
||||
export default class TraceUtil {
|
||||
public static buildTraceDataList(data: Span[]): string[] {
|
||||
return Array.from(new Set(data.map((span: Span) => span.serviceCode)));
|
||||
}
|
||||
|
||||
public static changeTree(data: Span[], cureentTraceId: string) {
|
||||
const segmentIdList: Span[] = [];
|
||||
const traceTreeRef: any = this.changeTreeCore(data);
|
||||
traceTreeRef.segmentIdGroup.forEach((segmentId: string) => {
|
||||
if (traceTreeRef.segmentMap.get(segmentId).refs) {
|
||||
traceTreeRef.segmentMap.get(segmentId).refs.forEach((ref: Ref) => {
|
||||
if (ref.traceId === cureentTraceId) {
|
||||
this.traverseTree(
|
||||
traceTreeRef.segmentMap.get(ref.parentSegmentId) as Span,
|
||||
ref.parentSpanId,
|
||||
ref.parentSegmentId,
|
||||
traceTreeRef.segmentMap.get(segmentId) as Span
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
// set a breakpoint at this line
|
||||
traceTreeRef.segmentMap.forEach((value: Span) => {
|
||||
if ((value.refs && value.refs.length === 0) || !value.refs) {
|
||||
segmentIdList.push(value as Span);
|
||||
}
|
||||
});
|
||||
segmentIdList.forEach((segmentId: Span) => {
|
||||
this.collapse(segmentId);
|
||||
});
|
||||
return segmentIdList;
|
||||
}
|
||||
|
||||
public static changeStatisticsTree(data: Span[]): Map<string, Span[]> {
|
||||
const result = new Map<string, Span[]>();
|
||||
const traceTreeRef = this.changeTreeCore(data);
|
||||
traceTreeRef.segmentMap.forEach((span) => {
|
||||
const groupRef = span.endpointName + ":" + span.type;
|
||||
if (span.children && span.children.length > 0) {
|
||||
this.calculationChildren(span.children, result);
|
||||
this.collapse(span);
|
||||
}
|
||||
if (result.get(groupRef) === undefined) {
|
||||
result.set(groupRef, []);
|
||||
result.get(groupRef)!.push(span);
|
||||
} else {
|
||||
result.get(groupRef)!.push(span);
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
private static changeTreeCore(data: Span[]): TraceTreeRef {
|
||||
// set a breakpoint at this line
|
||||
if (data.length === 0) {
|
||||
return {
|
||||
segmentMap: new Map(),
|
||||
segmentIdGroup: [],
|
||||
};
|
||||
}
|
||||
const segmentGroup: any = {};
|
||||
const segmentMap: Map<string, Span> = new Map();
|
||||
const segmentIdGroup: string[] = [];
|
||||
const fixSpans: Span[] = [];
|
||||
const segmentHeaders: Span[] = [];
|
||||
data.forEach((span) => {
|
||||
if (span.parentSpanId === -1) {
|
||||
segmentHeaders.push(span);
|
||||
} else {
|
||||
const index = data.findIndex((patchSpan: Span) => {
|
||||
return (
|
||||
patchSpan.segmentId === span.segmentId &&
|
||||
patchSpan.spanId === span.spanId - 1
|
||||
);
|
||||
});
|
||||
const fixSpanKeyContent = {
|
||||
traceId: span.traceId,
|
||||
segmentId: span.segmentId,
|
||||
spanId: span.spanId - 1,
|
||||
parentSpanId: span.spanId - 2,
|
||||
};
|
||||
if (index === -1 && !lodash.find(fixSpans, fixSpanKeyContent)) {
|
||||
fixSpans.push({
|
||||
...fixSpanKeyContent,
|
||||
refs: [],
|
||||
endpointName: `VNode: ${span.segmentId}`,
|
||||
serviceCode: "VirtualNode",
|
||||
type: `[Broken] ${span.type}`,
|
||||
peer: "",
|
||||
component: `VirtualNode: #${span.spanId - 1}`,
|
||||
isError: true,
|
||||
isBroken: true,
|
||||
layer: "Broken",
|
||||
tags: [],
|
||||
logs: [],
|
||||
startTime: 0,
|
||||
endTime: 0,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
segmentHeaders.forEach((span) => {
|
||||
if (span.refs && span.refs.length) {
|
||||
span.refs.forEach((ref) => {
|
||||
const index = data.findIndex((patchSpan: Span) => {
|
||||
return (
|
||||
ref.parentSegmentId === patchSpan.segmentId &&
|
||||
ref.parentSpanId === patchSpan.spanId
|
||||
);
|
||||
});
|
||||
if (index === -1) {
|
||||
// create a known broken node.
|
||||
const parentSpanId: number = ref.parentSpanId;
|
||||
const fixSpanKeyContent = {
|
||||
traceId: ref.traceId,
|
||||
segmentId: ref.parentSegmentId,
|
||||
spanId: parentSpanId,
|
||||
parentSpanId: parentSpanId > -1 ? 0 : -1,
|
||||
};
|
||||
if (lodash.find(fixSpans, fixSpanKeyContent)) {
|
||||
fixSpans.push({
|
||||
...fixSpanKeyContent,
|
||||
refs: [],
|
||||
endpointName: `VNode: ${ref.parentSegmentId}`,
|
||||
serviceCode: "VirtualNode",
|
||||
type: `[Broken] ${ref.type}`,
|
||||
peer: "",
|
||||
component: `VirtualNode: #${parentSpanId}`,
|
||||
isError: true,
|
||||
isBroken: true,
|
||||
layer: "Broken",
|
||||
tags: [],
|
||||
logs: [],
|
||||
startTime: 0,
|
||||
endTime: 0,
|
||||
});
|
||||
}
|
||||
// if root broken node is not exist, create a root broken node.
|
||||
if (fixSpanKeyContent.parentSpanId > -1) {
|
||||
const fixRootSpanKeyContent = {
|
||||
traceId: ref.traceId,
|
||||
segmentId: ref.parentSegmentId,
|
||||
spanId: 0,
|
||||
parentSpanId: -1,
|
||||
};
|
||||
if (!lodash.find(fixSpans, fixRootSpanKeyContent)) {
|
||||
fixSpans.push({
|
||||
...fixRootSpanKeyContent,
|
||||
refs: [],
|
||||
endpointName: `VNode: ${ref.parentSegmentId}`,
|
||||
serviceCode: "VirtualNode",
|
||||
type: `[Broken] ${ref.type}`,
|
||||
peer: "",
|
||||
component: `VirtualNode: #0`,
|
||||
isError: true,
|
||||
isBroken: true,
|
||||
layer: "Broken",
|
||||
tags: [],
|
||||
logs: [],
|
||||
startTime: 0,
|
||||
endTime: 0,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
[...fixSpans, ...data].forEach((fixSpan: Span) => {
|
||||
fixSpan.label = fixSpan.endpointName || "no operation name";
|
||||
fixSpan.children = [];
|
||||
const id = fixSpan.segmentId || "top";
|
||||
if (segmentGroup[id] === undefined) {
|
||||
segmentIdGroup.push(id);
|
||||
segmentGroup[id] = [];
|
||||
segmentGroup[id].push(fixSpan);
|
||||
} else {
|
||||
segmentGroup[id].push(fixSpan);
|
||||
}
|
||||
});
|
||||
|
||||
segmentIdGroup.forEach((segmentId: string) => {
|
||||
const currentSegmentSet = segmentGroup[segmentId].sort(
|
||||
(a: Span, b: Span) => b.parentSpanId - a.parentSpanId
|
||||
);
|
||||
currentSegmentSet.forEach((curSegment: Span) => {
|
||||
const index = currentSegmentSet.findIndex(
|
||||
(curSegment2: Span) => curSegment2.spanId === curSegment.parentSpanId
|
||||
);
|
||||
if (index !== -1) {
|
||||
if (
|
||||
(currentSegmentSet[index].isBroken &&
|
||||
currentSegmentSet[index].parentSpanId === -1) ||
|
||||
!currentSegmentSet[index].isBroken
|
||||
) {
|
||||
currentSegmentSet[index].children.push(curSegment);
|
||||
currentSegmentSet[index].children.sort(
|
||||
(a: Span, b: Span) => a.spanId - b.spanId
|
||||
);
|
||||
}
|
||||
}
|
||||
if (curSegment.isBroken) {
|
||||
const children = lodash.filter(data, (span: Span) => {
|
||||
return lodash.find(span.refs, {
|
||||
traceId: curSegment.traceId,
|
||||
parentSegmentId: curSegment.segmentId,
|
||||
parentSpanId: curSegment.spanId,
|
||||
});
|
||||
}) as Span[];
|
||||
if (children.length) {
|
||||
curSegment.children = curSegment.children || [];
|
||||
curSegment.children.push(...children);
|
||||
}
|
||||
}
|
||||
});
|
||||
segmentMap.set(
|
||||
segmentId,
|
||||
currentSegmentSet[currentSegmentSet.length - 1]
|
||||
);
|
||||
});
|
||||
|
||||
return {
|
||||
segmentMap,
|
||||
segmentIdGroup,
|
||||
};
|
||||
}
|
||||
|
||||
private static collapse(span: Span) {
|
||||
if (span.children) {
|
||||
let dur = span.endTime - span.startTime;
|
||||
span.children.forEach((chlid: Span) => {
|
||||
dur -= chlid.endTime - chlid.startTime;
|
||||
});
|
||||
span.dur = dur < 0 ? 0 : dur;
|
||||
span.children.forEach((chlid) => this.collapse(chlid));
|
||||
}
|
||||
}
|
||||
|
||||
private static traverseTree(
|
||||
node: Span,
|
||||
spanId: number,
|
||||
segmentId: string,
|
||||
childNode: Span
|
||||
) {
|
||||
if (!node || node.isBroken) {
|
||||
return;
|
||||
}
|
||||
if (node.spanId === spanId && node.segmentId === segmentId) {
|
||||
node.children!.push(childNode);
|
||||
return;
|
||||
}
|
||||
if (node.children && node.children.length > 0) {
|
||||
for (const grandchild of node.children) {
|
||||
this.traverseTree(grandchild, spanId, segmentId, childNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static getSpanGroupData(
|
||||
groupspans: Span[],
|
||||
groupRef: StatisticsGroupRef
|
||||
): StatisticsSpan {
|
||||
let maxTime = 0;
|
||||
let minTime = 0;
|
||||
let sumTime = 0;
|
||||
const count = groupspans.length;
|
||||
groupspans.forEach((groupspan: Span) => {
|
||||
const duration = groupspan.dur || 0;
|
||||
if (duration > maxTime) {
|
||||
maxTime = duration;
|
||||
}
|
||||
if (duration < minTime) {
|
||||
minTime = duration;
|
||||
}
|
||||
sumTime = sumTime + duration;
|
||||
});
|
||||
const avgTime = count === 0 ? 0 : sumTime / count;
|
||||
return {
|
||||
groupRef,
|
||||
maxTime,
|
||||
minTime,
|
||||
sumTime,
|
||||
avgTime,
|
||||
count,
|
||||
};
|
||||
}
|
||||
|
||||
private static calculationChildren(
|
||||
nodes: Span[],
|
||||
result: Map<string, Span[]>
|
||||
): void {
|
||||
nodes.forEach((node: Span) => {
|
||||
const groupRef = node.endpointName + ":" + node.type;
|
||||
if (node.children && node.children.length > 0) {
|
||||
this.calculationChildren(node.children, result);
|
||||
}
|
||||
if (result.get(groupRef) === undefined) {
|
||||
result.set(groupRef, []);
|
||||
result.get(groupRef)!.push(node);
|
||||
} else {
|
||||
result.get(groupRef)!.push(node);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|