43 Commits

Author SHA1 Message Date
Fine0830
b88356ba46 fix: polish the network profiling widget, bugfix (#151) 2022-08-30 13:20:45 +08:00
Fine0830
d8889f1787 fix: set configurations for process topology (#150) 2022-08-29 17:32:49 +08:00
Fine0830
1a989a1434 fix widget name (#149) 2022-08-29 17:02:42 +08:00
Fine0830
82b348a766 feat: add a calculation to convert nanoseconds to milliseconds (#148) 2022-08-26 16:26:25 +08:00
Fine0830
42b20660e4 fix: set selector parameters for metrics, optimize the process topology (#147) 2022-08-26 12:42:12 +08:00
Fine0830
cb3aa940b3 feat: create markers on process topology calls (#146) 2022-08-25 12:03:13 +08:00
Fine0830
87a5553e6d fix: update process widget and query process metrics (#145) 2022-08-24 17:39:40 +08:00
dependabot[bot]
e17562a766 build(deps): bump moment and @vue/cli-plugin-e2e-cypress (#144) 2022-08-23 13:56:13 +08:00
pg.yang
fdfdaab47b Add Nats icon for Java plugin (#142)
Co-authored-by: 吴晟 Wu Sheng <wu.sheng@foxmail.com>
2022-08-23 13:48:54 +08:00
Fine0830
a4fc5192ac feat: Implement the network profiling widget (#132) 2022-08-23 13:41:05 +08:00
yswdqz
ffabc7c7a7 Fix a typo about PostgreSQL Monitoring. (#141) 2022-08-22 23:21:27 +08:00
Fine0830
2fd5fb9b1e add sub menu for PostgreSQL layer (#140) 2022-08-22 21:48:02 +08:00
Fine0830
adb457d660 fix: pick calendar with a wrong time range and set a unique value for dashboard grid key (#139) 2022-08-18 16:29:36 +08:00
Fine0830
9c0bb988e6 feat: support the process dashboard and create the time range text widget (#138) 2022-08-15 16:49:00 +08:00
pg.yang
973b51e9ca Add Micronaut icon for Java plugin (#137) 2022-08-10 17:00:22 +08:00
Fine0830
f5bcd5da2e feat: add a calculation to convert seconds to days (#135) 2022-08-08 10:26:36 +08:00
Fine0830
732b834749 feat: add the MYSQL layer and update layer routers (#134) 2022-08-07 17:16:44 +08:00
Fine0830
4b43196bc2 fix query order for trace list (#133) 2022-08-05 17:02:29 +08:00
Fine0830
b01565b2b8 fix: set the value(SECOND) of the step filed for queries (#131) 2022-07-29 20:54:57 +08:00
Fine0830
3b3e790dd9 feat: event widget associates with trace and log widget (#130) 2022-07-29 16:34:36 +08:00
Fine0830
dc8e4bf273 fix: the log details don't display when the log fields don't have value (#129) 2022-07-27 19:31:17 +08:00
Fine0830
2ba3c67d31 feat: the log widget and the trace widget associate with each other, remove log tables on the trace widget (#128) 2022-07-27 16:24:34 +08:00
Fine0830
673b1a41a8 refactor: update the tags component (#127) 2022-07-25 17:34:05 +08:00
Fine0830
c7079ea17c fix short time range (#125) 2022-07-21 10:06:51 +08:00
Fine0830
017f5bf709 fix errors (#124) 2022-07-20 19:42:41 +08:00
Fine0830
bec86e80fd fix: update dashboard list with using the search box (#123) 2022-07-20 16:53:01 +08:00
Fine0830
ec7a8bbfa9 fix: update event associations with the duration step (#122) 2022-07-19 14:11:41 +08:00
Fine0830
4e022ff29a fix short time range (#121) 2022-07-19 12:47:03 +08:00
Fine0830
42ead4a572 feat: Enhance associations for the Event widget (#120) 2022-07-19 11:57:26 +08:00
Fine0830
e144b43267 fix: update widget name rules and the association selector options (#119) 2022-07-14 16:40:06 +08:00
Fine0830
b5c07e023b fix view mode (#118) 2022-07-13 10:34:30 +08:00
horochx
04d109d0e3 fix: hide the copy button when db.statement is empty (#117) 2022-07-11 14:53:03 +08:00
Fine0830
024cd1195d fix tag dropdown (#116) 2022-07-11 10:22:09 +08:00
Fine0830
7fbd6170de feat: Implement an association between widgets(line, bar, area graphs) with time (#115) 2022-07-08 16:17:17 +08:00
jiang1997
3ff3d5d1cd Add Python Bottle Plugin Logo (#114) 2022-07-05 18:43:32 +08:00
Fine0830
2230702d97 feat: Implement the Event widget (#112) 2022-06-22 19:11:02 +08:00
horochx
9ad9362935 fix: SpanDetail text overlap (#113) 2022-06-22 18:54:31 +08:00
Fine0830
2be0a84d48 fix: optimize widgets (#111) 2022-06-16 15:19:17 +08:00
Fine0830
4c762a2458 fix: optimize widgets (#110) 2022-06-15 19:24:07 +08:00
drgnchan
7813c92673 fix:clear interval fail when close autoRefresh (#108) 2022-06-15 10:06:53 +08:00
Fine0830
f12d7899e4 fix router (#109) 2022-06-14 22:23:23 +08:00
Fine0830
b697fe4713 feat: set a url parameter to activate tab index (#107) 2022-06-14 17:01:11 +08:00
Fine0830
0828f8a7aa fix: update query conditions for the browser logs (#106) 2022-06-14 11:26:53 +08:00
142 changed files with 6552 additions and 1478 deletions

1350
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "skywalking-booster-ui",
"version": "9.1.0",
"version": "9.3.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
@@ -19,12 +19,12 @@
"lodash": "^4.17.21",
"monaco-editor": "^0.27.0",
"pinia": "^2.0.5",
"vis-timeline": "^7.5.1",
"vue": "^3.0.0",
"vue-grid-layout": "^3.0.0-beta1",
"vue-i18n": "^9.1.9",
"vue-router": "^4.0.0-0",
"vue-types": "^4.1.1",
"vuex": "^4.0.0-0"
"vue-types": "^4.1.1"
},
"devDependencies": {
"@types/d3": "^7.1.0",
@@ -36,7 +36,7 @@
"@typescript-eslint/eslint-plugin": "^4.18.0",
"@typescript-eslint/parser": "^4.18.0",
"@vue/cli-plugin-babel": "~4.5.0",
"@vue/cli-plugin-e2e-cypress": "~4.5.0",
"@vue/cli-plugin-e2e-cypress": "~5.0.8",
"@vue/cli-plugin-eslint": "~4.5.0",
"@vue/cli-plugin-router": "~4.5.0",
"@vue/cli-plugin-typescript": "~4.5.0",

View 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="1655799536378" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="9286" width="48" height="48"><path d="M563.2 614.4v51.2c0 30.72-20.48 51.2-51.2 51.2s-51.2-20.48-51.2-51.2v-51.2H409.6c-30.72 0-51.2-20.48-51.2-51.2s20.48-51.2 51.2-51.2h51.2V460.8c0-30.72 20.48-51.2 51.2-51.2s51.2 20.48 51.2 51.2v51.2h51.2c30.72 0 51.2 20.48 51.2 51.2s-20.48 51.2-51.2 51.2h-51.2z m51.2-563.2c158.72 15.36 281.6 143.36 281.6 307.2v512c0 56.32-46.08 102.4-102.4 102.4h-563.2c-56.32 0-102.4-46.08-102.4-102.4V153.6c0-56.32 46.08-102.4 102.4-102.4H614.4z m163.84 230.4c-25.6-61.44-76.8-107.52-138.24-122.88v71.68c0 30.72 20.48 51.2 51.2 51.2h87.04zM537.6 153.6h-256c-30.72 0-51.2 20.48-51.2 51.2v614.4c0 30.72 20.48 51.2 51.2 51.2h460.8c30.72 0 51.2-20.48 51.2-51.2V384h-153.6c-56.32 0-102.4-46.08-102.4-102.4V153.6z" fill="#707070" p-id="9287"></path></svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -12,4 +12,4 @@ 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="1648717513168" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="15451" width="48" height="48"><path d="M810.666667 213.333333v597.333334H213.333333V213.333333h597.333334m85.333333-85.333333H128v768h768V128z m-512 170.666667h-85.333333v85.333333h85.333333z m170.666667 0h-85.333334v85.333333h85.333334z m-170.666667 170.666666h-85.333333v85.333334h85.333333z m170.666667 0h-85.333334v85.333334h85.333334z m-170.666667 170.666667h-85.333333v85.333333h85.333333z m170.666667 0h-85.333334v85.333333h85.333334z m170.666666-341.333333h-85.333333v85.333333h85.333333z m0 170.666666h-85.333333v85.333334h85.333333z m0 170.666667h-85.333333v85.333333h85.333333z" p-id="15452" fill="#515151"></path></svg>
<svg t="1648717513168" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="15451" width="48" height="48"><path d="M810.666667 213.333333v597.333334H213.333333V213.333333h597.333334m85.333333-85.333333H128v768h768V128z m-512 170.666667h-85.333333v85.333333h85.333333z m170.666667 0h-85.333334v85.333333h85.333334z m-170.666667 170.666666h-85.333333v85.333334h85.333333z m170.666667 0h-85.333334v85.333334h85.333334z m-170.666667 170.666667h-85.333333v85.333333h85.333333z m170.666667 0h-85.333334v85.333333h85.333334z m170.666666-341.333333h-85.333333v85.333333h85.333333z m0 170.666666h-85.333333v85.333334h85.333333z m0 170.666667h-85.333333v85.333333h85.333333z" p-id="15452"></path></svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View 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="1655695739627" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2218" width="48" height="48"><path d="M173.292308 177.230769C86.646154 265.846154 39.384615 382.030769 39.384615 504.123077 39.384615 531.692308 61.046154 551.384615 86.646154 551.384615s47.261538-21.661538 47.261538-47.261538c-1.969231-96.492308 37.415385-189.046154 106.338462-257.969231s163.446154-106.338462 257.969231-106.338461c27.569231 0 47.261538-21.661538 47.261538-47.261539 0-27.569231-21.661538-47.261538-47.261538-47.261538C378.092308 43.323077 259.938462 90.584615 173.292308 177.230769z m57.107692 326.892308c0 27.569231 19.692308 47.261538 47.261538 47.261538s47.261538-21.661538 47.261539-47.261538c0-45.292308 17.723077-90.584615 51.2-122.092308 33.476923-33.476923 76.8-49.230769 122.092308-51.2 27.569231 0 47.261538-21.661538 47.261538-47.261538 0-27.569231-19.692308-47.261538-47.261538-47.261539-70.892308 0-139.815385 27.569231-191.015385 76.8-7.876923 9.846154-80.738462 82.707692-76.8 191.015385z m665.6-204.8c-17.723077-23.630769-41.353846-51.2-45.292308-55.138462-5.907692-3.938462-13.784615-7.876923-21.661538-7.876923-7.876923 0-15.753846 1.969231-19.692308 7.876923L610.461538 441.107692c-47.261538-39.384615-118.153846-37.415385-163.446153 7.876923-45.292308 45.292308-47.261538 116.184615-7.876923 163.446154l-191.015385 191.015385c-5.907692 5.907692-9.846154 13.784615-9.846154 21.661538 0 9.846154 3.938462 19.692308 11.815385 25.6l53.16923 39.384616c72.861538 57.107692 163.446154 88.615385 259.938462 88.615384 232.369231 0 421.415385-189.046154 421.415385-421.415384 0-94.523077-33.476923-185.107692-88.615385-257.969231z" p-id="2219" fill="#707070"></path></svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View 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="M7.406 8.578l4.594 4.594 4.594-4.594 1.406 1.406-6 6-6-6z"></path>
</svg>

After

Width:  |  Height:  |  Size: 946 B

View 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="M7.406 15.422l-1.406-1.406 6-6 6 6-1.406 1.406-4.594-4.594z"></path>
</svg>

After

Width:  |  Height:  |  Size: 948 B

View 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="M18.984 11.016v-2.016h-3.984v-3.984h-2.016v3.984h-3.984v2.016h3.984v3.984h2.016v-3.984h3.984zM20.016 2.016q0.797 0 1.383 0.586t0.586 1.383v12q0 0.797-0.586 1.406t-1.383 0.609h-12q-0.797 0-1.406-0.609t-0.609-1.406v-12q0-0.797 0.609-1.383t1.406-0.586h12zM3.984 6v14.016h14.016v1.969h-14.016q-0.797 0-1.383-0.586t-0.586-1.383v-14.016h1.969z"></path>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.7 KiB

View 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. -->
<svg t="1660976558460" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="813" width="48" height="48">
<path d="M498.596 482.29H345.42v57.308h210.478V274.197h-57.301V482.29z m0 0M577.685 644.985h379.88v57.302h-379.88v-57.302z m0 0M577.685 773.765h379.88v57.307h-379.88v-57.307z m0 0M577.685 902.55h379.88v57.307h-379.88V902.55z m0 0" p-id="814"></path>
<path d="M102.523 382.29a28.668 28.668 0 0 0 23.367 2.56l190.81-61.886c15.053-4.883 23.298-21.04 18.415-36.09-4.882-15.052-21.04-23.297-36.093-18.415l-123.346 40c15.994-26.117 35.17-50.538 57.37-72.745 73.768-73.767 171.847-114.388 276.169-114.388 104.32 0 202.395 40.622 276.161 114.388S899.77 407.56 899.77 511.882c0 26.428-2.616 52.45-7.71 77.78h58.303c4.465-25.499 6.709-51.47 6.709-77.78 0-60.45-11.846-119.102-35.205-174.336-22.56-53.335-54.85-101.227-95.969-142.35-41.122-41.122-89.017-73.408-142.348-95.968-55.233-23.361-113.89-35.207-174.334-35.207-60.45 0-119.107 11.846-174.337 35.208-53.335 22.56-101.23 54.846-142.35 95.969-23.98 23.98-44.933 50.278-62.727 78.6l-20.738-105.654c-3.043-15.528-18.105-25.642-33.632-22.6-15.528 3.048-25.643 18.105-22.6 33.637l36.103 183.932a28.666 28.666 0 0 0 13.588 19.178z m0 0M126.02 587.942H67.768c5.76 33.679 15.368 66.544 28.79 98.278 22.56 53.334 54.85 101.225 95.972 142.348 41.123 41.123 89.014 73.409 142.349 95.969 54.112 22.888 111.518 34.711 170.668 35.182v-57.324c-102.95-0.941-199.595-41.446-272.5-114.349-55.501-55.502-92.237-124.77-107.027-200.104z m0 0" p-id="815">
</path>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 895 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 626 B

View File

@@ -552,7 +552,7 @@ const ok = (info: any) => {
if (props.right && _time.getTime() / 100000 > start.value) {
emit("setDates", _time, "right");
}
if (!(props.left && props.right)) {
if (!(props.left || props.right)) {
emit("setDates", _time);
}
emit("ok", info === "h");

View File

@@ -44,6 +44,17 @@ const props = defineProps({
type: Object as PropType<{ [key: string]: any }>,
default: () => ({}),
},
filters: {
type: Object as PropType<{
duration: {
startTime: string;
endTime: string;
};
isRange: boolean;
dataIndex?: number;
sourceId: string;
}>,
},
});
const available = computed(
() =>
@@ -61,12 +72,93 @@ onMounted(async () => {
if (!instance) {
return;
}
instance.on("click", (params: any) => {
instance.on("click", (params: unknown) => {
emits("select", params);
});
document.addEventListener(
"click",
() => {
if (instance.isDisposed()) {
return;
}
instance.dispatchAction({
type: "hideTip",
});
instance.dispatchAction({
type: "updateAxisPointer",
currTrigger: "leave",
});
},
true
);
}, 1000);
});
function updateOptions() {
const instance = getInstance();
if (!instance) {
return;
}
if (!props.filters) {
return;
}
if (props.filters.isRange) {
const options = eventAssociate();
setOptions(options || props.option);
} else {
instance.dispatchAction({
type: "showTip",
dataIndex: props.filters.dataIndex,
seriesIndex: 0,
});
}
}
function eventAssociate() {
if (!props.filters) {
return;
}
if (!props.filters.duration) {
return props.option;
}
if (!props.option.series[0]) {
return;
}
const list = props.option.series[0].data.map(
(d: (number | string)[]) => d[0]
);
if (!list.includes(props.filters.duration.endTime)) {
return;
}
const markArea = {
silent: true,
itemStyle: {
opacity: 0.3,
},
data: [
[
{
xAxis: props.filters.duration.startTime,
},
{
xAxis: props.filters.duration.endTime,
},
],
],
};
const series = (window as any).structuredClone(props.option.series);
for (const [key, temp] of series.entries()) {
if (key === 0) {
temp.markArea = markArea;
}
}
const options = {
...props.option,
series,
};
return options;
}
watch(
() => props.option,
(newVal, oldVal) => {
@@ -76,7 +168,17 @@ watch(
if (JSON.stringify(newVal) === JSON.stringify(oldVal)) {
return;
}
setOptions(newVal);
let options;
if (props.filters && props.filters.isRange) {
options = eventAssociate();
}
setOptions(options || props.option);
}
);
watch(
() => props.filters,
() => {
updateOptions();
}
);

View File

@@ -29,9 +29,9 @@ limitations under the License. -->
>
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value"
:key="item.value || ''"
:label="item.label || ''"
:value="item.value || ''"
>
</el-option>
</el-select>

View File

@@ -28,15 +28,30 @@ export const Languages = [
export const RoutesMap: { [key: string]: string } = {
GeneralServices: "GENERAL",
Database: "VIRTUAL_DATABASE",
GeneralServicesActiveTabIndex: "GENERAL",
VirtualDatabase: "VIRTUAL_DATABASE",
VirtualDatabaseActiveTabIndex: "VIRTUAL_DATABASE",
MeshServices: "MESH",
MeshServicesActiveTabIndex: "MESH",
ControlPanel: "MESH_CP",
ControlPanelActiveTabIndex: "MESH_CP",
DataPanel: "MESH_DP",
DataPanelActiveTabIndex: "MESH_DP",
Linux: "OS_LINUX",
SkyWalkingServer: "SO11Y_OAP",
SkyWalkingServerActiveTabIndex: "SO11Y_OAP",
SatelliteActiveTabIndex: "SO11Y_SATELLITE",
Satellite: "SO11Y_SATELLITE",
Functions: "FAAS",
FunctionsActiveTabIndex: "FAAS",
Browser: "BROWSER",
BrowserActiveTabIndex: "BROWSER",
KubernetesCluster: "K8S",
KubernetesClusterActiveTabIndex: "K8S",
KubernetesService: "K8S_SERVICE",
KubernetesServiceActiveTabIndex: "K8S_SERVICE",
MySQL: "MYSQL",
MySQLActiveTabIndex: "MYSQL",
PostgreSQL: "POSTGRESQL",
PostgreSQLActiveTabIndex: "POSTGRESQL",
};

View File

@@ -33,12 +33,15 @@ export const createEBPFTask = {
}`,
};
export const queryEBPFTasks = {
variable: "$serviceId: ID!",
variable:
"$serviceId: ID, $serviceInstanceId: ID, $targets: [EBPFProfilingTargetType!]",
query: `
queryEBPFTasks: queryEBPFProfilingTasks(serviceId: $serviceId) {
queryEBPFTasks: queryEBPFProfilingTasks(serviceId: $serviceId, serviceInstanceId: $serviceInstanceId, targets: $targets) {
taskId
serviceName
serviceId
serviceInstanceId
serviceInstanceName
processLabels
taskStartTime
triggerType
@@ -90,3 +93,22 @@ export const analysisEBPFResult = {
}
}`,
};
export const createNetworkProfiling = {
variable: "$request: EBPFProfilingNetworkTaskRequest!",
query: `
createEBPFNetworkProfiling(request: $request) {
status
errorReason
id
}`,
};
export const keepNetworkProfiling = {
variable: "$taskId: ID!",
query: `
keepEBPFNetworkProfiling(taskId: $taskId) {
status
errorReason
}`,
};

View File

@@ -48,6 +48,29 @@ export const Instances = {
}
`,
};
export const Processes = {
variable: "$instanceId: ID!, $duration: Duration!",
query: `
processes: listProcesses(instanceId: $instanceId, duration: $duration) {
id
value: name
label: name
serviceId
serviceName
instanceId
instanceName
agentId
detectType
attributes {
name
value
}
labels
}
`,
};
export const Endpoints = {
variable: "$serviceId: ID!, $keyword: String!",
query: `
@@ -102,3 +125,24 @@ export const getEndpoint = {
}
`,
};
export const getProcess = {
variable: "$processId: ID!",
query: `
process: getProcess(processId: $processId) {
id
value: name
label: name
serviceId
serviceName
instanceId
instanceName
agentId
detectType
attributes {
name
value
}
}
`,
};

View File

@@ -75,3 +75,28 @@ export const InstanceTopology = {
}
`,
};
export const ProcessTopology = {
variable: "$serviceInstanceId: ID!, $duration: Duration!",
query: `
topology: getProcessTopology(serviceInstanceId: $serviceInstanceId,
duration: $duration) {
nodes {
id
name
isReal
serviceName
serviceId
serviceInstanceId
serviceInstanceName
}
calls {
id
source
detectPoints
target
sourceComponents
targetComponents
}
}
`,
};

View File

@@ -21,6 +21,8 @@ import {
queryEBPFTasks,
queryEBPFSchedules,
analysisEBPFResult,
createNetworkProfiling,
keepNetworkProfiling,
} from "../fragments/ebpf";
export const getCreateTaskData = `query queryCreateTaskData(${queryCreateTaskData.variable}) {${queryCreateTaskData.query}}`;
@@ -32,3 +34,7 @@ export const getEBPFTasks = `query queryEBPFTasks(${queryEBPFTasks.variable}) {$
export const getEBPFSchedules = `query queryEBPFSchedules(${queryEBPFSchedules.variable}) {${queryEBPFSchedules.query}}`;
export const getEBPFResult = `query analysisEBPFResult(${analysisEBPFResult.variable}) {${analysisEBPFResult.query}}`;
export const newNetworkProfiling = `mutation createNetworkProfiling(${createNetworkProfiling.variable}) {${createNetworkProfiling.query}}`;
export const aliveNetworkProfiling = `mutation keepNetworkProfiling(${keepNetworkProfiling.variable}) {${keepNetworkProfiling.query}}`;

View File

@@ -22,6 +22,8 @@ import {
getService,
getInstance,
getEndpoint,
Processes,
getProcess,
} from "../fragments/selector";
export const queryServices = `query queryServices(${Services.variable}) {${Services.query}}`;
@@ -31,3 +33,5 @@ export const queryLayers = `query listLayer {${Layers.query}}`;
export const queryService = `query queryService(${getService.variable}) {${getService.query}}`;
export const queryInstance = `query queryInstance(${getInstance.variable}) {${getInstance.query}}`;
export const queryEndpoint = `query queryInstance(${getEndpoint.variable}) {${getEndpoint.query}}`;
export const queryProcesses = `query queryProcesses(${Processes.variable}) {${Processes.query}}`;
export const queryProcess = `query queryProcess(${getProcess.variable}) {${getProcess.query}}`;

View File

@@ -18,8 +18,10 @@ import {
InstanceTopology,
EndpointTopology,
ServicesTopology,
ProcessTopology,
} from "../fragments/topology";
export const getInstanceTopology = `query queryData(${InstanceTopology.variable}) {${InstanceTopology.query}}`;
export const getEndpointTopology = `query queryData(${EndpointTopology.variable}) {${EndpointTopology.query}}`;
export const getServicesTopology = `query queryData(${ServicesTopology.variable}) {${ServicesTopology.query}}`;
export const getProcessTopology = `query queryData(${ProcessTopology.variable}) {${ProcessTopology.query}}`;

View File

@@ -32,10 +32,12 @@ export enum Calculations {
Precision = "precision",
ConvertSeconds = "convertSeconds",
ConvertMilliseconds = "convertMilliseconds",
MsTos = "msTos",
MsToS = "msTos",
Average = "average",
PercentageAvg = "percentageAvg",
ApdexAvg = "apdexAvg",
SecondToDay = "secondToDay",
NanosecondToMillisecond = "nanosecondToMillisecond",
}
export enum sizeEnum {
XS = "XS",

View File

@@ -14,19 +14,68 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { ElMessage } from "element-plus";
import { useDashboardStore } from "@/store/modules/dashboard";
import { LayoutConfig } from "@/types/dashboard";
export default function getDashboard(param: {
export default function getDashboard(param?: {
name: string;
layer: string;
entity: string;
}) {
const dashboardStore = useDashboardStore();
const opt = param || dashboardStore.currentDashboard;
const list = JSON.parse(sessionStorage.getItem("dashboards") || "[]");
const dashboard = list.find(
(d: { name: string; layer: string; entity: string }) =>
d.name === param.name &&
d.entity === param.entity &&
d.layer === param.layer
d.name === opt.name && d.entity === opt.entity && d.layer === opt.layer
);
const all = dashboardStore.layout;
const widgets: LayoutConfig[] = [];
for (const item of all) {
if (item.type === "Tab") {
widgets.push(item);
if (item.children && item.children.length) {
for (const child of item.children) {
if (child.children && child.children.length) {
widgets.push(...child.children);
}
}
}
} else {
widgets.push(item);
}
}
function associationWidget(sourceId: string, filters: unknown, type: string) {
const widget = widgets.find((d: { type: string }) => d.type === type);
if (!widget) {
return ElMessage.info(`There has no a ${type} widget in the dashboard`);
}
const item = {
...widget,
filters,
};
dashboardStore.setWidget(item);
const targetTabIndex = (widget.id || "").split("-");
const sourceTabindex = (sourceId || "").split("-") || [];
let container: Nullable<Element>;
return dashboard;
if (targetTabIndex[1] === undefined) {
container = document.querySelector(".ds-main");
} else {
const w = widgets.find((d: any) => d.id === targetTabIndex[0]);
container = document.querySelector(".tab-layout");
const layout: Nullable<Element> = document.querySelector(".ds-main");
if (w && layout) {
layout.scrollTop = w.y * 10 + w.h * 5;
}
}
if (targetTabIndex[1] && targetTabIndex[1] !== sourceTabindex[1]) {
dashboardStore.setActiveTabIndex(Number(targetTabIndex[1]));
}
if (container && widget) {
container.scrollTop = widget.y * 10 + widget.h * 5;
}
}
return { dashboard, widgets, associationWidget };
}

View File

@@ -18,7 +18,6 @@ import {
BarSeriesOption,
LineSeriesOption,
HeatmapSeriesOption,
PieSeriesOption,
SankeySeriesOption,
} from "echarts/charts";
import {
@@ -46,7 +45,6 @@ export type ECOption = echarts.ComposeOption<
| DatasetComponentOption
| LegendComponentOption
| HeatmapSeriesOption
| PieSeriesOption
| SankeySeriesOption
>;

View File

@@ -46,6 +46,7 @@ export function useQueryProcessor(config: any) {
"ServiceRelation",
"ServiceInstanceRelation",
"EndpointRelation",
"ProcessRelation",
].includes(dashboardStore.entity);
if (isRelation && !selectorStore.currentDestService) {
return;
@@ -93,28 +94,40 @@ export function useQueryProcessor(config: any) {
dashboardStore.entity === "All"
? undefined
: selectorStore.currentService.normal,
serviceInstanceName: dashboardStore.entity.includes("ServiceInstance")
serviceInstanceName: [
"ServiceInstance",
"ServiceInstanceRelation",
"ProcessRelation",
].includes(dashboardStore.entity)
? selectorStore.currentPod && selectorStore.currentPod.value
: undefined,
endpointName: dashboardStore.entity.includes("Endpoint")
? selectorStore.currentPod && selectorStore.currentPod.value
: undefined,
processName: dashboardStore.entity.includes("Process")
? selectorStore.currentProcess && selectorStore.currentProcess.value
: undefined,
destNormal: isRelation
? selectorStore.currentDestService.normal
: undefined,
destServiceName: isRelation
? selectorStore.currentDestService.value
: undefined,
destServiceInstanceName:
dashboardStore.entity === "ServiceInstanceRelation"
? selectorStore.currentDestPod &&
selectorStore.currentDestPod.value
destServiceInstanceName: [
"ServiceInstanceRelation",
"ProcessRelation",
].includes(dashboardStore.entity)
? selectorStore.currentDestPod && selectorStore.currentDestPod.value
: undefined,
destEndpointName:
dashboardStore.entity === "EndpointRelation"
? selectorStore.currentDestPod &&
selectorStore.currentDestPod.value
: undefined,
destProcessName: dashboardStore.entity.includes("ProcessRelation")
? selectorStore.currentDestProcess &&
selectorStore.currentDestProcess.value
: undefined,
},
};
}
@@ -142,6 +155,10 @@ export function useSourceProcessor(
ElMessage.error(resp.errors);
return {};
}
if (!resp.data) {
ElMessage.error("The query is wrong");
return {};
}
const source: { [key: string]: unknown } = {};
const keys = Object.keys(resp.data);
@@ -150,7 +167,10 @@ export function useSourceProcessor(
const c = (config.metricConfig && config.metricConfig[index]) || {};
if (type === MetricQueryTypes.ReadMetricsValues) {
source[m] = calculateExp(resp.data[keys[index]].values.values, c);
source[m] =
(resp.data[keys[index]] &&
calculateExp(resp.data[keys[index]].values.values, c)) ||
[];
}
if (type === MetricQueryTypes.ReadLabeledMetricsValues) {
const resVal = Object.values(resp.data)[0] || [];
@@ -377,23 +397,26 @@ export function aggregation(
data = (val / 1024 / 1024 / 1024).toFixed(2);
break;
case Calculations.Apdex:
data = val / 10000;
break;
case Calculations.ApdexAvg:
data = val / 10000;
data = (val / 10000).toFixed(2);
break;
case Calculations.ConvertSeconds:
data = dayjs(val).format("YYYY-MM-DD HH:mm:ss");
data = dayjs(val * 1000).format("YYYY-MM-DD HH:mm:ss");
break;
case Calculations.ConvertMilliseconds:
data = dayjs.unix(val).format("YYYY-MM-DD HH:mm:ss");
data = dayjs(val).format("YYYY-MM-DD HH:mm:ss");
break;
case Calculations.Precision:
data = data.toFixed(2);
break;
case Calculations.MsTos:
case Calculations.MsToS:
data = (val / 1000).toFixed(2);
break;
case Calculations.SecondToDay:
data = (val / 86400).toFixed(2);
break;
case Calculations.NanosecondToMillisecond:
data = (val / 1000 / 1000).toFixed(2);
break;
default:
data;
break;

View File

@@ -13,7 +13,7 @@ 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="nav-bar flex-h" :class="{ dark: theme === 'dark' }">
<div class="nav-bar flex-h">
<div class="title">{{ appStore.pageTitle || t(pageName) }}</div>
<div class="app-config">
<span class="red" v-show="timeRange">{{ t("timeTips") }}</span>
@@ -49,34 +49,38 @@ limitations under the License. -->
</div>
</template>
<script lang="ts" setup>
import { ref, watch, computed } from "vue";
import { ref, watch } from "vue";
import { useRoute } from "vue-router";
import { useI18n } from "vue-i18n";
import timeFormat from "@/utils/timeFormat";
import { useAppStoreWithOut } from "@/store/modules/app";
import { ElMessage } from "element-plus";
import getLocalTime from "@/utils/localtime";
const { t } = useI18n();
const appStore = useAppStoreWithOut();
const route = useRoute();
const pageName = ref<string>("");
const timeRange = ref<number>(0);
const theme = ref<string>("light");
getVersion();
const setConfig = (value: string) => {
pageName.value = value || "";
// theme.value = route.path.includes("/infrastructure/") ? "dark" : "light";
};
const time = computed(() => [
const time = ref<Date[]>([
appStore.durationRow.start,
appStore.durationRow.end,
]);
resetDuration();
getVersion();
const setConfig = (value: string) => {
pageName.value = value || "";
};
const handleReload = () => {
const gap =
appStore.duration.end.getTime() - appStore.duration.start.getTime();
const time: Date[] = [new Date(new Date().getTime() - gap), new Date()];
appStore.setDuration(timeFormat(time));
const dates: Date[] = [
getLocalTime(appStore.utc, new Date(new Date().getTime() - gap)),
getLocalTime(appStore.utc, new Date()),
];
appStore.setDuration(timeFormat(dates));
};
function changeTimeRange(val: Date[] | any) {
timeRange.value =
@@ -99,6 +103,20 @@ async function getVersion() {
ElMessage.error(res.errors);
}
}
function resetDuration() {
const { duration }: any = route.params;
if (duration) {
const d = JSON.parse(duration);
appStore.updateDurationRow({
start: new Date(d.start),
end: new Date(d.end),
step: d.step,
});
appStore.updateUTC(d.utc);
time.value = [new Date(d.start), new Date(d.end)];
}
}
</script>
<style lang="scss" scoped>
.nav-bar {

View File

@@ -55,9 +55,9 @@ limitations under the License. -->
<router-link
class="items"
:to="m.path"
:exact="m.meta.exact || false"
:exact="(m.meta && m.meta.exact) || false"
>
<span class="title">{{ t(m.meta.title) }}</span>
<span class="title">{{ m.meta && t(m.meta.title) }}</span>
</router-link>
</el-menu-item>
</el-menu-item-group>

View File

@@ -32,6 +32,7 @@ const msg = {
dashboards: "Dashboards",
profiles: "Profiles",
database: "Database",
mySQL: "MySQL",
serviceName: "Service Name",
technologies: "Technologies",
generalServicePanel: "General Service Panel",
@@ -142,6 +143,15 @@ const msg = {
interval: "Refresh Interval",
pause: "Pause",
begin: "Start",
associateOptions: "Association Options",
widget: "Widget",
nameTip:
"The name only supports Chinese and English, horizontal lines and underscores. The length of the name is limited to 300 characters",
duplicateName: "Duplicate name",
enableAssociate: "Enable association",
text: "Text",
query: "Query",
postgreSQL: "PostgreSQL",
seconds: "Seconds",
hourTip: "Select Hour",
minuteTip: "Select Minute",
@@ -151,8 +161,6 @@ const msg = {
monthsHead: "Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec",
months: "Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec",
weeks: "Mon_Tue_Wed_Thu_Fri_Sat_Sun",
hello: "Hello",
helloMessage: "Welcome Back, Apache SkyWalking APM System !",
username: "Username",
password: "Password",
title: "Title",
@@ -161,7 +169,7 @@ const msg = {
dashboard: "Dashboard",
topology: "Topology",
trace: "Trace",
alarm: "Alarms",
alarm: "Alerting",
auto: "Auto",
reload: "Reload",
version: "Version",

View File

@@ -32,6 +32,7 @@ const msg = {
dashboards: "Paneles",
profiles: "Perfiles",
database: "Base de Datos",
mySQL: "MySQL",
serviceName: "Nombre Servicio",
technologies: "Tecnologías",
generalServicePanel: "Panel Servicio General",
@@ -142,6 +143,15 @@ const msg = {
interval: "Intervalo de actualización",
pause: "Pausa",
begin: "Inicio",
associateOptions: "Opciones de asociación",
widget: "Dispositivo pequeño",
text: "Texto",
duplicateName: "Nombre duplicado",
nameTip:
"El nombre sólo admite chino e inglés, líneas horizontales y subrayado, y la longitud del nombre no excederá de 300 caracteres",
enableAssociate: "Activar asociación",
query: "Consulta",
postgreSQL: "PostgreSQL",
seconds: "Segundos",
hourTip: "Seleccione Hora",
minuteTip: "Seleccione Minuto",
@@ -151,8 +161,6 @@ const msg = {
monthsHead: "Ene_Feb_Mar_Abr_May_Jun_Jul_Ago_Set_Oct_Nov_Dic",
months: "Ene_Feb_Mar_Abr_May_Jun_Jul_Ago_Set_Oct_Nov_Dic",
weeks: "Lun_Mar_Mier_Jue_Vie_Sáb_Dom",
hello: "Hola",
helloMessage: "Bienvenido de vuelta, Apache SkyWalking APM System !",
username: "Usuario",
password: "Contraseña",
title: "Título",
@@ -161,7 +169,7 @@ const msg = {
dashboard: "Panel",
topology: "Topología",
trace: "Traza",
alarm: "Alarmas",
alarm: "Recordatorio en curso",
auto: "Auto",
reload: "Recargar",
version: "Versión",

View File

@@ -32,6 +32,7 @@ const msg = {
dashboards: "仪表盘",
profiles: "性能剖析",
database: "数据库",
mySQL: "MySQL",
serviceName: "服务名称",
technologies: "技术",
health: "健康",
@@ -140,6 +141,14 @@ const msg = {
interval: "刷新间隔时间",
pause: "暂停",
begin: "开始",
associateOptions: "关联选项",
widget: "部件",
enableAssociate: "启用关联",
nameTip: "该名称仅支持中文和英文、横线和下划线, 并且限制长度为300个字符",
duplicateName: "重复的名称",
text: "文本",
query: "查询",
postgreSQL: "PostgreSQL",
seconds: "秒",
hourTip: "选择小时",
minuteTip: "选择分钟",
@@ -149,8 +158,6 @@ const msg = {
monthsHead: "1月_2月_3月_4月_5月_6月_7月_8月_9月_10月_11月_12月",
months: "一月_二月_三月_四月_五月_六月_七月_八月_九月_十月_十一月_十二月",
weeks: "一_二_三_四_五_六_日",
hello: "你好",
helloMessage: "欢迎来到, Apache SkyWalking APM 系统 !",
username: "用户名",
password: "密码",
title: "标题",

View File

@@ -30,13 +30,13 @@ export const routesAlarm: Array<RouteRecordRaw> = [
component: Layout,
children: [
{
path: "/alarm",
path: "/alerting",
name: "Alarm",
meta: {
exact: false,
},
component: () =>
import(/* webpackChunkName: "alarms" */ "@/views/Alarm.vue"),
import(/* webpackChunkName: "alerting" */ "@/views/Alarm.vue"),
},
],
},

View File

@@ -33,12 +33,20 @@ export const routesBrowser: Array<RouteRecordRaw> = [
name: "Browser",
meta: {
title: "browser",
headPath: "/browser",
exact: true,
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
{
path: "/browser/tab/:activeTabIndex",
name: "BrowserActiveTabIndex",
meta: {
notShow: true,
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
],
},
];

View File

@@ -53,32 +53,79 @@ export const routesDashboard: Array<RouteRecordRaw> = [
exact: false,
},
},
{
path: "",
redirect: "/dashboard/:layerId/:entity/:name",
name: "Create",
component: () =>
import(
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
),
meta: {
notShow: true,
},
children: [
{
path: "/dashboard/:layerId/:entity/:name",
component: () =>
import(
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
),
name: "Create",
name: "CreateChild",
},
{
path: "/dashboard/:layerId/:entity/:name/tab/:activeTabIndex",
component: () =>
import(
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
),
name: "CreateActiveTabIndex",
},
],
},
{
path: "",
component: () =>
import(
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
),
name: "View",
redirect: "/dashboard/:layerId/:entity/:serviceId/:name",
meta: {
title: "dashboardEdit",
exact: false,
notShow: true,
},
},
children: [
{
path: "/dashboard/:layerId/:entity/:serviceId/:name",
component: () =>
import(
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
),
name: "View",
name: "ViewChild",
},
{
path: "/dashboard/:layerId/:entity/:serviceId/:name/tab/:activeTabIndex",
component: () =>
import(
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
),
name: "ViewActiveTabIndex",
},
],
},
{
path: "",
redirect:
"/dashboard/related/:layerId/:entity/:serviceId/:destServiceId/:name",
component: () =>
import(
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
),
name: "ViewServiceRelation",
meta: {
title: "dashboardEdit",
exact: false,
notShow: true,
},
},
children: [
{
path: "/dashboard/related/:layerId/:entity/:serviceId/:destServiceId/:name",
component: () =>
@@ -86,12 +133,29 @@ export const routesDashboard: Array<RouteRecordRaw> = [
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
),
name: "ViewServiceRelation",
},
{
path: "/dashboard/related/:layerId/:entity/:serviceId/:destServiceId/:name/tab/:activeTabIndex",
component: () =>
import(
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
),
name: "ViewServiceRelationActiveTabIndex",
},
],
},
{
path: "",
redirect: "/dashboard/:layerId/:entity/:serviceId/:podId/:name",
component: () =>
import(
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
),
name: "ViewPod",
meta: {
title: "dashboardEdit",
exact: false,
notShow: true,
},
},
children: [
{
path: "/dashboard/:layerId/:entity/:serviceId/:podId/:name",
component: () =>
@@ -99,12 +163,30 @@ export const routesDashboard: Array<RouteRecordRaw> = [
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
),
name: "ViewPod",
},
{
path: "/dashboard/:layerId/:entity/:serviceId/:podId/:name/tab/:activeTabIndex",
component: () =>
import(
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
),
name: "ViewPodActiveTabIndex",
},
],
},
{
path: "",
redirect:
"/dashboard/:layerId/:entity/:serviceId/:podId/:destServiceId/:destPodId/:name",
component: () =>
import(
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
),
name: "PodRelation",
meta: {
title: "dashboardEdit",
exact: false,
notShow: true,
},
},
children: [
{
path: "/dashboard/:layerId/:entity/:serviceId/:podId/:destServiceId/:destPodId/:name",
component: () =>
@@ -112,11 +194,55 @@ export const routesDashboard: Array<RouteRecordRaw> = [
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
),
name: "ViewPodRelation",
},
{
path: "/dashboard/:layerId/:entity/:serviceId/:podId/:destServiceId/:destPodId/:name/tab/:activeTabIndex",
component: () =>
import(
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
),
name: "ViewPodRelationActiveTabIndex",
},
],
},
{
path: "",
redirect:
"/dashboard/:layerId/:entity/:serviceId/:podId/:processId/:destServiceId/:destPodId/:destProcessId/:name",
component: () =>
import(
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
),
name: "ProcessRelation",
meta: {
title: "dashboardEdit",
exact: true,
notShow: true,
},
children: [
{
path: "/dashboard/:layerId/:entity/:serviceId/:podId/:processId/:destServiceId/:destPodId/:destProcessId/:name",
component: () =>
import(
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
),
name: "ViewProcessRelation",
},
{
path: "/dashboard/:layerId/:entity/:serviceId/:podId/:processId/:destServiceId/:destPodId/:destProcessId/:name/tab/:activeTabIndex",
component: () =>
import(
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
),
name: "ViewProcessRelationActiveTabIndex",
},
{
path: "/dashboard/:layerId/:entity/:serviceId/:podId/:processId/:destServiceId/:destPodId/:destProcessId/:name/duration/:duration",
component: () =>
import(
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
),
name: "ViewProcessRelationDuration",
},
],
},
],
},

View File

@@ -26,20 +26,47 @@ export const routesDatabase: Array<RouteRecordRaw> = [
icon: "storage",
hasGroup: true,
},
redirect: "/database",
redirect: "/mySQL",
component: Layout,
children: [
{
path: "/database",
name: "Database",
path: "/mySQL",
name: "MySQL",
meta: {
title: "virtualDatabase",
headPath: "/database",
title: "mySQL",
exact: true,
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
{
path: "/mySQL/tab/:activeTabIndex",
name: "MySQLActiveTabIndex",
meta: {
notShow: true,
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
{
path: "/postgreSQL",
name: "PostgreSQL",
meta: {
title: "postgreSQL",
exact: true,
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
{
path: "/postgreSQL/tab/:activeTabIndex",
name: "PostgreSQLActiveTabIndex",
meta: {
notShow: true,
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
],
},
];

View File

@@ -32,13 +32,17 @@ export const routesFunctions: Array<RouteRecordRaw> = [
path: "/functions",
name: "Functions",
meta: {
title: "functions",
headPath: "/functions",
exact: true,
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
{
path: "/functions/tab/:activeTabIndex",
name: "FunctionsActiveTabIndex",
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
],
},
];

View File

@@ -24,7 +24,7 @@ export const routesGen: Array<RouteRecordRaw> = [
meta: {
title: "general",
icon: "chart",
hasGroup: false,
hasGroup: true,
exact: true,
},
component: Layout,
@@ -33,13 +33,41 @@ export const routesGen: Array<RouteRecordRaw> = [
path: "/general",
name: "GeneralServices",
meta: {
title: "services",
headPath: "/general/service",
exact: true,
title: "services",
},
component: () =>
import(/* webpackChunkName: "layers" */ "@/views/Layer.vue"),
},
{
path: "/general/tab/:activeTabIndex",
name: "GeneralServicesActiveTabIndex",
meta: {
exact: true,
notShow: true,
},
component: () =>
import(/* webpackChunkName: "layers" */ "@/views/Layer.vue"),
},
{
path: "/database",
name: "VirtualDatabase",
meta: {
title: "virtualDatabase",
exact: true,
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
{
path: "/database/tab/:activeTabIndex",
name: "VirtualDatabaseActiveTabIndex",
meta: {
notShow: true,
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
],
},
];

View File

@@ -20,7 +20,6 @@ import { routesMesh } from "./serviceMesh";
import { routesDatabase } from "./database";
import { routesInfra } from "./infrastructure";
import { routesDashboard } from "./dashboard";
import { routesEvent } from "./event";
import { routesSetting } from "./setting";
import { routesAlarm } from "./alarm";
import { routesSelf } from "./selfObservability";
@@ -39,7 +38,6 @@ const routes: Array<RouteRecordRaw> = [
...routesSelf,
...routesDashboard,
...routesAlarm,
...routesEvent,
...routesSetting,
];

View File

@@ -39,6 +39,16 @@ export const routesInfra: Array<RouteRecordRaw> = [
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
{
path: "/linux/tab/:activeTabIndex",
name: "LinuxActiveTabIndex",
meta: {
title: "linux",
notShow: true,
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
// {
// path: "/infrastructure/vm",
// name: "VirtualMachine",

View File

@@ -33,20 +33,42 @@ export const routesK8s: Array<RouteRecordRaw> = [
path: "/kubernetes/cluster",
name: "KubernetesCluster",
meta: {
notShow: false,
title: "kubernetesCluster",
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
{
path: "/kubernetes/cluster/tab/:activeTabIndex",
name: "KubernetesClusterActiveTabIndex",
meta: {
notShow: true,
title: "kubernetesClusterActiveTabIndex",
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
{
path: "/kubernetes/service",
name: "KubernetesService",
meta: {
notShow: false,
title: "kubernetesService",
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
{
path: "/kubernetes/service/tab/:activeTabIndex",
name: "KubernetesServiceActiveTabIndex",
meta: {
notShow: true,
title: "kubernetesServiceActiveTabIndex",
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
],
},
];

View File

@@ -34,7 +34,15 @@ export const routesSelf: Array<RouteRecordRaw> = [
name: "SkyWalkingServer",
meta: {
title: "skyWalkingServer",
headPath: "/mesh/services",
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
{
path: "/self/skyWalkingServer/tab/:activeTabIndex",
name: "SkyWalkingServerActiveTabIndex",
meta: {
notShow: true,
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
@@ -44,7 +52,15 @@ export const routesSelf: Array<RouteRecordRaw> = [
name: "Satellite",
meta: {
title: "satellite",
headPath: "/mesh/controlPanel",
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
{
path: "/self/satellite/tab/:activeTabIndex",
name: "SatelliteActiveTabIndex",
meta: {
notShow: true,
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),

View File

@@ -33,8 +33,17 @@ export const routesMesh: Array<RouteRecordRaw> = [
path: "/mesh/services",
name: "MeshServices",
meta: {
notShow: false,
title: "services",
headPath: "/mesh/services",
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
{
path: "/mesh/services/tab/:activeTabIndex",
name: "MeshServicesActiveTabIndex",
meta: {
notShow: true,
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
@@ -43,8 +52,17 @@ export const routesMesh: Array<RouteRecordRaw> = [
path: "/mesh/controlPanel",
name: "ControlPanel",
meta: {
notShow: false,
title: "controlPanel",
headPath: "/mesh/controlPanel",
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
{
path: "/mesh/controlPanel/tab/:activeTabIndex",
name: "ControlPanelActiveTabIndex",
meta: {
notShow: true,
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
@@ -53,10 +71,21 @@ export const routesMesh: Array<RouteRecordRaw> = [
path: "/mesh/dataPanel",
name: "DataPanel",
meta: {
notShow: false,
title: "dataPanel",
headPath: "/mesh/dataPanel",
},
component: () => import("@/views/Layer.vue"),
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
{
path: "/mesh/dataPanel/tab/:activeTabIndex",
name: "DataPanelActiveTabIndex",
meta: {
notShow: true,
title: "dataPanelActiveTabIndex",
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
],
},

View File

@@ -29,3 +29,11 @@ export const TextConfig = {
fontSize: 14,
textAlign: "left",
};
export const TimeRangeConfig = {
fontColor: "black",
backgroundColor: "white",
fontSize: 14,
textAlign: "center",
text: "text",
};

View File

@@ -34,6 +34,7 @@ interface AppState {
pageTitle: string;
version: string;
isMobile: boolean;
reloadTimer: Nullable<any>;
}
export const appStore = defineStore({
@@ -53,6 +54,7 @@ export const appStore = defineStore({
pageTitle: "",
version: "",
isMobile: false,
reloadTimer: null,
}),
getters: {
duration(): Duration {
@@ -117,12 +119,18 @@ export const appStore = defineStore({
}
this.runEventStack();
},
updateDurationRow(data: Duration) {
this.durationRow = data;
},
setUTC(utcHour: number, utcMin: number): void {
this.runEventStack();
this.utcMin = utcMin;
this.utcHour = utcHour;
this.utc = `${utcHour}:${utcMin}`;
},
updateUTC(data: string) {
this.utc = data;
},
setIsMobile(mode: boolean) {
this.isMobile = mode;
},
@@ -153,10 +161,9 @@ export const appStore = defineStore({
.params({});
if (res.data.errors) {
this.utc = -(new Date().getTimezoneOffset() / 60) + ":0";
return res.data;
}
} else {
this.utc = res.data.data.getTimeInfo.timezone / 100 + ":0";
}
const utcArr = this.utc.split(":");
this.utcHour = isNaN(Number(utcArr[0])) ? 0 : Number(utcArr[0]);
this.utcMin = isNaN(Number(utcArr[1])) ? 0 : Number(utcArr[1]);
@@ -173,6 +180,9 @@ export const appStore = defineStore({
this.version = res.data.data.version;
return res.data;
},
setReloadTimer(timer: any): void {
this.reloadTimer = timer;
},
},
});
export function useAppStoreWithOut(): any {

View File

@@ -21,7 +21,7 @@ import graphql from "@/graphql";
import query from "@/graphql/fetch";
import { DashboardItem } from "@/types/dashboard";
import { useSelectorStore } from "@/store/modules/selectors";
import { NewControl, TextConfig } from "../data";
import { NewControl, TextConfig, TimeRangeConfig } from "../data";
import { AxiosResponse } from "axios";
import { ElMessage } from "element-plus";
import { useI18n } from "vue-i18n";
@@ -39,6 +39,7 @@ interface DashboardState {
dashboards: DashboardItem[];
currentDashboard: Nullable<DashboardItem>;
editMode: boolean;
currentTabIndex: number;
}
export const dashboardStore = defineStore({
@@ -56,6 +57,7 @@ export const dashboardStore = defineStore({
dashboards: [],
currentDashboard: null,
editMode: false,
currentTabIndex: 0,
}),
actions: {
setLayout(data: LayoutConfig[]) {
@@ -80,6 +82,7 @@ export const dashboardStore = defineStore({
const newItem: LayoutConfig = {
...NewControl,
i: index,
id: index,
type,
metricTypes: [""],
metrics: [""],
@@ -111,11 +114,14 @@ export const dashboardStore = defineStore({
};
}
if (
type === "Trace" ||
type === "Profile" ||
type === "Log" ||
type === "Ebpf" ||
type === "DemandLog"
[
"Trace",
"Profile",
"Log",
"DemandLog",
"Ebpf",
"NetworkProfiling",
].includes(type)
) {
newItem.h = 36;
}
@@ -123,6 +129,11 @@ export const dashboardStore = defineStore({
newItem.h = 6;
newItem.graph = TextConfig;
}
if (type === "TimeRange") {
newItem.w = 8;
newItem.h = 6;
newItem.graph = TimeRangeConfig;
}
this.activedGridItem = newItem.i;
this.selectedGrid = newItem;
this.layout = this.layout.map((d: LayoutConfig) => {
@@ -158,9 +169,11 @@ export const dashboardStore = defineStore({
if (!children.length) {
index = "0";
}
const id = `${activedGridItem}-${tabIndex}-${index}`;
const newItem: LayoutConfig = {
...NewControl,
i: index,
id,
type,
metricTypes: [""],
metrics: [""],
@@ -172,11 +185,14 @@ export const dashboardStore = defineStore({
};
}
if (
type === "Trace" ||
type === "Profile" ||
type === "Log" ||
type === "DemandLog" ||
type === "Ebpf"
[
"Trace",
"Profile",
"Log",
"DemandLog",
"Ebpf",
"NetworkProfiling",
].includes(type)
) {
newItem.h = 32;
}
@@ -184,6 +200,11 @@ export const dashboardStore = defineStore({
newItem.h = 6;
newItem.graph = TextConfig;
}
if (type === "TimeRange") {
newItem.w = 8;
newItem.h = 6;
newItem.graph = TextConfig;
}
if (this.layout[idx].children) {
const items = children.map((d: LayoutConfig) => {
d.y = d.y + newItem.h;
@@ -198,6 +219,7 @@ export const dashboardStore = defineStore({
this.activedGridItem = index;
},
setActiveTabIndex(index: number, target?: number) {
this.currentTabIndex = index;
const m = target || this.activedGridItem;
const idx = this.layout.findIndex((d: LayoutConfig) => d.i === m);
if (idx < 0) {
@@ -287,6 +309,28 @@ export const dashboardStore = defineStore({
};
this.selectedGrid = this.layout[index];
},
setWidget(param: LayoutConfig) {
for (let i = 0; i < this.layout.length; i++) {
if (this.layout[i].type === "Tab") {
if (this.layout[i].children && this.layout[i].children.length) {
for (const child of this.layout[i].children) {
if (child.children && child.children.length) {
for (let c = 0; c < child.children.length; c++) {
if (child.children[c].id === param.id) {
child.children.splice(c, 1, param);
return;
}
}
}
}
}
} else {
if (this.layout[i].id === param.id) {
this.layout.splice(i, 1, param);
}
}
}
},
async fetchMetricType(item: string) {
const res: AxiosResponse = await graphql
.query("queryTypeOfMetrics")

View File

@@ -25,8 +25,7 @@ import {
import { store } from "@/store";
import graphql from "@/graphql";
import { AxiosResponse } from "axios";
interface EbpfStore {
interface EbpfState {
taskList: EBPFTaskList[];
eBPFSchedules: EBPFProfilingSchedule[];
currentSchedule: EBPFProfilingSchedule | Record<string, never>;
@@ -40,7 +39,7 @@ interface EbpfStore {
export const ebpfStore = defineStore({
id: "eBPF",
state: (): EbpfStore => ({
state: (): EbpfState => ({
taskList: [],
eBPFSchedules: [],
currentSchedule: {},
@@ -53,7 +52,7 @@ export const ebpfStore = defineStore({
}),
actions: {
setSelectedTask(task: EBPFTaskList) {
this.selectedTask = task;
this.selectedTask = task || {};
},
setCurrentSchedule(s: EBPFProfilingSchedule) {
this.currentSchedule = s;
@@ -84,22 +83,31 @@ export const ebpfStore = defineStore({
if (res.data.errors) {
return res.data;
}
this.getTaskList(param.serviceId);
this.getTaskList({
serviceId: param.serviceId,
targets: ["ON_CPU", "OFF_CPU"],
});
return res.data;
},
async getTaskList(serviceId: string) {
if (!serviceId) {
async getTaskList(params: {
serviceId: string;
serviceInstanceId: string;
targets: string[];
}) {
if (!params.serviceId) {
return new Promise((resolve) => resolve({}));
}
const res: AxiosResponse = await graphql
.query("getEBPFTasks")
.params({ serviceId });
.params(params);
this.tip = "";
if (res.data.errors) {
return res.data;
}
this.taskList = res.data.data.queryEBPFTasks || [];
this.selectedTask = this.taskList[0] || {};
this.setSelectedTask(this.selectedTask);
if (!this.taskList.length) {
return res.data;
}

View File

@@ -19,16 +19,16 @@ import { store } from "@/store";
import graphql from "@/graphql";
import { AxiosResponse } from "axios";
import { Event, QueryEventCondition } from "@/types/events";
import { Instance, Endpoint, Service } from "@/types/selector";
import { Instance, Endpoint } from "@/types/selector";
import { useAppStoreWithOut } from "@/store/modules/app";
import { useSelectorStore } from "@/store/modules/selectors";
interface eventState {
loading: boolean;
events: Event[];
services: Service[];
instances: Instance[];
endpoints: Endpoint[];
condition: QueryEventCondition | any;
condition: Nullable<QueryEventCondition>;
}
export const eventStore = defineStore({
@@ -36,32 +36,18 @@ export const eventStore = defineStore({
state: (): eventState => ({
loading: false,
events: [],
services: [{ value: "", label: "All" }],
instances: [{ value: "", label: "All" }],
endpoints: [{ value: "", label: "All" }],
condition: {
paging: { pageNum: 1, pageSize: 15 },
},
condition: null,
}),
actions: {
setEventCondition(data: any) {
this.condition = { ...this.condition, ...data };
setEventCondition(data: QueryEventCondition) {
this.condition = data;
},
async getServices(layer: string) {
if (!layer) {
this.services = [{ value: "", label: "All" }];
return new Promise((resolve) => resolve([]));
}
const res: AxiosResponse = await graphql.query("queryServices").params({
layer,
});
if (res.data.errors) {
return res.data;
}
this.services = res.data.data.services;
return res.data;
},
async getInstances(serviceId: string) {
async getInstances() {
const serviceId = useSelectorStore().currentService
? useSelectorStore().currentService.id
: "";
const res: AxiosResponse = await graphql.query("queryInstances").params({
serviceId,
duration: useAppStoreWithOut().durationTime,
@@ -75,7 +61,13 @@ export const eventStore = defineStore({
];
return res.data;
},
async getEndpoints(serviceId: string) {
async getEndpoints() {
const serviceId = useSelectorStore().currentService
? useSelectorStore().currentService.id
: "";
if (!serviceId) {
return;
}
const res: AxiosResponse = await graphql.query("queryEndpoints").params({
serviceId,
duration: useAppStoreWithOut().durationTime,
@@ -112,6 +104,9 @@ export const eventStore = defineStore({
scope = "Endpoint";
}
item.scope = scope;
if (!item.endTime || item.endTime === item.startTime) {
item.endTime = Number(item.startTime) + 60000;
}
return item;
}
);

View File

@@ -53,6 +53,13 @@ export const logStore = defineStore({
setLogCondition(data: any) {
this.conditions = { ...this.conditions, ...data };
},
resetState() {
this.logs = [];
this.conditions = {
queryDuration: useAppStoreWithOut().durationTime,
paging: { pageNum: 1, pageSize: 15 },
};
},
async getServices(layer: string) {
const res: AxiosResponse = await graphql.query("queryServices").params({
layer,

View File

@@ -0,0 +1,183 @@
/**
* 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 { EBPFTaskList, ProcessNode } from "@/types/ebpf";
import { store } from "@/store";
import graphql from "@/graphql";
import { AxiosResponse } from "axios";
import { Call } from "@/types/topology";
import { LayoutConfig } from "@/types/dashboard";
import { ElMessage } from "element-plus";
interface NetworkProfilingState {
networkTasks: EBPFTaskList[];
networkTip: string;
selectedNetworkTask: Recordable<EBPFTaskList>;
nodes: ProcessNode[];
calls: Call[];
node: Nullable<ProcessNode>;
call: Nullable<Call>;
metricsLayout: LayoutConfig[];
selectedMetric: Nullable<LayoutConfig>;
activeMetricIndex: string;
aliveNetwork: boolean;
loadNodes: boolean;
}
export const networkProfilingStore = defineStore({
id: "networkProfiling",
state: (): NetworkProfilingState => ({
networkTasks: [],
networkTip: "",
selectedNetworkTask: {},
nodes: [],
calls: [],
node: null,
call: null,
metricsLayout: [],
selectedMetric: null,
activeMetricIndex: "",
aliveNetwork: false,
loadNodes: false,
}),
actions: {
setSelectedNetworkTask(task: EBPFTaskList) {
this.selectedNetworkTask = task || {};
},
setNode(node: Node) {
this.node = node;
},
setLink(link: Call) {
this.call = link;
},
setMetricsLayout(layout: LayoutConfig[]) {
this.metricsLayout = layout;
},
setSelectedMetric(item: LayoutConfig) {
this.selectedMetric = item;
},
setActiveItem(index: string) {
this.activeMetricIndex = index;
},
setTopology(data: { nodes: ProcessNode[]; calls: Call[] }) {
const obj = {} as any;
let calls = (data.calls || []).reduce((prev: Call[], next: Call) => {
if (!obj[next.id]) {
obj[next.id] = true;
next.value = next.value || 1;
for (const node of data.nodes) {
if (next.source === node.id) {
next.sourceObj = node;
}
if (next.target === node.id) {
next.targetObj = node;
}
}
next.value = next.value || 1;
prev.push(next);
}
return prev;
}, []);
calls = calls.map((d: any) => {
d.sourceId = d.source;
d.targetId = d.target;
d.source = d.sourceObj;
d.target = d.targetObj;
delete d.sourceObj;
delete d.targetObj;
return d;
});
this.calls = calls;
this.nodes = data.nodes;
},
async createNetworkTask(param: {
serviceId: string;
serviceInstanceId: string;
}) {
const res: AxiosResponse = await graphql
.query("newNetworkProfiling")
.params({ request: { instanceId: param.serviceInstanceId } });
if (res.data.errors) {
return res.data;
}
return res.data;
},
async getTaskList(params: {
serviceId: string;
serviceInstanceId: string;
targets: string[];
}) {
if (!params.serviceId) {
return new Promise((resolve) => resolve({}));
}
const res: AxiosResponse = await graphql
.query("getEBPFTasks")
.params(params);
this.networkTip = "";
if (res.data.errors) {
return res.data;
}
this.networkTasks = res.data.data.queryEBPFTasks || [];
this.selectedNetworkTask = this.networkTasks[0] || {};
this.setSelectedNetworkTask(this.selectedNetworkTask);
return res.data;
},
async keepNetworkProfiling(taskId: string) {
if (!taskId) {
return new Promise((resolve) => resolve({}));
}
const res: AxiosResponse = await graphql
.query("aliveNetworkProfiling")
.params({ taskId });
this.aliveMessage = "";
if (res.data.errors) {
return res.data;
}
this.aliveNetwork = res.data.data.keepEBPFNetworkProfiling.status;
if (!this.aliveNetwork) {
ElMessage.warning(res.data.data.keepEBPFNetworkProfiling.errorReason);
}
return res.data;
},
async getProcessTopology(params: {
duration: any;
serviceInstanceId: string;
}) {
this.loadNodes = true;
const res: AxiosResponse = await graphql
.query("getProcessTopology")
.params(params);
this.loadNodes = false;
if (res.data.errors) {
this.nodes = [];
this.calls = [];
return res.data;
}
const { topology } = res.data.data;
this.setTopology(topology);
return res.data;
},
},
});
export function useNetworkProfilingStore(): any {
return networkProfilingStore(store);
}

View File

@@ -15,7 +15,7 @@
* limitations under the License.
*/
import { defineStore } from "pinia";
import { Service, Instance, Endpoint } from "@/types/selector";
import { Service, Instance, Endpoint, Process } from "@/types/selector";
import { store } from "@/store";
import graphql from "@/graphql";
import { AxiosResponse } from "axios";
@@ -24,11 +24,15 @@ interface SelectorState {
services: Service[];
destServices: Service[];
pods: Array<Instance | Endpoint>;
processes: Process[];
destProcesses: Process[];
currentService: Nullable<Service>;
currentPod: Nullable<Instance | Endpoint>;
currentProcess: Nullable<Process>;
currentDestService: Nullable<Service>;
currentDestPod: Nullable<Instance | Endpoint>;
destPods: Array<Instance | Endpoint>;
currentDestProcess: Nullable<Process>;
}
export const selectorStore = defineStore({
@@ -38,10 +42,14 @@ export const selectorStore = defineStore({
destServices: [],
pods: [],
destPods: [],
processes: [],
destProcesses: [],
currentService: null,
currentPod: null,
currentProcess: null,
currentDestService: null,
currentDestPod: null,
currentDestProcess: null,
}),
actions: {
setCurrentService(service: Nullable<Service>) {
@@ -56,6 +64,18 @@ export const selectorStore = defineStore({
setCurrentDestPod(pod: Nullable<Instance | Endpoint>) {
this.currentDestPod = pod;
},
setCurrentProcess(process: Nullable<Process>) {
this.currentProcess = process;
},
setCurrentDestProcess(process: Nullable<Process>) {
this.currentDestProcess = process;
},
setDestPods(pods: Array<Instance | Endpoint>) {
this.destPods = pods;
},
setDestProcesses(processes: Array<Process>) {
this.destProcesses = processes;
},
async fetchLayers(): Promise<AxiosResponse> {
const res: AxiosResponse = await graphql.query("queryLayers").params({});
@@ -93,6 +113,27 @@ export const selectorStore = defineStore({
}
return res.data;
},
async getProcesses(param?: {
instanceId: string;
isRelation: boolean;
}): Promise<Nullable<AxiosResponse>> {
const instanceId = param ? param.instanceId : this.currentPod?.id;
if (!instanceId) {
return null;
}
const res: AxiosResponse = await graphql.query("queryProcesses").params({
instanceId,
duration: useAppStoreWithOut().durationTime,
});
if (!res.data.errors) {
if (param && param.isRelation) {
this.destProcesses = res.data.data.processes || [];
return res.data;
}
this.processes = res.data.data.processes || [];
}
return res.data;
},
async getEndpoints(params: {
keyword?: string;
serviceId?: string;
@@ -176,6 +217,25 @@ export const selectorStore = defineStore({
this.pods = [res.data.data.endpoint];
}
return res.data;
},
async getProcess(instanceId: string, isRelation?: boolean) {
if (!instanceId) {
return;
}
const res: AxiosResponse = await graphql.query("queryProcess").params({
instanceId,
});
if (!res.data.errors) {
if (isRelation) {
this.currentDestProcess = res.data.data.process || null;
this.destProcesses = [res.data.data.process];
return;
}
this.currentProcess = res.data.data.process || null;
this.processes = [res.data.data.process];
}
return res.data;
},
},

View File

@@ -63,6 +63,17 @@ export const traceStore = defineStore({
setTraceSpans(spans: Span) {
this.traceSpans = spans;
},
resetState() {
this.traceSpans = [];
this.traceList = [];
this.currentTrace = {};
this.conditions = {
queryDuration: useAppStoreWithOut().durationTime,
paging: { pageNum: 1, pageSize: 20 },
traceState: "ALL",
queryOrder: "BY_START_TIME",
};
},
async getServices(layer: string) {
const res: AxiosResponse = await graphql.query("queryServices").params({
layer,

View File

@@ -139,8 +139,10 @@ pre {
padding-left: 56px !important;
}
.el-icon.menu-icons {
margin-top: -3px !important;
.el-sub-menu__title {
.el-icon.menu-icons {
margin-top: -5px !important;
}
}
.el-switch__label--left {
@@ -154,3 +156,38 @@ pre {
.switch {
margin: 0 5px;
}
div.vis-tooltip {
max-width: 600px;
overflow: hidden;
background-color: #fff !important;
white-space: normal !important;
font-size: 12px !important;
}
.vis-item {
cursor: pointer;
height: 17px;
}
.vis-item.Error {
background-color: #e66;
opacity: 0.8;
border-color: #e66;
color: #fff !important;
}
.vis-item.Normal {
background-color: #fac858;
border-color: #fac858;
color: #666 !important;
}
.vis-item .vis-item-content {
padding: 0 5px !important;
}
.vis-item.vis-selected.Error,
.vis-item.vis-selected.Normal {
color: #1a1a1a !important;
}

View File

@@ -36,6 +36,21 @@ export interface LayoutConfig {
children?: { name: string; children: LayoutConfig[] }[];
activedTabIndex?: number;
metricConfig?: MetricConfigOpt[];
id?: string;
associate?: { widgetId: string }[];
eventAssociate?: boolean;
filters?: {
dataIndex: number;
sourceId: string;
isRange?: boolean;
duration?: {
startTime: string;
endTime: string;
};
traceId?: string;
spanId?: string;
segmentId?: string;
};
}
export type MetricConfigOpt = {
@@ -48,6 +63,7 @@ export type MetricConfigOpt = {
};
export interface WidgetConfig {
name?: string;
title?: string;
tips?: string;
}
@@ -137,3 +153,15 @@ export interface TopologyConfig {
depth?: number;
showDepth?: boolean;
}
export type EventParams = {
componentType: string;
seriesType: string;
seriesIndex: number;
seriesName: string;
name: string;
dataIndex: number;
data: Record<string, unknown>;
dataType: string;
value: number | number[];
color: string;
};

24
src/types/ebpf.d.ts vendored
View File

@@ -15,6 +15,7 @@
* limitations under the License.
*/
import { Process } from "./selector";
export interface EBPFTaskCreationRequest {
serviceId: string;
processLabels: string[];
@@ -43,18 +44,7 @@ export interface EBPFProfilingSchedule {
startTime: number;
}
export type Process = {
id: string;
name: string;
serviceId: string;
serviceName: string;
instanceId: string;
instanceName: string;
agentId: string;
detectType: string;
attributes: { name: string; value: string }[];
labels: string[];
};
export type Process = Process;
export type StackElement = {
id: string;
originId: string;
@@ -75,3 +65,13 @@ export type AnalyzationTrees = {
dumpCount: number;
stackType: string;
};
export type ProcessNode = {
id: string;
name: string;
serviceId: string;
serviceName: string;
serviceInstanceId: string;
serviceInstanceName: string;
name: string;
isReal: boolean;
};

View File

@@ -46,3 +46,16 @@ export type Service = {
layers: string[];
shortName: string;
};
export type Process = {
id: string;
name: string;
serviceId: string;
serviceName: string;
instanceId: string;
instanceName: string;
agentId: string;
detectType: string;
attributes: { name: string; value: string }[];
labels: string[];
};

View File

@@ -23,6 +23,9 @@ export interface Call {
sourceObj?: any;
targetObj?: any;
value?: number;
lowerArc?: boolean;
sourceComponents: string[];
targetComponents: string[];
}
export interface Node {
id: string;

View File

@@ -14,6 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import dayjs from "dayjs";
export default function dateFormatStep(
date: Date,
step: string,
@@ -99,3 +100,6 @@ export const dateFormatTime = (date: Date, step: string): string => {
}
return "";
};
export const dateFormat = (date: number, pattern = "YYYY-MM-DD HH:mm:ss") =>
dayjs(new Date(date)).format(pattern);

View File

@@ -16,13 +16,7 @@
*/
import * as echarts from "echarts/core";
import {
BarChart,
LineChart,
PieChart,
HeatmapChart,
SankeyChart,
} from "echarts/charts";
import { BarChart, LineChart, HeatmapChart, SankeyChart } from "echarts/charts";
import {
TitleComponent,
@@ -32,6 +26,7 @@ import {
DataZoomComponent,
VisualMapComponent,
TimelineComponent,
MarkAreaComponent,
} from "echarts/components";
import { SVGRenderer } from "echarts/renderers";
@@ -43,13 +38,13 @@ echarts.use([
GridComponent,
BarChart,
LineChart,
PieChart,
HeatmapChart,
SankeyChart,
SVGRenderer,
DataZoomComponent,
VisualMapComponent,
TimelineComponent,
MarkAreaComponent,
]);
export default echarts;

View File

@@ -24,7 +24,7 @@ import Header from "./alarm/Header.vue";
import Content from "./alarm/Content.vue";
const appStore = useAppStoreWithOut();
appStore.setPageTitle("Alarm");
appStore.setPageTitle("Alerting");
</script>
<style lang="scss" scoped>
.alarm {

View File

@@ -69,18 +69,16 @@ limitations under the License. -->
</div>
</template>
<script lang="ts" setup>
import { ref, reactive } from "vue";
import { ref } from "vue";
import { useI18n } from "vue-i18n";
import { useAppStoreWithOut } from "@/store/modules/app";
import timeFormat from "@/utils/timeFormat";
import { Languages } from "@/constants/data";
import Selector from "@/components/Selector.vue";
import getLocalTime from "@/utils/localtime";
const { t, locale } = useI18n();
const appStore = useAppStoreWithOut();
const state = reactive<{ timer: ReturnType<typeof setInterval> | null }>({
timer: null,
});
const lang = ref<string>(locale.value || "en");
const autoTime = ref<number>(6);
const auto = ref<boolean>(appStore.autoRefresh || false);
@@ -91,8 +89,11 @@ appStore.setPageTitle("Setting");
const handleReload = () => {
const gap =
appStore.duration.end.getTime() - appStore.duration.start.getTime();
const time: Date[] = [new Date(new Date().getTime() - gap), new Date()];
appStore.setDuration(timeFormat(time));
const dates: Date[] = [
getLocalTime(appStore.utc, new Date(new Date().getTime() - gap)),
getLocalTime(appStore.utc, new Date()),
];
appStore.setDuration(timeFormat(dates));
};
const handleAuto = () => {
if (autoTime.value < 1) {
@@ -101,10 +102,10 @@ const handleAuto = () => {
appStore.setAutoRefresh(auto.value);
if (auto.value) {
handleReload();
state.timer = setInterval(handleReload, autoTime.value * 1000);
appStore.setReloadTimer(setInterval(handleReload, autoTime.value * 1000));
} else {
if (state.timer) {
clearInterval(state.timer);
if (appStore.reloadTimer) {
clearInterval(appStore.reloadTimer);
}
}
};
@@ -112,12 +113,12 @@ const changeAutoTime = () => {
if (autoTime.value < 1) {
return;
}
if (state.timer) {
clearInterval(state.timer);
if (appStore.reloadTimer) {
clearInterval(appStore.reloadTimer);
}
if (auto.value) {
handleReload();
state.timer = setInterval(handleReload, autoTime.value * 1000);
appStore.setReloadTimer(setInterval(handleReload, autoTime.value * 1000));
}
};
const setLang = (): void => {

View File

@@ -145,16 +145,14 @@ limitations under the License. -->
</template>
<script lang="ts" setup>
import { ref } from "vue";
import dayjs from "dayjs";
import { useI18n } from "vue-i18n";
import { Alarm, Event } from "@/types/alarm";
import { useAlarmStore } from "@/store/modules/alarm";
import { EventsDetailHeaders, AlarmDetailCol, EventsDetailKeys } from "./data";
import { dateFormat } from "@/utils/dateFormat";
const { t } = useI18n();
const alarmStore = useAlarmStore();
const dateFormat = (date: number, pattern = "YYYY-MM-DD HH:mm:ss") =>
dayjs(date).format(pattern);
const isShowDetails = ref<boolean>(false);
const showEventDetails = ref<boolean>(false);
const currentDetail = ref<Alarm | any>({});

View File

@@ -15,11 +15,7 @@ limitations under the License. -->
<template>
<div>
<span class="grey">{{ t("tags") }}: </span>
<span
v-if="tagsList.length"
class="trace-tags"
:style="type === 'LOG' ? `min-width: 122px;` : ''"
>
<span v-if="tagsList.length" class="trace-tags">
<span class="selected" v-for="(item, index) in tagsList" :key="index">
<span>{{ item }}</span>
<span class="remove-icon" @click="removeTags(index)">×</span>
@@ -33,31 +29,28 @@ limitations under the License. -->
@change="addLabels"
:placeholder="t('addTags')"
/>
<span v-else>
<el-popover v-else trigger="click" :visible="visible" width="300px">
<template #reference>
<el-input
size="small"
v-model="tags"
class="trace-new-tag"
@click="showClick"
@input="searchTags"
@blur="visible = false"
@focus="visible = true"
/>
<el-dropdown
ref="dropdownTag"
trigger="contextmenu"
:hide-on-click="false"
style="margin: 20px 0 0 -130px"
v-if="tagArr.length"
</template>
<div class="content">
<span
v-for="(item, index) in tagList"
:key="index"
@click="selectTag(item)"
class="tag-item"
>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item v-for="(item, index) in tagArr" :key="index">
<span @click="selectTag(item)" class="tag-item">
{{ item }}
</span>
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</span>
</div>
</el-popover>
<span
class="tags-tip"
:class="type !== 'ALARM' && tagArr.length ? 'link-tips' : ''"
@@ -68,12 +61,12 @@ limitations under the License. -->
>
{{ t("tagsLink") }}
</a>
<el-tooltip :content="t(tipsMap[type])">
<el-tooltip :content="t((tipsMap as any)[type])">
<span>
<Icon class="icon-help mr-5" iconName="help" size="middle" />
</span>
</el-tooltip>
<b v-if="type === 'AL'">{{ t("noticeTag") }}</b>
<b v-if="type === 'ALARM'">{{ t("noticeTag") }}</b>
</span>
</div>
</template>
@@ -85,7 +78,7 @@ import { useLogStore } from "@/store/modules/log";
import { ElMessage } from "element-plus";
import { useAppStoreWithOut } from "@/store/modules/app";
/*global Nullable, defineEmits, defineProps */
/*global defineEmits, defineProps */
const emit = defineEmits(["update"]);
const props = defineProps({
type: { type: String, default: "TRACE" },
@@ -97,13 +90,14 @@ const { t } = useI18n();
const tags = ref<string>("");
const tagsList = ref<string[]>([]);
const tagArr = ref<string[]>([]);
const tagList = ref<string[]>([]);
const tagKeys = ref<string[]>([]);
const visible = ref<boolean>(false);
const tipsMap = {
LOG: "logTagsTip",
TRACE: "traceTagsTip",
ALARM: "alarmTagsTip",
};
const dropdownTag = ref<Nullable<any>>(null);
fetchTagKeys();
@@ -143,6 +137,7 @@ async function fetchTagKeys() {
}
tagArr.value = resp.data.tagKeys;
tagKeys.value = resp.data.tagKeys;
searchTags();
}
async function fetchTagValues() {
@@ -159,25 +154,36 @@ async function fetchTagValues() {
return;
}
tagArr.value = resp.data.tagValues;
searchTags();
}
function selectTag(item: string) {
if (tags.value.includes("=")) {
tags.value += item;
const key = tags.value.split("=")[0];
tags.value = key + "=" + item;
addLabels();
tagArr.value = tagKeys.value;
dropdownTag.value.handleClose();
searchTags();
return;
}
tags.value = item + "=";
fetchTagValues();
}
function showClick() {
if (dropdownTag.value) {
dropdownTag.value.handleOpen();
function searchTags() {
if (!tags.value) {
tagList.value = tagArr.value;
return;
}
let search = "";
if (tags.value.includes("=")) {
search = tags.value.split("=")[1];
} else {
search = tags.value;
}
tagList.value = tagArr.value.filter((d: string) => d.includes(search));
}
watch(
() => appStore.durationTime,
() => {
@@ -221,6 +227,12 @@ watch(
.tag-item {
display: inline-block;
min-width: 210px;
cursor: pointer;
margin-top: 10px;
&:hover {
color: #409eff;
}
}
.tags-tip {
@@ -229,7 +241,6 @@ watch(
.link-tips {
display: inline-block;
margin-left: 130px;
}
.light {
@@ -247,4 +258,10 @@ watch(
.icon-help {
cursor: pointer;
}
.content {
width: 300px;
max-height: 400px;
overflow: auto;
}
</style>

View File

@@ -41,6 +41,7 @@ import Tool from "./panel/Tool.vue";
import { useDashboardStore } from "@/store/modules/dashboard";
import { useAppStoreWithOut } from "@/store/modules/app";
import Configuration from "./configuration";
import { LayoutConfig } from "@/types/dashboard";
export default defineComponent({
name: "Dashboard",
@@ -66,7 +67,8 @@ export default defineComponent({
sessionStorage.getItem(layoutKey.value) || "{}"
);
const layout: any = c.configuration || {};
dashboardStore.setLayout(layout.children || []);
dashboardStore.setLayout(setWidgetsID(layout.children || []));
appStore.setPageTitle(layout.name);
if (p.entity) {
dashboardStore.setCurrentDashboard({
@@ -78,6 +80,24 @@ export default defineComponent({
});
}
}
function setWidgetsID(widgets: LayoutConfig[]) {
for (const item of widgets) {
item.id = item.i;
if (item.type === "Tab") {
if (item.children && item.children.length) {
for (const [index, child] of item.children.entries()) {
if (child.children && child.children.length) {
child.children.map((d: { i: string; index: string } | any) => {
d.id = `${item.i}-${index}-${d.i}`;
return d;
});
}
}
}
}
}
return widgets;
}
function handleClick(e: any) {
e.stopPropagation();
if (e.target.className === "ds-main") {

View File

@@ -20,7 +20,7 @@ limitations under the License. -->
placeholder="Please input name"
class="input-with-search ml-10"
size="small"
@change="searchDashboards"
@change="searchDashboards(1)"
>
<template #append>
<el-button size="small">
@@ -129,7 +129,8 @@ limitations under the License. -->
small
layout="prev, pager, next"
:page-size="pageSize"
:total="dashboardStore.dashboards.length"
:total="total"
v-model="currentPage"
@current-change="changePage"
@prev-click="changePage"
@next-click="changePage"
@@ -159,6 +160,8 @@ const pageSize = 18;
const dashboards = ref<DashboardItem[]>([]);
const searchText = ref<string>("");
const loading = ref<boolean>(false);
const currentPage = ref<number>(1);
const total = ref<number>(0);
const multipleTableRef = ref<InstanceType<typeof ElTable>>();
const multipleSelection = ref<DashboardItem[]>([]);
const dashboardFile = ref<Nullable<HTMLDivElement>>(null);
@@ -170,7 +173,7 @@ const handleSelectionChange = (val: DashboardItem[]) => {
setList();
async function setList() {
await dashboardStore.setDashboards();
searchDashboards();
searchDashboards(1);
}
async function importTemplates(event: any) {
const arr: any = await readFile(event);
@@ -232,12 +235,20 @@ function exportTemplates() {
}, 2000);
}
function optimizeTemplate(
children: (LayoutConfig & { moved?: boolean; standard?: unknown })[]
children: (LayoutConfig & {
moved?: boolean;
standard?: unknown;
label?: string;
value?: string;
})[]
) {
for (const child of children || []) {
delete child.moved;
delete child.activedTabIndex;
delete child.standard;
delete child.id;
delete child.label;
delete child.value;
if (isEmptyObject(child.graph)) {
delete child.graph;
}
@@ -370,7 +381,7 @@ async function setRoot(row: DashboardItem) {
items.push(d);
}
dashboardStore.resetDashboards(items);
searchDashboards();
searchDashboards(1);
loading.value = false;
}
function handleRename(row: DashboardItem) {
@@ -402,6 +413,7 @@ async function updateName(d: DashboardItem, value: string) {
name: value,
};
delete c.id;
delete c.filters;
const setting = {
id: d.id,
configuration: JSON.stringify(c),
@@ -453,10 +465,13 @@ function searchDashboards(pageIndex?: any) {
const arr = list.filter((d: { name: string }) =>
d.name.includes(searchText.value)
);
dashboards.value = arr.splice(
(pageIndex - 1 || 0) * pageSize,
pageSize * (pageIndex || 1)
total.value = arr.length;
dashboards.value = arr.filter(
(d: { name: string }, index: number) =>
index < pageIndex * pageSize && index >= (pageIndex - 1) * pageSize
);
currentPage.value = pageIndex;
}
async function reloadTemplates() {
@@ -465,6 +480,7 @@ async function reloadTemplates() {
loading.value = false;
}
function changePage(pageIndex: number) {
currentPage.value = pageIndex;
searchDashboards(pageIndex);
}
</script>

View File

@@ -0,0 +1,82 @@
<!-- 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>
<span class="label">{{ t("enableAssociate") }}</span>
<el-switch
v-model="eventAssociate"
active-text="Yes"
inactive-text="No"
@change="updateConfig"
/>
</div>
<div class="footer">
<el-button size="small" @click="cancelConfig">
{{ t("cancel") }}
</el-button>
<el-button size="small" type="primary" @click="applyConfig">
{{ t("apply") }}
</el-button>
</div>
</template>
<script lang="ts" setup>
import { useI18n } from "vue-i18n";
import { ref } from "vue";
import { useDashboardStore } from "@/store/modules/dashboard";
const { t } = useI18n();
const dashboardStore = useDashboardStore();
const originConfig = dashboardStore.selectedGrid;
const eventAssociate = ref(dashboardStore.selectedGrid.eventAssociate || false);
function updateConfig() {
dashboardStore.selectedGrid = {
...dashboardStore.selectedGrid,
eventAssociate,
};
dashboardStore.selectWidget(dashboardStore.selectedGrid);
}
function applyConfig() {
dashboardStore.setConfigPanel(false);
dashboardStore.setConfigs(dashboardStore.selectedGrid);
}
function cancelConfig() {
dashboardStore.selectWidget(originConfig);
dashboardStore.setConfigPanel(false);
}
</script>
<style lang="scss" scoped>
.label {
font-size: 13px;
font-weight: 500;
display: block;
margin-bottom: 5px;
}
.item {
margin: 10px 0;
}
.footer {
position: fixed;
bottom: 0;
right: 0;
border-top: 1px solid #eee;
padding: 10px;
text-align: right;
width: 100%;
background-color: #fff;
}
</style>

View File

@@ -0,0 +1,161 @@
<!-- Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. -->
<template>
<div class="item">
<span class="label">{{ t("text") }}</span>
<el-input
class="input"
v-model="text"
size="small"
@change="changeConfig({ text })"
/>
</div>
<div class="item">
<span class="label">{{ t("textAlign") }}</span>
<Selector
:value="textAlign"
:options="AlignStyle"
size="small"
placeholder="Select a color"
class="input"
@change="changeConfig({ textAlign: $event[0].value })"
/>
</div>
<div class="item">
<span class="label">{{ t("backgroundColors") }}</span>
<Selector
:value="backgroundColor"
:options="Colors"
size="small"
placeholder="Select a color"
class="input"
@change="changeConfig({ backgroundColor: $event[0].value })"
/>
</div>
<div class="item">
<span class="label">{{ t("fontSize") }}</span>
<el-slider
class="slider"
v-model="fontSize"
show-input
input-size="small"
:min="12"
:max="30"
:step="1"
@change="changeConfig({ fontSize })"
/>
</div>
<div class="item">
<span class="label">{{ t("fontColors") }}</span>
<Selector
:value="fontColor"
:options="Colors"
size="small"
placeholder="Select a color"
class="input"
@change="changeConfig({ fontColor: $event[0].value })"
/>
</div>
<div class="footer">
<el-button size="small" @click="cancelConfig">
{{ t("cancel") }}
</el-button>
<el-button size="small" type="primary" @click="applyConfig">
{{ t("apply") }}
</el-button>
</div>
</template>
<script lang="ts" setup>
import { useI18n } from "vue-i18n";
import { ref } from "vue";
import { useDashboardStore } from "@/store/modules/dashboard";
const { t } = useI18n();
const dashboardStore = useDashboardStore();
const originConfig = dashboardStore.selectedGrid;
const graph = originConfig.graph || {};
const backgroundColor = ref(graph.backgroundColor || "green");
const fontColor = ref(graph.fontColor || "white");
const fontSize = ref<number>(graph.fontSize || 12);
const textAlign = ref(graph.textAlign || "left");
const text = ref<string>(graph.text || "");
const Colors = [
{
label: "Green",
value: "green",
},
{ label: "Blue", value: "blue" },
{ label: "Red", value: "red" },
{ label: "Grey", value: "grey" },
{ label: "White", value: "white" },
{ label: "Black", value: "black" },
{ label: "Orange", value: "orange" },
];
const AlignStyle = [
{
label: "Left",
value: "left",
},
{ label: "Center", value: "center" },
{ label: "Right", value: "right" },
];
function changeConfig(param: { [key: string]: unknown }) {
const { selectedGrid } = dashboardStore;
const graph = {
...selectedGrid.graph,
...param,
};
dashboardStore.selectWidget({ ...selectedGrid, graph });
}
function applyConfig() {
dashboardStore.setConfigPanel(false);
dashboardStore.setConfigs(dashboardStore.selectedGrid);
}
function cancelConfig() {
dashboardStore.selectWidget(originConfig);
dashboardStore.setConfigPanel(false);
}
</script>
<style lang="scss" scoped>
.slider {
width: 500px;
margin-top: -3px;
}
.label {
font-size: 13px;
font-weight: 500;
display: block;
margin-bottom: 5px;
}
.input {
width: 500px;
}
.item {
margin-bottom: 10px;
}
.footer {
position: fixed;
bottom: 0;
right: 0;
border-top: 1px solid #eee;
padding: 10px;
text-align: right;
width: 100%;
background-color: #fff;
}
</style>

View File

@@ -58,6 +58,13 @@ limitations under the License. -->
<el-collapse-item :title="t('widgetOptions')" name="3">
<WidgetOptions />
</el-collapse-item>
<el-collapse-item
:title="t('associateOptions')"
name="4"
v-if="hasAssociate"
>
<AssociateOptions />
</el-collapse-item>
</el-collapse>
</div>
<div class="footer">
@@ -77,17 +84,13 @@ import { useDashboardStore } from "@/store/modules/dashboard";
import { useAppStoreWithOut } from "@/store/modules/app";
import { Option } from "@/types/app";
import graphs from "../graphs";
import configs from "./widget/graph-styles";
import WidgetOptions from "./widget/WidgetOptions.vue";
import MetricOptions from "./widget/metric/Index.vue";
import CustomOptions from "./widget/index";
export default defineComponent({
name: "WidgetEdit",
components: {
...graphs,
...configs,
WidgetOptions,
MetricOptions,
...CustomOptions,
},
setup() {
const configHeight = document.documentElement.clientHeight - 520;
@@ -111,6 +114,12 @@ export default defineComponent({
const graph = computed(() => dashboardStore.selectedGrid.graph || {});
const title = computed(() => encodeURIComponent(widget.value.title || ""));
const tips = computed(() => encodeURIComponent(widget.value.tips || ""));
const hasAssociate = computed(() =>
["Bar", "Line", "Area"].includes(
dashboardStore.selectedGrid.graph &&
dashboardStore.selectedGrid.graph.type
)
);
function getSource(source: unknown) {
states.source = source;
@@ -145,6 +154,7 @@ export default defineComponent({
graph,
title,
tips,
hasAssociate,
};
},
});

View File

@@ -18,9 +18,13 @@
import Text from "./Text.vue";
import Widget from "./Widget.vue";
import Topology from "./Topology.vue";
import Event from "./Event.vue";
import TimeRange from "./TimeRange.vue";
export default {
Text,
Widget,
Topology,
Event,
TimeRange,
};

View File

@@ -0,0 +1,112 @@
<!-- Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. -->
<template>
<div class="item">
<span class="label">{{ t("widget") }}</span>
<Selector
:multiple="true"
:value="widgetIds"
:options="widgets"
size="small"
placeholder="Select a widget"
class="selectors"
@change="updateWidgetConfig"
/>
</div>
</template>
<script lang="ts" setup>
import { ref, computed } from "vue";
import { useI18n } from "vue-i18n";
import { useDashboardStore } from "@/store/modules/dashboard";
import getDashboard from "@/hooks/useDashboardsSession";
import { Option } from "@/types/app";
const { t } = useI18n();
const dashboardStore = useDashboardStore();
const associate = dashboardStore.selectedGrid.associate || [];
const widgetIds = ref<string[]>(
associate.map((d: { widgetId: string }) => d.widgetId)
);
const widgets: any = computed(() => {
const widgetList = getDashboard(dashboardStore.currentDashboard).widgets;
const items = [];
for (const d of widgetList) {
const isLinear = ["Bar", "Line", "Area"].includes(
(d.graph && d.graph.type) || ""
);
if (isLinear && d.id && dashboardStore.selectedGrid.id !== d.id) {
items.push({ value: d.id, label: (d.widget && d.widget.name) || d.id });
}
}
return items;
});
function updateWidgetConfig(options: Option[]) {
const arr: any = getDashboard(dashboardStore.currentDashboard).widgets;
const opt = options.map((d: Option) => {
return { widgetId: d.value };
});
const newVal = options.map((d: Option) => d.value);
// add association options in the source widget
const widget = {
...dashboardStore.selectedGrid,
associate: opt,
};
dashboardStore.selectWidget({ ...widget });
// remove unuse association widget option
for (const id of widgetIds.value) {
if (!newVal.includes(id)) {
const w = arr.find((d: { id: string }) => d.id === id);
const config = {
...w,
associate: [],
};
dashboardStore.setWidget(config);
}
}
// add association options in target widgets
for (let i = 0; i < opt.length; i++) {
const item = JSON.parse(JSON.stringify(opt));
item[i] = { widgetId: dashboardStore.selectedGrid.id };
const w = arr.find((d: { id: string }) => d.id === opt[i].widgetId);
const config = {
...w,
associate: item,
};
dashboardStore.setWidget(config);
}
widgetIds.value = newVal;
}
</script>
<style lang="scss" scoped>
.label {
font-size: 13px;
font-weight: 500;
display: block;
margin-bottom: 5px;
}
.input {
width: 500px;
}
.item {
margin-bottom: 10px;
}
.selectors {
width: 500px;
}
</style>

View File

@@ -13,6 +13,16 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. -->
<template>
<div class="item">
<span class="label">{{ t("name") }}</span>
<el-input
class="input"
v-model="name"
size="small"
placeholder="Please input name"
@change="updateWidgetName({ name: encodeURIComponent(name) })"
/>
</div>
<div class="item">
<span class="label">{{ t("title") }}</span>
<el-input
@@ -37,13 +47,17 @@ limitations under the License. -->
<script lang="ts" setup>
import { ref } from "vue";
import { useI18n } from "vue-i18n";
import { ElMessage } from "element-plus";
import { useDashboardStore } from "@/store/modules/dashboard";
import getDashboard from "@/hooks/useDashboardsSession";
import { LayoutConfig } from "@/types/dashboard";
const { t } = useI18n();
const dashboardStore = useDashboardStore();
const widget = dashboardStore.selectedGrid.widget || {};
const title = ref<string>(widget.title || "");
const tips = ref<string>(widget.tips || "");
const name = ref<string>(widget.name || "");
function updateWidgetConfig(param: { [key: string]: string }) {
const key = Object.keys(param)[0];
@@ -57,6 +71,24 @@ function updateWidgetConfig(param: { [key: string]: string }) {
};
dashboardStore.selectWidget({ ...selectedGrid, widget });
}
function updateWidgetName(param: { [key: string]: string }) {
const key = Object.keys(param)[0];
const n = decodeURIComponent(param[key]);
const pattern = /^[A-Za-z0-9-_\u4e00-\u9fa5]{1,300}$/;
if (!pattern.test(n)) {
ElMessage.error(t("nameTip"));
return;
}
const { widgets } = getDashboard(dashboardStore.currentDashboard);
const item = widgets.find(
(d: LayoutConfig) => d.widget && d.widget.name === n
);
if (item) {
ElMessage.error(t("duplicateName"));
return;
}
updateWidgetConfig(param);
}
</script>
<style lang="scss" scoped>
.label {

View 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 StyleOptions from "./graph-styles";
import WidgetOptions from "./WidgetOptions.vue";
import MetricOptions from "./metric/Index.vue";
import AssociateOptions from "./AssociateOptions.vue";
export default {
...StyleOptions,
WidgetOptions,
MetricOptions,
AssociateOptions,
};

View File

@@ -179,12 +179,16 @@ const setVisTypes = computed(() => {
async function setMetricType(chart?: any) {
const g = chart || dashboardStore.selectedGrid.graph || {};
let arr: any[] = states.metricList;
if (!chart) {
const json = await dashboardStore.fetchMetricList();
if (json.errors) {
ElMessage.error(json.errors);
return;
}
states.metricList = (json.data.metrics || []).filter(
arr = json.data.metrics;
}
states.metricList = (arr || []).filter(
(d: { catalog: string; type: string }) => {
if (states.isList) {
if (d.type === MetricsType.REGULAR_VALUE) {

View File

@@ -22,7 +22,9 @@ limitations under the License. -->
size="small"
placeholder="Please input unit"
@change="
updateConfig(index, { unit: encodeURIComponent(currentMetric.unit) })
updateConfig(index, {
unit: encodeURIComponent(currentMetric.unit || ''),
})
"
/>
</div>
@@ -35,7 +37,7 @@ limitations under the License. -->
placeholder="Please input a name"
@change="
updateConfig(index, {
label: encodeURIComponent(currentMetric.label),
label: encodeURIComponent(currentMetric.label || ''),
})
"
/>

View File

@@ -29,7 +29,7 @@ limitations under the License. -->
<span>{{ t("delete") }}</span>
</div>
</el-popover>
<Header />
<Header :needQuery="needQuery" />
<Content />
</div>
</template>
@@ -47,6 +47,7 @@ const props = defineProps({
default: () => ({ graph: {} }),
},
activeIndex: { type: String, default: "" },
needQuery: { type: Boolean, default: true },
});
const { t } = useI18n();
const dashboardStore = useDashboardStore();

View File

@@ -0,0 +1,109 @@
<!-- 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="event-wrapper flex-v">
<el-popover
placement="bottom"
trigger="click"
:width="100"
v-if="dashboardStore.editMode"
>
<template #reference>
<span class="delete cp">
<Icon iconName="ellipsis_v" size="middle" class="operation" />
</span>
</template>
<div class="tools" @click="editConfig">
<span>{{ t("edit") }}</span>
</div>
<div class="tools" @click="removeWidget">
<span>{{ t("delete") }}</span>
</div>
</el-popover>
<div class="header">
<Header :needQuery="needQuery" />
</div>
<div class="event">
<Content :data="data" />
</div>
</div>
</template>
<script lang="ts" setup>
import { useI18n } from "vue-i18n";
import { useDashboardStore } from "@/store/modules/dashboard";
import Header from "../related/event/Header.vue";
import Content from "../related/event/Content.vue";
/*global defineProps */
const props = defineProps({
data: {
type: Object,
default: () => ({}),
},
activeIndex: { type: String, default: "" },
needQuery: { type: Boolean, default: true },
});
const { t } = useI18n();
const dashboardStore = useDashboardStore();
function removeWidget() {
dashboardStore.removeControls(props.data);
}
function editConfig() {
dashboardStore.setConfigPanel(true);
dashboardStore.selectWidget(props.data);
}
</script>
<style lang="scss" scoped>
.event-wrapper {
width: 100%;
height: 100%;
font-size: 12px;
position: relative;
overflow: auto;
}
.delete {
position: absolute;
top: 5px;
right: 3px;
z-index: 9999;
}
.header {
padding: 10px;
font-size: 12px;
border-bottom: 1px solid #dcdfe6;
min-width: 1024px;
}
.tools {
padding: 5px 0;
color: #999;
cursor: pointer;
position: relative;
text-align: center;
&:hover {
color: #409eff;
background-color: #eee;
}
}
.event {
width: 100%;
height: calc(100% - 80px);
}
</style>

View File

@@ -30,7 +30,7 @@ limitations under the License. -->
</div>
</el-popover>
<div class="header">
<Header />
<Header :needQuery="needQuery" :data="data" />
</div>
<div class="log">
<List />
@@ -38,19 +38,24 @@ limitations under the License. -->
</div>
</template>
<script lang="ts" setup>
import { provide } from "vue";
import { useI18n } from "vue-i18n";
import { useDashboardStore } from "@/store/modules/dashboard";
import Header from "../related/log/Header.vue";
import List from "../related/log/List.vue";
import { LayoutConfig } from "@/types/dashboard";
import type { PropType } from "vue";
/*global defineProps */
const props = defineProps({
data: {
type: Object,
default: () => ({}),
type: Object as PropType<LayoutConfig>,
default: () => ({ graph: {} }),
},
activeIndex: { type: String, default: "" },
needQuery: { type: Boolean, default: true },
});
provide("options", props.data);
const { t } = useI18n();
const dashboardStore = useDashboardStore();
@@ -71,6 +76,7 @@ function removeWidget() {
position: absolute;
top: 5px;
right: 3px;
z-index: 1000;
}
.header {

View File

@@ -0,0 +1,97 @@
<!-- 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="profile-wrapper flex-v">
<div class="title">Network Profiling</div>
<el-popover
placement="bottom"
trigger="click"
:width="100"
v-if="dashboardStore.editMode"
>
<template #reference>
<span class="operation cp">
<Icon iconName="ellipsis_v" size="middle" />
</span>
</template>
<div class="tools" @click="removeWidget">
<span>{{ t("delete") }}</span>
</div>
</el-popover>
<Content :config="props.data" />
</div>
</template>
<script lang="ts" setup>
import type { PropType } from "vue";
import { useI18n } from "vue-i18n";
import { useDashboardStore } from "@/store/modules/dashboard";
import Content from "../related/network-profiling/Content.vue";
/*global defineProps */
const props = defineProps({
data: {
type: Object as PropType<any>,
default: () => ({ graph: {} }),
},
activeIndex: { type: String, default: "" },
needQuery: { type: Boolean, default: true },
});
const { t } = useI18n();
const dashboardStore = useDashboardStore();
function removeWidget() {
dashboardStore.removeControls(props.data);
}
</script>
<style lang="scss" scoped>
.profile-wrapper {
width: 100%;
height: 100%;
font-size: 12px;
position: relative;
}
.operation {
position: absolute;
top: 8px;
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;
}
}
.title {
font-weight: bold;
line-height: 40px;
padding: 0 10px;
border-bottom: 1px solid #dcdfe6;
}
</style>

View File

@@ -29,7 +29,7 @@ limitations under the License. -->
<span>{{ t("delete") }}</span>
</div>
</el-popover>
<Header />
<Header :needQuery="needQuery" />
<Content />
</div>
</template>
@@ -47,6 +47,7 @@ const props = defineProps({
default: () => ({ graph: {} }),
},
activeIndex: { type: String, default: "" },
needQuery: { type: Boolean, default: true },
});
const { t } = useI18n();
const dashboardStore = useDashboardStore();

View File

@@ -37,10 +37,17 @@ limitations under the License. -->
v-if="dashboardStore.editMode && canEditTabName"
/>
</span>
<span class="tab-icons">
<el-tooltip content="Copy Link" placement="bottom">
<i @click="copyLink">
<Icon size="middle" iconName="review-list" class="tab-icon" />
</i>
</el-tooltip>
</span>
<span class="tab-icons" v-if="dashboardStore.editMode">
<el-tooltip content="Add tab items" placement="bottom">
<i @click="addTabItem">
<Icon size="middle" iconName="add" />
<Icon size="middle" iconName="add_fill" class="tab-icon" />
</i>
</el-tooltip>
</span>
@@ -80,7 +87,7 @@ limitations under the License. -->
:w="item.w"
:h="item.h"
:i="item.i"
:key="item.i"
:key="item.id"
@click="clickTabGrid($event, item)"
:class="{ active: activeTabWidget === item.i }"
:drag-ignore-from="dragIgnoreFrom"
@@ -99,18 +106,13 @@ limitations under the License. -->
<script lang="ts">
import { ref, watch, defineComponent, toRefs } from "vue";
import { useI18n } from "vue-i18n";
import { useRoute } from "vue-router";
import type { PropType } from "vue";
import { LayoutConfig } from "@/types/dashboard";
import { useDashboardStore } from "@/store/modules/dashboard";
import Topology from "./Topology.vue";
import Widget from "./Widget.vue";
import Trace from "./Trace.vue";
import Profile from "./Profile.vue";
import Log from "./Log.vue";
import Text from "./Text.vue";
import Ebpf from "./Ebpf.vue";
import controls from "./tab";
import { dragIgnoreFrom } from "../data";
import DemandLog from "./DemandLog.vue";
import copy from "@/utils/copy";
const props = {
data: {
@@ -121,16 +123,23 @@ const props = {
};
export default defineComponent({
name: "Tab",
components: { Topology, Widget, Trace, Profile, Log, Text, Ebpf, DemandLog },
components: {
...controls,
},
props,
setup(props) {
const { t } = useI18n();
const dashboardStore = useDashboardStore();
const activeTabIndex = ref<number>(0);
const route = useRoute();
const activeTabIndex = ref<number>(
Number(route.params.activeTabIndex) || 0
);
const activeTabWidget = ref<string>("");
const editTabIndex = ref<number>(NaN); // edit tab item name
const canEditTabName = ref<boolean>(false);
const needQuery = ref<boolean>(false);
dashboardStore.setActiveTabIndex(activeTabIndex);
const l = dashboardStore.layout.findIndex(
(d: LayoutConfig) => d.i === props.data.i
);
@@ -154,6 +163,11 @@ export default defineComponent({
dashboardStore.layout[l].children[activeTabIndex.value].children
);
needQuery.value = true;
if (route.params.activeTabIndex) {
let p = location.href.split("/tab/")[0];
p = p + "/tab/" + activeTabIndex.value;
history.replaceState({}, "", p);
}
}
function removeTab(e: Event) {
e.stopPropagation();
@@ -180,6 +194,7 @@ export default defineComponent({
editTabIndex.value = index;
}
function handleClick(el: any) {
needQuery.value = true;
if (["tab-name", "edit-tab"].includes(el.target.className)) {
return;
}
@@ -202,6 +217,16 @@ export default defineComponent({
dashboardStore.layout[l].children[activeTabIndex.value].children
);
}
function copyLink() {
let path = "";
if (route.params.activeTabIndex === undefined) {
path = location.href + "/tab/" + activeTabIndex.value;
} else {
const p = location.href.split("/tab/")[0];
path = p + "/tab/" + activeTabIndex.value;
}
copy(path);
}
document.body.addEventListener("click", handleClick, false);
watch(
() => dashboardStore.activedGridItem,
@@ -218,6 +243,26 @@ export default defineComponent({
}
}
);
watch(
() => dashboardStore.currentTabIndex,
() => {
activeTabIndex.value = dashboardStore.currentTabIndex;
dashboardStore.activeGridItem(props.data.i);
dashboardStore.selectWidget(props.data);
const l = dashboardStore.layout.findIndex(
(d: LayoutConfig) => d.i === props.data.i
);
dashboardStore.setCurrentTabItems(
dashboardStore.layout[l].children[activeTabIndex.value].children
);
needQuery.value = true;
if (route.params.activeTabIndex) {
let p = location.href.split("/tab/")[0];
p = p + "/tab/" + activeTabIndex.value;
history.replaceState({}, "", p);
}
}
);
return {
handleClick,
layoutUpdatedEvent,
@@ -227,6 +272,7 @@ export default defineComponent({
deleteTabItem,
removeTab,
clickTabs,
copyLink,
...toRefs(props),
activeTabWidget,
dashboardStore,
@@ -330,6 +376,10 @@ export default defineComponent({
overflow: auto;
}
.tab-icon {
color: #666;
}
.vue-grid-item.active {
border: 1px solid #409eff;
}

View File

@@ -0,0 +1,187 @@
<!-- 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="time-range">
<div class="header">
<el-popover
placement="bottom"
trigger="click"
:width="100"
v-if="dashboardStore.editMode"
>
<template #reference>
<span>
<Icon iconName="ellipsis_v" size="middle" class="operation" />
</span>
</template>
<div class="tools" @click="editConfig">
<span>{{ t("edit") }}</span>
</div>
<div class="tools" @click="removeTopo">
<span>{{ t("delete") }}</span>
</div>
</el-popover>
</div>
<div
class="body"
:style="{
backgroundColor: TextColors[graph.backgroundColor],
justifyContent: graph.textAlign,
color: TextColors[graph.fontColor],
}"
>
<span
class="mr-5"
:style="{
fontSize: graph.fontSize + 'px',
}"
>{{ graph.text }}
</span>
<Icon iconName="time_range" size="middle" />
<span
class="ml-5"
:style="{
fontSize: graph.fontSize + 'px',
}"
>
{{ content }}
</span>
</div>
</div>
</template>
<script lang="ts" setup>
import { computed } from "vue";
import type { PropType } from "vue";
import { useI18n } from "vue-i18n";
import { useDashboardStore } from "@/store/modules/dashboard";
import { useAppStoreWithOut } from "@/store/modules/app";
import { TextColors } from "@/views/dashboard/data";
/*global defineProps */
const props = defineProps({
data: {
type: Object as PropType<any>,
default: () => ({ graph: {} }),
},
activeIndex: { type: String, default: "" },
});
const { t } = useI18n();
const graph = computed(() => props.data.graph || {});
const dashboardStore = useDashboardStore();
const appStore = useAppStoreWithOut();
const content = computed(() => {
const text = [appStore.durationRow.start, appStore.durationRow.end]
.map((date: Date) => tf(date, "YYYY-MM-DD HH:mm"))
.join(` ~ `);
return text;
});
function removeTopo() {
dashboardStore.removeControls(props.data);
}
function editConfig() {
dashboardStore.setConfigPanel(true);
dashboardStore.selectWidget(props.data);
}
function tf(time: Date, format: any): string {
const local = {
dow: 1, // Monday is the first day of the week
hourTip: t("hourTip"), // tip of select hour
minuteTip: t("minuteTip"), // tip of select minute
secondTip: t("secondTip"), // tip of select second
yearSuffix: t("yearSuffix"), // format of head
monthsHead: t("monthsHead").split("_"), // months of head
months: t("months").split("_"), // months of panel
weeks: t("weeks").split("_"), // weeks
cancelTip: t("cancel"), // default text for cancel button
submitTip: t("confirm"), // default text for submit button
quarterHourCutTip: t("quarterHourCutTip"),
halfHourCutTip: t("halfHourCutTip"),
hourCutTip: t("hourCutTip"),
dayCutTip: t("dayCutTip"),
weekCutTip: t("weekCutTip"),
monthCutTip: t("monthCutTip"),
};
const year = time.getFullYear();
const month = time.getMonth();
const day = time.getDate();
const hours24 = time.getHours();
const hours = hours24 % 12 === 0 ? 12 : hours24 % 12;
const minutes = time.getMinutes();
const seconds = time.getSeconds();
const milliseconds = time.getMilliseconds();
const dd = (t: number) => `0${t}`.slice(-2);
const map: { [key: string]: string | number } = {
YYYY: year,
MM: dd(month + 1),
MMM: local.months[month],
MMMM: local.monthsHead[month],
M: month + 1,
DD: dd(day),
D: day,
HH: dd(hours24),
H: hours24,
hh: dd(hours),
h: hours,
mm: dd(minutes),
m: minutes,
ss: dd(seconds),
s: seconds,
S: milliseconds,
};
return format.replace(/Y+|M+|D+|H+|h+|m+|s+|S+/g, (str: string) => map[str]);
}
</script>
<style lang="scss" scoped>
.time-range {
font-size: 12px;
height: 100%;
position: relative;
}
.operation {
cursor: pointer;
}
.header {
position: absolute;
top: 5px;
right: 5px;
}
.body {
padding: 0 20px 0 10px;
width: 100%;
height: 100%;
cursor: pointer;
display: flex;
align-items: center;
overflow: auto;
font-weight: bold;
}
.tools {
padding: 5px 0;
color: #999;
cursor: pointer;
position: relative;
text-align: center;
&:hover {
color: #409eff;
background-color: #eee;
}
}
</style>

View File

@@ -30,7 +30,7 @@ limitations under the License. -->
</div>
</el-popover>
<div class="header">
<Filter />
<Filter :needQuery="needQuery" :data="data" />
</div>
<div class="trace flex-h">
<TraceList />
@@ -39,6 +39,7 @@ limitations under the License. -->
</div>
</template>
<script lang="ts" setup>
import { provide } from "vue";
import type { PropType } from "vue";
import Filter from "../related/trace/Filter.vue";
import TraceList from "../related/trace/TraceList.vue";
@@ -53,7 +54,9 @@ const props = defineProps({
default: () => ({ graph: {} }),
},
activeIndex: { type: String, default: "" },
needQuery: { type: Boolean, default: true },
});
provide("options", props.data);
const { t } = useI18n();
const dashboardStore = useDashboardStore();
function removeWidget() {

View File

@@ -61,9 +61,12 @@ limitations under the License. -->
metrics: data.metrics || [''],
metricTypes: data.metricTypes || [''],
i: data.i,
id: data.id,
metricConfig: data.metricConfig,
filters: data.filters || {},
}"
:needQuery="needQuery"
@click="clickHandle"
/>
</div>
<div v-else class="no-data">{{ t("noGraph") }}</div>
@@ -84,6 +87,8 @@ import {
useGetMetricEntity,
} from "@/hooks/useProcessor";
import { EntityType, ListChartTypes } from "../data";
import { EventParams } from "@/types/dashboard";
import getDashboard from "@/hooks/useDashboardsSession";
const props = {
data: {
@@ -93,6 +98,7 @@ const props = {
activeIndex: { type: String, default: "" },
needQuery: { type: Boolean, default: false },
};
export default defineComponent({
name: "Widget",
components: { ...graphs },
@@ -156,6 +162,24 @@ export default defineComponent({
dashboardStore.activeGridItem(props.data.i);
}
}
function clickHandle(params: EventParams | any) {
const { widgets } = getDashboard(dashboardStore.currentDashboard);
const associate = (props.data.associate && props.data.associate) || [];
for (const item of associate) {
const widget = widgets.find(
(d: LayoutConfig) => d.id === item.widgetId
);
if (widget) {
widget.filters = {
dataIndex: params.dataIndex,
sourceId: props.data.id || "",
};
dashboardStore.setWidget(widget);
}
}
}
watch(
() => [props.data.metricTypes, props.data.metrics],
() => {
@@ -189,7 +213,10 @@ export default defineComponent({
watch(
() => [selectorStore.currentPod, selectorStore.currentDestPod],
() => {
if (dashboardStore.entity === EntityType[0].value) {
if (
dashboardStore.entity === EntityType[0].value ||
dashboardStore.entity === EntityType[7].value
) {
return;
}
if (isList.value) {
@@ -198,6 +225,14 @@ export default defineComponent({
queryMetrics();
}
);
watch(
() => [selectorStore.currentProcess, selectorStore.currentDestProcess],
() => {
if (dashboardStore.entity === EntityType[7].value) {
queryMetrics();
}
}
);
watch(
() => appStore.durationTime,
() => {
@@ -221,6 +256,7 @@ export default defineComponent({
t,
graph,
widget,
clickHandle,
};
},
});

View File

@@ -23,6 +23,9 @@ import Log from "./Log.vue";
import Text from "./Text.vue";
import Ebpf from "./Ebpf.vue";
import DemandLog from "./DemandLog.vue";
import Event from "./Event.vue";
import NetworkProfiling from "./NetworkProfiling.vue";
import TimeRange from "./TimeRange.vue";
export default {
Tab,
@@ -34,4 +37,7 @@ export default {
Text,
Ebpf,
DemandLog,
Event,
NetworkProfiling,
TimeRange,
};

View File

@@ -0,0 +1,41 @@
/**
* 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 Widget from "./Widget.vue";
import Trace from "./Trace.vue";
import Profile from "./Profile.vue";
import Log from "./Log.vue";
import Text from "./Text.vue";
import Ebpf from "./Ebpf.vue";
import DemandLog from "./DemandLog.vue";
import Event from "./Event.vue";
import NetworkProfiling from "./NetworkProfiling.vue";
import TimeRange from "./TimeRange.vue";
export default {
Widget,
Trace,
Topology,
Profile,
Log,
Text,
Ebpf,
DemandLog,
Event,
NetworkProfiling,
TimeRange,
};

View File

@@ -15,7 +15,7 @@
* limitations under the License.
*/
export const dragIgnoreFrom =
"svg.d3-trace-tree, .dragger, .micro-topo-chart, .schedules";
"svg.d3-trace-tree, .dragger, .micro-topo-chart, .schedules, .vis-item, .vis-timeline, .process-svg";
export const PodsChartTypes = ["EndpointList", "InstanceList"];
@@ -159,6 +159,7 @@ export const EntityType = [
key: 4,
},
{ value: "EndpointRelation", label: "Endpoint Relation", key: 4 },
{ value: "ProcessRelation", label: "Process Relation", key: 5 },
];
export const ListEntity: any = {
InstanceList: EntityType[3].value,
@@ -178,15 +179,16 @@ export const AllTools = [
{ name: "assignment", content: "Add Log", id: "addLog" },
];
export const ServiceTools = [
{ name: "playlist_add", content: "Widget", id: "addWidget" },
{ name: "all_inbox", content: "Tab", id: "addTab" },
{ name: "library_books", content: "Text", id: "addText" },
{ name: "device_hub", content: "Topology", id: "addTopology" },
{ name: "merge", content: "Trace", id: "addTrace" },
{ name: "timeline", content: "Trace Profiling", id: "addProfile" },
{ name: "insert_chart", content: "eBPF Profiling", id: "addEbpf" },
{ name: "assignment", content: "Log", id: "addLog" },
{ name: "playlist_add", content: "Add Widget", id: "addWidget" },
{ name: "all_inbox", content: "Add Tab", id: "addTab" },
{ name: "library_books", content: "Add Text", id: "addText" },
{ name: "device_hub", content: "Add Topology", id: "addTopology" },
{ name: "merge", content: "Add Trace", id: "addTrace" },
{ name: "timeline", content: "Add Trace Profiling", id: "addProfile" },
{ name: "insert_chart", content: "Add eBPF Profiling", id: "addEbpf" },
{ name: "assignment", content: "Add Log", id: "addLog" },
{ name: "demand", content: "Add On Demand Log", id: "addDemandLog" },
{ name: "event", content: "Add Event", id: "addEvent" },
];
export const InstanceTools = [
{ name: "playlist_add", content: "Add Widget", id: "addWidget" },
@@ -195,6 +197,12 @@ export const InstanceTools = [
{ name: "merge", content: "Add Trace", id: "addTrace" },
{ name: "assignment", content: "Add Log", id: "addLog" },
{ name: "demand", content: "Add On Demand Log", id: "addDemandLog" },
{ name: "event", content: "Add Event", id: "addEvent" },
{
name: "timeline",
content: "Add Network Profiling",
id: "addNetworkProfiling",
},
];
export const EndpointTools = [
{ name: "playlist_add", content: "Add Widget", id: "addWidget" },
@@ -203,6 +211,13 @@ export const EndpointTools = [
{ name: "device_hub", content: "Add Topology", id: "addTopology" },
{ name: "merge", content: "Add Trace", id: "addTrace" },
{ name: "assignment", content: "Add Log", id: "addLog" },
{ name: "event", content: "Add Event", id: "addEvent" },
];
export const ProcessTools = [
{ name: "playlist_add", content: "Add Widget", id: "addWidget" },
{ name: "all_inbox", content: "Add Tab", id: "addTab" },
{ name: "library_books", content: "Add Text", id: "addText" },
{ name: "time_range", content: "Add Time Range Text", id: "addTimeRange" },
];
export const ServiceRelationTools = [
{ name: "playlist_add", content: "Add Widget", id: "addWidget" },
@@ -281,4 +296,6 @@ export const CalculationOpts = [
{ label: "Seconds to YYYY-MM-DD HH:mm:ss", value: "convertSeconds" },
{ label: "Precision is 2", value: "precision" },
{ label: "Milliseconds to seconds", value: "msTos" },
{ label: "Seconds to days", value: "secondToDay" },
{ label: "Nanoseconds to milliseconds", value: "nanosecondToMillisecond" },
];

View File

@@ -14,14 +14,20 @@ See the License for the specific language governing permissions and
limitations under the License. -->
<template>
<Line :data="data" :intervalTime="intervalTime" :config="config" />
<Line
:data="data"
:intervalTime="intervalTime"
:config="config"
@click="clickEvent"
/>
</template>
<script lang="ts" setup>
import type { PropType } from "vue";
import Line from "./Line.vue";
import { AreaConfig } from "@/types/dashboard";
import { AreaConfig, EventParams } from "@/types/dashboard";
/*global defineProps */
/*global defineProps, defineEmits */
const emits = defineEmits(["click"]);
defineProps({
data: {
type: Object as PropType<{ [key: string]: number[] }>,
@@ -29,8 +35,23 @@ defineProps({
},
intervalTime: { type: Array as PropType<string[]>, default: () => [] },
config: {
type: Object as PropType<AreaConfig>,
type: Object as PropType<
AreaConfig & {
filters: {
sourceId: string;
duration: {
startTime: string;
endTime: string;
};
isRange: boolean;
dataIndex?: number;
};
} & { id: string }
>,
default: () => ({}),
},
});
function clickEvent(params: EventParams) {
emits("click", params);
}
</script>

View File

@@ -13,15 +13,15 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. -->
<template>
<Graph :option="option" />
<Graph :option="option" @select="clickEvent" :filters="config.filters" />
</template>
<script lang="ts" setup>
import { computed } from "vue";
import type { PropType } from "vue";
import { Event } from "@/types/events";
import { BarConfig } from "@/types/dashboard";
import { BarConfig, EventParams } from "@/types/dashboard";
/*global defineProps */
/*global defineProps, defineEmits */
const emits = defineEmits(["click"]);
const props = defineProps({
data: {
type: Object as PropType<{ [key: string]: number[] }>,
@@ -29,9 +29,20 @@ const props = defineProps({
},
intervalTime: { type: Array as PropType<string[]>, default: () => [] },
theme: { type: String, default: "light" },
itemEvents: { type: Array as PropType<Event[]>, default: () => [] },
config: {
type: Object as PropType<BarConfig>,
type: Object as PropType<
BarConfig & {
filters: {
sourceId: string;
duration: {
startTime: string;
endTime: string;
};
isRange: boolean;
dataIndex?: number;
};
} & { id: string }
>,
default: () => ({}),
},
});
@@ -41,30 +52,7 @@ function getOption() {
const keys = Object.keys(props.data || {}).filter(
(i: any) => Array.isArray(props.data[i]) && props.data[i].length
);
const startP = keys.length > 1 ? 50 : 15;
const diff = 15;
const markAreas = (props.itemEvents || []).map(
(event: Event, index: number) => {
return [
{
name: `${event.name}:${event.type}`,
xAxis: event.startTime,
y: startP + diff * index,
itemStyle: {
borderWidth: 2,
borderColor: event.type === "Normal" ? "#5dc859" : "#FF0087",
color: event.type === "Normal" ? "#5dc859" : "#FF0087",
},
},
{
name: event.message,
xAxis: event.endTime,
y: startP + diff * (index + 1),
},
];
}
);
const temp = keys.map((i: string, index: number) => {
const temp = keys.map((i: string) => {
if (!props.intervalTime) {
return;
}
@@ -85,23 +73,6 @@ function getOption() {
backgroundStyle: {
color: "rgba(180, 180, 180, 0.1)",
},
markArea:
index === 0
? {
silent: false,
data: markAreas,
label: {
show: false,
width: 60,
},
emphasis: {
label: {
position: "bottom",
show: true,
},
},
}
: undefined,
};
});
let color: string[] = [];
@@ -139,11 +110,10 @@ function getOption() {
trigger: "axis",
zlevel: 1000,
z: 60,
backgroundColor: "rgb(50,50,50)",
confine: true,
textStyle: {
fontSize: 13,
color: "#ccc",
color: "#333",
},
enterable: true,
extraCssText: "max-height: 300px; overflow: auto; border: none",
@@ -186,4 +156,7 @@ function getOption() {
series: temp,
};
}
function clickEvent(params: EventParams) {
emits("click", params);
}
</script>

View File

@@ -46,6 +46,7 @@ limitations under the License. -->
:intervalTime="intervalTime"
:colMetrics="colMetrics"
:config="config"
v-if="colMetrics.length"
/>
</el-table>
</div>
@@ -96,7 +97,9 @@ const dashboardStore = useDashboardStore();
const chartLoading = ref<boolean>(false);
const endpoints = ref<Endpoint[]>([]);
const searchText = ref<string>("");
const colMetrics = computed(() => props.config.metrics.map((d: string) => d));
const colMetrics = computed(() =>
(props.config.metrics || []).filter((d: string) => d)
);
if (props.needQuery) {
queryEndpoints();
@@ -119,7 +122,7 @@ async function queryEndpointMetrics(currentPods: Endpoint[]) {
if (!currentPods.length) {
return;
}
const metrics = (props.config.metrics || []).filter((d: string) => d);
const metrics = props.config.metrics || [];
const metricTypes = props.config.metricTypes || [];
if (metrics.length && metrics[0] && metricTypes.length && metricTypes[0]) {
const params = await useQueryPodsMetrics(
@@ -144,17 +147,17 @@ async function queryEndpointMetrics(currentPods: Endpoint[]) {
endpoints.value = currentPods;
}
function clickEndpoint(scope: any) {
const d = getDashboard({
const { dashboard } = getDashboard({
name: props.config.dashboardName,
layer: dashboardStore.layerId,
entity: EntityType[2].value,
});
if (!d) {
if (!dashboard) {
ElMessage.error("No this dashboard");
return;
}
router.push(
`/dashboard/${d.layer}/${d.entity}/${selectorStore.currentService.id}/${scope.row.id}/${d.name}`
`/dashboard/${dashboard.layer}/${dashboard.entity}/${selectorStore.currentService.id}/${scope.row.id}/${dashboard.name}`
);
}
async function searchList() {

View File

@@ -43,6 +43,7 @@ limitations under the License. -->
</template>
</el-table-column>
<ColumnGraph
v-if="colMetrics.length"
:intervalTime="intervalTime"
:colMetrics="colMetrics"
:config="config"
@@ -126,7 +127,9 @@ const chartLoading = ref<boolean>(false);
const instances = ref<Instance[]>([]); // current instances
const pageSize = 10;
const searchText = ref<string>("");
const colMetrics = computed(() => props.config.metrics.map((d: string) => d));
const colMetrics = computed(() =>
(props.config.metrics || []).filter((d: string) => d)
);
if (props.needQuery) {
queryInstance();
}
@@ -151,7 +154,8 @@ async function queryInstanceMetrics(currentInstances: Instance[]) {
if (!currentInstances.length) {
return;
}
const { metrics, metricTypes } = props.config;
const metrics = props.config.metrics || [];
const metricTypes = props.config.metricTypes || [];
if (metrics.length && metrics[0] && metricTypes.length && metricTypes[0]) {
const params = await useQueryPodsMetrics(
@@ -176,19 +180,19 @@ async function queryInstanceMetrics(currentInstances: Instance[]) {
}
function clickInstance(scope: any) {
const d = getDashboard({
const { dashboard } = getDashboard({
name: props.config.dashboardName,
layer: dashboardStore.layerId,
entity: EntityType[3].value,
});
if (!d) {
if (!dashboard) {
ElMessage.error("No this dashboard");
return;
}
router.push(
`/dashboard/${d.layer}/${d.entity}/${selectorStore.currentService.id}/${
scope.row.id
}/${d.name.split(" ").join("-")}`
`/dashboard/${dashboard.layer}/${dashboard.entity}/${
selectorStore.currentService.id
}/${scope.row.id}/${dashboard.name.split(" ").join("-")}`
);
}

View File

@@ -13,15 +13,15 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. -->
<template>
<Graph :option="option" />
<Graph :option="option" @select="clickEvent" :filters="config.filters" />
</template>
<script lang="ts" setup>
import { computed } from "vue";
import type { PropType } from "vue";
import { Event } from "@/types/events";
import { LineConfig } from "@/types/dashboard";
import { LineConfig, EventParams } from "@/types/dashboard";
/*global defineProps */
/*global defineProps, defineEmits */
const emits = defineEmits(["click"]);
const props = defineProps({
data: {
type: Object as PropType<{ [key: string]: number[] }>,
@@ -29,9 +29,20 @@ const props = defineProps({
},
intervalTime: { type: Array as PropType<string[]>, default: () => [] },
theme: { type: String, default: "light" },
itemEvents: { type: Array as PropType<Event[]>, default: () => [] },
config: {
type: Object as PropType<LineConfig>,
type: Object as PropType<
LineConfig & {
filters: {
sourceId: string;
duration: {
startTime: string;
endTime: string;
};
isRange: boolean;
dataIndex?: number;
};
} & { id: string }
>,
default: () => ({
step: false,
smooth: false,
@@ -49,30 +60,7 @@ function getOption() {
const keys = Object.keys(props.data || {}).filter(
(i: any) => Array.isArray(props.data[i]) && props.data[i].length
);
const startP = keys.length > 1 ? 50 : 15;
const diff = 10;
const markAreas = (props.itemEvents || []).map(
(event: Event, index: number) => {
return [
{
name: `${event.name}:${event.type}`,
xAxis: event.startTime,
y: startP + diff * index,
itemStyle: {
borderWidth: 2,
borderColor: event.type === "Normal" ? "#5dc859" : "#FF0087",
color: event.type === "Normal" ? "#5dc859" : "#FF0087",
},
},
{
name: event.message,
xAxis: event.endTime,
y: startP + diff * (index + 1),
},
];
}
);
const temp = keys.map((i: any, index: number) => {
const temp = keys.map((i: any) => {
const serie: any = {
data: props.data[i].map((item: any, itemIndex: number) => [
props.intervalTime[itemIndex],
@@ -81,7 +69,7 @@ function getOption() {
name: i,
type: "line",
symbol: "circle",
symbolSize: 6,
symbolSize: 8,
showSymbol: props.config.showSymbol,
step: props.config.step,
smooth: props.config.smooth,
@@ -89,23 +77,6 @@ function getOption() {
width: 1.5,
type: "solid",
},
markArea:
index === 0
? {
silent: false,
data: markAreas,
label: {
show: false,
width: 60,
},
emphasis: {
label: {
position: "bottom",
show: true,
},
},
}
: undefined,
};
if (props.config.type === "Area") {
serie.areaStyle = {
@@ -145,10 +116,9 @@ function getOption() {
}
const tooltip = {
trigger: "axis",
backgroundColor: "rgb(50,50,50)",
textStyle: {
fontSize: 12,
color: "#ccc",
color: "#333",
},
enterable: true,
confine: true,
@@ -215,4 +185,8 @@ function getOption() {
series: temp,
};
}
function clickEvent(params: EventParams) {
emits("click", params);
}
</script>

View File

@@ -1,66 +0,0 @@
<!-- Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. -->
<template>
<Graph :option="option" />
</template>
<script lang="ts" setup>
import { computed } from "vue";
import type { PropType } from "vue";
/*global defineProps */
const props = defineProps({
data: {
type: Array as PropType<{ name: string; value: number }[]>,
default: () => [],
},
config: {
type: Object as PropType<{ sortOrder: string }>,
default: () => ({}),
},
});
const option = computed(() => getOption());
function getOption() {
return {
tooltip: {
trigger: "item",
},
legend: {
type: "scroll",
show: props.data.length === 1 ? false : true,
icon: "circle",
top: 0,
left: 0,
itemWidth: 12,
textStyle: {
color: "#333",
},
},
series: [
{
type: "pie",
radius: "50%",
data: props.data,
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: "rgba(0, 0, 0, 0.5)",
},
},
},
],
};
}
</script>

View File

@@ -58,6 +58,7 @@ limitations under the License. -->
:intervalTime="intervalTime"
:colMetrics="colMetrics"
:config="config"
v-if="colMetrics.length"
/>
</el-table>
</div>
@@ -81,6 +82,7 @@ import type { PropType } from "vue";
import { ServiceListConfig } from "@/types/dashboard";
import { useSelectorStore } from "@/store/modules/selectors";
import { useDashboardStore } from "@/store/modules/dashboard";
import { useAppStoreWithOut } from "@/store/modules/app";
import { Service } from "@/types/selector";
import { useQueryPodsMetrics, usePodsSource } from "@/hooks/useProcessor";
import { EntityType } from "../data";
@@ -110,6 +112,7 @@ const props = defineProps({
});
const selectorStore = useSelectorStore();
const dashboardStore = useDashboardStore();
const appStore = useAppStoreWithOut();
const chartLoading = ref<boolean>(false);
const pageSize = 10;
const services = ref<Service[]>([]);
@@ -117,7 +120,7 @@ const searchText = ref<string>("");
const groups = ref<any>({});
const sortServices = ref<(Service & { merge: boolean })[]>([]);
const colMetrics = computed(() =>
props.config.metrics.filter((d: string) => d)
(props.config.metrics || []).filter((d: string) => d)
);
queryServices();
@@ -178,16 +181,16 @@ function setServices(arr: (Service & { merge: boolean })[]) {
}
function clickService(scope: any) {
const d = getDashboard({
const { dashboard } = getDashboard({
name: props.config.dashboardName,
layer: dashboardStore.layerId,
entity: EntityType[0].value,
});
if (!d) {
if (!dashboard) {
ElMessage.error("No this dashboard");
return;
}
const path = `/dashboard/${d.layer}/${d.entity}/${scope.row.id}/${d.name}`;
const path = `/dashboard/${dashboard.layer}/${dashboard.entity}/${scope.row.id}/${dashboard.name}`;
router.push(path);
}
@@ -195,7 +198,8 @@ async function queryServiceMetrics(currentServices: Service[]) {
if (!currentServices.length) {
return;
}
const { metrics, metricTypes } = props.config;
const metrics = props.config.metrics || [];
const metricTypes = props.config.metricTypes || [];
if (metrics.length && metrics[0] && metricTypes.length && metricTypes[0]) {
const params = await useQueryPodsMetrics(
@@ -271,6 +275,14 @@ watch(
queryServiceMetrics(services.value);
}
);
watch(
() => appStore.durationTime,
() => {
if (dashboardStore.entity === EntityType[1].value) {
queryServices();
}
}
);
</script>
<style lang="scss" scoped>
@import "./style.scss";

View File

@@ -21,7 +21,6 @@ import Bar from "./Bar.vue";
import HeatMap from "./HeatMap.vue";
import TopList from "./TopList.vue";
import Table from "./Table.vue";
import Pie from "./Pie.vue";
import Card from "./Card.vue";
import InstanceList from "./InstanceList.vue";
import EndpointList from "./EndpointList.vue";
@@ -34,7 +33,6 @@ export default {
TopList,
Area,
Table,
Pie,
Card,
EndpointList,
InstanceList,

View File

@@ -0,0 +1,32 @@
/**
* 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 Area from "./Area.vue";
import Line from "./Line.vue";
import Bar from "./Bar.vue";
import TopList from "./TopList.vue";
import Table from "./Table.vue";
import Card from "./Card.vue";
export default {
Line,
Bar,
TopList,
Area,
Table,
Card,
};

View File

@@ -28,7 +28,7 @@ limitations under the License. -->
:w="item.w"
:h="item.h"
:i="item.i"
:key="item.i"
:key="item.id"
@click="clickGrid(item, $event)"
:class="{ active: dashboardStore.activedGridItem === item.i }"
:drag-ignore-from="dragIgnoreFrom"

View File

@@ -14,6 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. -->
<template>
<div class="dashboard-tool flex-h">
<div :class="isRelation ? 'flex-v' : 'flex-h'">
<div class="flex-h">
<div class="selectors-item" v-if="key !== 10">
<span class="label">$Service</span>
@@ -26,7 +27,7 @@ limitations under the License. -->
class="selectors"
/>
</div>
<div class="selectors-item" v-if="key === 3 || key === 4">
<div class="selectors-item" v-if="key === 3 || key === 4 || key === 5">
<span class="label">
{{
["EndpointRelation", "Endpoint"].includes(dashboardStore.entity)
@@ -47,7 +48,20 @@ limitations under the License. -->
"
/>
</div>
<div class="selectors-item" v-if="key === 2 || key === 4">
<div class="selectors-item" v-if="key === 5">
<span class="label"> $Process </span>
<Selector
v-model="states.currentProcess"
:options="selectorStore.processes"
size="small"
placeholder="Select a data"
@change="changeProcess"
class="selectors"
/>
</div>
</div>
<div class="flex-h" :class="isRelation ? 'relation' : ''">
<div class="selectors-item" v-if="key === 2 || key === 4 || key === 5">
<span class="label">$DestinationService</span>
<Selector
v-model="states.currentDestService"
@@ -58,7 +72,7 @@ limitations under the License. -->
class="selectors"
/>
</div>
<div class="selectors-item" v-if="key === 4">
<div class="selectors-item" v-if="key === 4 || key === 5">
<span class="label">
{{
dashboardStore.entity === "EndpointRelation"
@@ -77,9 +91,21 @@ limitations under the License. -->
:isRemote="dashboardStore.entity === 'EndpointRelation'"
/>
</div>
<div class="selectors-item" v-if="key === 5">
<span class="label"> $DestinationProcess </span>
<Selector
v-model="states.currentDestProcess"
:options="selectorStore.destProcesses"
size="small"
placeholder="Select a data"
@change="changeDestProcess"
class="selectors"
/>
</div>
</div>
</div>
<div class="flex-h tools" v-loading="loading" v-if="!appStore.isMobile">
<div class="tool-icons flex-h" v-if="dashboardStore.editMode">
<div class="tool-icons" v-if="dashboardStore.editMode">
<el-dropdown content="Controls" placement="bottom" :persistent="false">
<i>
<Icon class="icon-btn" size="sm" iconName="control" />
@@ -133,6 +159,7 @@ import {
EndpointRelationTools,
InstanceRelationTools,
ServiceRelationTools,
ProcessTools,
} from "../data";
import { useSelectorStore } from "@/store/modules/selectors";
import { ElMessage } from "element-plus";
@@ -144,9 +171,8 @@ const dashboardStore = useDashboardStore();
const selectorStore = useSelectorStore();
const appStore = useAppStoreWithOut();
const params = useRoute().params;
const toolIcons = ref<{ name: string; content: string; id: string }[]>(
EndpointRelationTools
);
const toolIcons =
ref<{ name: string; content: string; id: string }[]>(AllTools);
const loading = ref<boolean>(false);
const states = reactive<{
destService: string;
@@ -154,16 +180,20 @@ const states = reactive<{
key: number;
currentService: string;
currentPod: string;
currentProcess: string;
currentDestService: string;
currentDestPod: string;
currentDestProcess: string;
}>({
destService: "",
destPod: "",
key: 0,
currentService: "",
currentPod: "",
currentProcess: "",
currentDestService: "",
currentDestPod: "",
currentDestProcess: "",
});
const key = computed(() => {
const type = EntityType.find(
@@ -172,6 +202,14 @@ const key = computed(() => {
return (type && type.key) || 0;
});
const isRelation = computed(() => {
return [
EntityType[7].value,
EntityType[6].value,
EntityType[5].value,
].includes(dashboardStore.entity);
});
setCurrentDashboard();
appStore.setEventStack([initSelector]);
initSelector();
@@ -199,6 +237,7 @@ async function setSelector() {
EntityType[3].value,
EntityType[5].value,
EntityType[6].value,
EntityType[7].value,
].includes(String(params.entity))
) {
setSourceSelector();
@@ -248,25 +287,7 @@ async function setSourceSelector() {
await selectorStore.getService(String(params.serviceId));
states.currentService = selectorStore.currentService.value;
const e = String(params.entity).split("Relation")[0];
await fetchPods(e, selectorStore.currentService.id, false);
if (!(selectorStore.pods.length && selectorStore.pods[0])) {
selectorStore.setCurrentPod(null);
states.currentPod = "";
return;
}
const pod = params.podId || selectorStore.pods[0].id;
let currentPod;
if (states.currentPod) {
currentPod = selectorStore.pods.find(
(d: { label: string }) => d.label === states.currentPod
);
} else {
currentPod = selectorStore.pods.find((d: { id: string }) => d.id === pod);
}
if (currentPod) {
selectorStore.setCurrentPod(currentPod);
states.currentPod = currentPod.label;
}
await fetchPods(e, selectorStore.currentService.id, true);
}
async function setDestSelector() {
@@ -275,28 +296,8 @@ async function setDestSelector() {
await fetchPods(
String(params.entity),
selectorStore.currentDestService.id,
false
true
);
if (!(selectorStore.destPods.length && selectorStore.destPods[0])) {
selectorStore.setCurrentDestPod(null);
states.currentDestPod = "";
return;
}
const destPod = params.destPodId || selectorStore.destPods[0].id;
let currentDestPod = { label: "" };
if (states.currentDestPod) {
currentDestPod = selectorStore.pods.find(
(d: { label: string }) => d.label === states.currentDestPod
);
} else {
currentDestPod = selectorStore.destPods.find(
(d: { id: string }) => d.id === destPod
);
}
if (currentDestPod) {
selectorStore.setCurrentDestPod(currentDestPod);
states.currentDestPod = currentDestPod.label;
}
}
async function getServices() {
@@ -325,16 +326,21 @@ async function getServices() {
);
}
selectorStore.setCurrentService(s || null);
let d;
let d,
val = 1;
if (key.value === 5) {
val = 0;
}
if (states.currentService) {
d = (selectorStore.services || []).find(
(d: { label: string }) => d.label === states.currentDestService
);
} else {
d = (selectorStore.services || []).find(
(d: unknown, index: number) => index === 1
(d: unknown, index: number) => index === val
);
}
selectorStore.setCurrentDestService(d || null);
if (!selectorStore.currentService) {
return;
@@ -347,62 +353,86 @@ async function getServices() {
EntityType[3].value,
EntityType[5].value,
EntityType[6].value,
EntityType[7].value,
].includes(dashboardStore.entity)
) {
fetchPods(e, selectorStore.currentService.id, true);
await fetchPods(e, selectorStore.currentService.id, true);
}
if (!selectorStore.currentDestService) {
return;
}
states.currentDestService = selectorStore.currentDestService.value;
if (
[EntityType[5].value, EntityType[6].value].includes(dashboardStore.entity)
[EntityType[5].value, EntityType[6].value, EntityType[7].value].includes(
dashboardStore.entity
)
) {
fetchPods(dashboardStore.entity, selectorStore.currentDestService.id, true);
await fetchPods(
dashboardStore.entity,
selectorStore.currentDestService.id,
true
);
}
}
async function changeService(service: any) {
async function changeService(service: Option[]) {
if (service[0]) {
states.currentService = service[0].value;
selectorStore.setCurrentService(service[0]);
const e = dashboardStore.entity.split("Relation")[0];
selectorStore.setCurrentPod(null);
states.currentPod = "";
fetchPods(e, selectorStore.currentService.id, true);
states.currentProcess = "";
if (dashboardStore.entity === EntityType[7].value) {
fetchPods("Process", selectorStore.currentService.id, true);
} else {
fetchPods(dashboardStore.entity, selectorStore.currentService.id, true);
}
} else {
selectorStore.setCurrentService(null);
}
}
function changeDestService(service: any) {
function changeDestService(service: Option[]) {
if (service[0]) {
states.currentDestService = service[0].value;
selectorStore.setCurrentDestService(service[0]);
selectorStore.setCurrentDestPod(null);
states.currentDestPod = "";
states.currentDestProcess = "";
fetchPods(dashboardStore.entity, selectorStore.currentDestService.id, true);
} else {
selectorStore.setCurrentDestService(null);
}
}
function changePods(pod: any) {
if (pod[0]) {
selectorStore.setCurrentPod(pod[0]);
} else {
selectorStore.setCurrentPod("");
async function changePods(pod: Option[]) {
selectorStore.setCurrentPod(pod[0] || null);
if (dashboardStore.entity === EntityType[7].value) {
selectorStore.setCurrentProcess(null);
states.currentProcess = "";
fetchProcess(true);
}
}
function changeDestPods(pod: any) {
if (pod[0]) {
selectorStore.setCurrentDestPod(pod[0]);
} else {
selectorStore.setCurrentDestPod(null);
function changeDestPods(pod: Option[]) {
selectorStore.setCurrentDestPod(pod[0] || null);
if (dashboardStore.entity === EntityType[7].value) {
selectorStore.setCurrentDestProcess(null);
states.currentDestProcess = "";
fetchDestProcess(true);
}
}
function changeDestProcess(pod: Option[]) {
selectorStore.setCurrentDestProcess(pod[0] || null);
states.currentDestProcess = pod[0].label || "";
}
function changeProcess(pod: Option[]) {
selectorStore.setCurrentProcess(pod[0] || null);
states.currentProcess = pod[0].label || "";
}
function changeMode() {
if (dashboardStore.editMode) {
ElMessage.warning(t("editWarning"));
@@ -458,6 +488,15 @@ function setTabControls(id: string) {
case "addDemandLog":
dashboardStore.addTabControls("DemandLog");
break;
case "addEvent":
dashboardStore.addTabControls("Event");
break;
case "addNetworkProfiling":
dashboardStore.addTabControls("NetworkProfiling");
break;
case "addTimeRange":
dashboardStore.addTabControls("TimeRange");
break;
default:
ElMessage.info("Don't support this control");
break;
@@ -493,6 +532,15 @@ function setControls(id: string) {
case "addDemandLog":
dashboardStore.addControl("DemandLog");
break;
case "addEvent":
dashboardStore.addControl("Event");
break;
case "addNetworkProfiling":
dashboardStore.addControl("NetworkProfiling");
break;
case "addTimeRange":
dashboardStore.addControl("TimeRange");
break;
default:
dashboardStore.addControl("Widget");
}
@@ -509,35 +557,13 @@ async function fetchPods(
case EntityType[2].value:
resp = await selectorStore.getEndpoints({ serviceId, ...param });
if (setPod) {
let p;
if (states.currentPod) {
p = selectorStore.pods.find(
(d: { label: unknown }) => d.label === states.currentPod
);
} else {
p = selectorStore.pods.find(
(d: unknown, index: number) => index === 0
);
}
selectorStore.setCurrentPod(p || null);
states.currentPod = selectorStore.currentPod.label;
updateCurrentPod();
}
break;
case EntityType[3].value:
resp = await selectorStore.getServiceInstances({ serviceId });
if (setPod) {
let p;
if (states.currentPod) {
p = selectorStore.pods.find(
(d: { label: string }) => d.label === states.currentPod
);
} else {
p = selectorStore.pods.find(
(d: { label: string }, index: number) => index === 0
);
}
selectorStore.setCurrentPod(p || null);
states.currentPod = selectorStore.currentPod.label;
updateCurrentPod();
}
break;
case EntityType[6].value:
@@ -547,18 +573,7 @@ async function fetchPods(
...param,
});
if (setPod) {
let p;
if (states.currentDestPod) {
p = selectorStore.destPods.find(
(d: { label: string }) => d.label === states.currentDestPod
);
} else {
p = selectorStore.destPods.find(
(d: { label: string }, index: number) => index === 0
);
}
selectorStore.setCurrentDestPod(p || null);
states.currentDestPod = selectorStore.currentDestPod.label;
updateCurrentDestPod();
}
break;
case EntityType[5].value:
@@ -567,20 +582,17 @@ async function fetchPods(
isRelation: true,
});
if (setPod) {
let p;
if (states.currentDestPod) {
p = selectorStore.destPods.find(
(d: { label: string }) => d.label === states.currentDestPod
);
} else {
p = selectorStore.destPods.find(
(d: { label: string }, index: number) => index === 0
);
}
selectorStore.setCurrentDestPod(p || null);
states.currentDestPod = selectorStore.currentDestPod.label;
updateCurrentDestPod();
}
break;
case EntityType[7].value:
await fetchPods(EntityType[5].value, serviceId, setPod, param);
resp = await fetchDestProcess(setPod);
break;
case "Process":
await fetchPods(EntityType[3].value, serviceId, setPod, param);
resp = await fetchProcess(setPod);
break;
default:
resp = {};
}
@@ -588,6 +600,81 @@ async function fetchPods(
ElMessage.error(resp.errors);
}
}
async function fetchProcess(setPod: boolean) {
const resp = await selectorStore.getProcesses({
instanceId: selectorStore.currentPod.id,
});
if (setPod) {
const process = params.processId || selectorStore.processes[0].id;
const currentProcess = selectorStore.processes.find(
(d: { id: string }) => d.id === process
);
if (currentProcess) {
selectorStore.setCurrentProcess(currentProcess);
states.currentProcess = currentProcess.label;
}
}
return resp;
}
async function fetchDestProcess(setPod: boolean) {
const resp = await selectorStore.getProcesses({
instanceId: selectorStore.currentDestPod.id,
isRelation: true,
});
if (setPod) {
const destProcess =
params.destProcessId || selectorStore.destProcesses[0].id;
const currentDestProcess = selectorStore.destProcesses.find(
(d: { id: string }) => d.id === destProcess
);
if (!currentDestProcess) {
states.currentDestProcess = "";
selectorStore.setCurrentDestProcess(null);
return;
}
selectorStore.setCurrentDestProcess(currentDestProcess);
states.currentDestProcess = currentDestProcess.label;
}
return resp;
}
function updateCurrentDestPod() {
if (!(selectorStore.destPods.length && selectorStore.destPods[0])) {
selectorStore.setCurrentDestPod(null);
states.currentDestPod = "";
return;
}
const destPod = params.destPodId || selectorStore.destPods[0].id;
const currentDestPod = selectorStore.destPods.find(
(d: { id: string }) => d.id === destPod
);
if (!currentDestPod) {
states.currentDestPod = "";
selectorStore.setCurrentDestPod(null);
return;
}
selectorStore.setCurrentDestPod(currentDestPod);
states.currentDestPod = currentDestPod.label;
}
function updateCurrentPod() {
if (!(selectorStore.pods.length && selectorStore.pods[0])) {
selectorStore.setCurrentPod(null);
states.currentPod = "";
return;
}
const pod = params.podId || selectorStore.pods[0].id;
const currentPod = selectorStore.pods.find(
(d: { id: string }) => d.id === pod
);
if (!currentPod) {
selectorStore.setCurrentPod(null);
states.currentPod = "";
return;
}
selectorStore.setCurrentPod(currentPod);
states.currentPod = currentPod.label;
}
function getTools() {
switch (params.entity) {
case EntityType[1].value:
@@ -611,8 +698,11 @@ function getTools() {
case EntityType[6].value:
toolIcons.value = EndpointRelationTools;
break;
case EntityType[7].value:
toolIcons.value = ProcessTools;
break;
default:
toolIcons.value = EndpointRelationTools;
toolIcons.value = AllTools;
}
}
function searchPods(query: string) {
@@ -695,4 +785,8 @@ watch(
.selectorPod {
width: 300px;
}
.relation {
margin-top: 5px;
}
</style>

View File

@@ -0,0 +1,81 @@
<!-- 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="profile-detail flex-v">
<div>
<h5 class="mb-10">{{ t("task") }}.</h5>
<div class="mb-10 clear item">
<span class="g-sm-4 grey">{{ t("taskId") }}:</span>
<span class="g-sm-8 wba">
{{ details.taskId }}
</span>
</div>
<div class="mb-10 clear item">
<span class="g-sm-4 grey">{{ t("service") }}:</span>
<span class="g-sm-8 wba">
{{ details.serviceName }}
</span>
</div>
<div class="mb-10 clear item">
<span class="g-sm-4 grey">{{ t("labels") }}:</span>
<span class="g-sm-8 wba">
{{ details.processLabels.join(";") }}
</span>
</div>
<div class="mb-10 clear item">
<span class="g-sm-4 grey">{{ t("monitorTime") }}:</span>
<span class="g-sm-8 wba">
{{ dateFormat(details.taskStartTime) }}
</span>
</div>
<div class="mb-10 clear item">
<span class="g-sm-4 grey">{{ t("monitorDuration") }}:</span>
<span class="g-sm-8 wba">
{{ details.fixedTriggerDuration / 60 }} min
</span>
</div>
<div class="mb-10 clear item">
<span class="g-sm-4 grey">{{ t("triggerType") }}:</span>
<span class="g-sm-8 wba">{{ details.triggerType }}</span>
</div>
<div class="mb-10 clear item">
<span class="g-sm-4 grey">{{ t("targetType") }}:</span>
<span class="g-sm-8 wba">{{ details.targetType }}</span>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import type { PropType } from "vue";
import dayjs from "dayjs";
import { useI18n } from "vue-i18n";
import { EBPFTaskList } from "@/types/ebpf";
/*global defineProps */
defineProps({
details: {
type: Object as PropType<EBPFTaskList>,
default: () => ({}),
},
});
const { t } = useI18n();
const dateFormat = (date: number, pattern = "YYYY-MM-DD HH:mm:ss") =>
dayjs(date).format(pattern);
</script>
<style lang="scss" scoped>
.item span {
height: 21px;
}
</style>

Some files were not shown because too many files have changed in this diff Show More