diff --git a/src/views/dashboard/related/log/LogTable/LogService.vue b/src/views/dashboard/related/log/LogTable/LogService.vue
index c04b4980..549029e6 100644
--- a/src/views/dashboard/related/log/LogTable/LogService.vue
+++ b/src/views/dashboard/related/log/LogTable/LogService.vue
@@ -27,7 +27,12 @@ limitations under the License. -->
-
+
+
+ {{ part.text }}
+ {{ part.text }}
+
+
@@ -59,10 +64,41 @@ limitations under the License. -->
}
return (props.data.tags.find((d: { key: string; value: string }) => d.key === "level") || {}).value || "";
});
- const highlightKeywords = (content: string) => {
- const keywords = Object.values(logStore.conditions.keywordsOfContent || {});
- const regex = new RegExp(keywords.join("|"), "gi");
- return `${content}`.replace(regex, (match) => `${match}`);
+ type HighlightPart = {
+ text: string;
+ highlight: boolean;
+ };
+
+ const escapeRegExp = (value: string) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
+
+ const highlightKeywords = (content: string): HighlightPart[] => {
+ const text = `${content || ""}`;
+ const keywords = [
+ ...new Set(Object.values(logStore.conditions.keywordsOfContent || {}).map((keyword) => `${keyword}`.trim())),
+ ].filter(Boolean);
+
+ if (!keywords.length) {
+ return [{ text, highlight: false }];
+ }
+
+ const regex = new RegExp(keywords.map(escapeRegExp).join("|"), "gi");
+ const parts: HighlightPart[] = [];
+ let lastIndex = 0;
+ let match: RegExpExecArray | null;
+
+ while ((match = regex.exec(text)) !== null) {
+ if (match.index > lastIndex) {
+ parts.push({ text: text.slice(lastIndex, match.index), highlight: false });
+ }
+ parts.push({ text: match[0], highlight: true });
+ lastIndex = regex.lastIndex;
+ }
+
+ if (lastIndex < text.length) {
+ parts.push({ text: text.slice(lastIndex), highlight: false });
+ }
+
+ return parts.length ? parts : [{ text, highlight: false }];
};
function getDataValue(label: string) {
@@ -165,4 +201,8 @@ limitations under the License. -->
.warning {
color: var(--sw-orange);
}
+
+ .keyword-highlight {
+ color: var(--sw-red);
+ }
diff --git a/src/views/dashboard/related/network-profiling/components/ProcessTopology.vue b/src/views/dashboard/related/network-profiling/components/ProcessTopology.vue
index d4a0f28b..e25191e6 100644
--- a/src/views/dashboard/related/network-profiling/components/ProcessTopology.vue
+++ b/src/views/dashboard/related/network-profiling/components/ProcessTopology.vue
@@ -461,14 +461,28 @@ limitations under the License. -->
}
}
- function showNodeTip(d: ProcessNode, event: MouseEvent) {
- const tipHtml = `
name: ${d.name}
`;
+ type TooltipRow = {
+ label: string;
+ value: unknown;
+ };
+ function renderTooltipRows(rows: TooltipRow[]) {
+ tooltip.value.html("");
+ const row = tooltip.value.selectAll("div").data(rows).enter().append("div").attr("class", "mb-5");
+
+ row
+ .append("span")
+ .attr("class", "grey")
+ .text((d: TooltipRow) => `${d.label}: `);
+ row.append("span").text((d: TooltipRow) => `${d.value ?? ""}`);
+ }
+
+ function showNodeTip(d: ProcessNode, event: MouseEvent) {
tooltip.value
.style("top", event.offsetY + "px")
.style("left", event.offsetX + "px")
- .style("visibility", "visible")
- .html(tipHtml);
+ .style("visibility", "visible");
+ renderTooltipRows([{ label: "name", value: d.name }]);
}
function hideNodeTip() {
@@ -487,14 +501,14 @@ limitations under the License. -->
if (types.includes("tls")) {
l = "TLS";
}
- const tipHtml = `${t("detectPoint")}: ${link.detectPoints.join(" | ")}
- Type: ${l}
`;
-
tooltip.value
.style("top", event.offsetY + "px")
.style("left", event.offsetX + "px")
- .style("visibility", "visible")
- .html(tipHtml);
+ .style("visibility", "visible");
+ renderTooltipRows([
+ { label: t("detectPoint"), value: link.detectPoints.join(" | ") },
+ { label: "Type", value: l },
+ ]);
}
function hideLinkTip() {
diff --git a/src/views/dashboard/related/topology/pod/InstanceMap.vue b/src/views/dashboard/related/topology/pod/InstanceMap.vue
index 01deabd6..f4d2e705 100644
--- a/src/views/dashboard/related/topology/pod/InstanceMap.vue
+++ b/src/views/dashboard/related/topology/pod/InstanceMap.vue
@@ -122,27 +122,33 @@ limitations under the License. -->
).dashboard || {};
const exprssions = dashboard.expressions || [];
const nodeMetricConfig = dashboard.expressionsConfig || [];
- const html = exprssions.map((m: string, index: number) => {
+ const metrics = exprssions.map((m: string, index: number) => {
const metric =
topologyStore.hierarchyInstanceNodeMetrics[data.layer || ""][m].values.find(
(val: { id: string; value: unknown }) => val.id === data.id,
) || null;
const opt: MetricConfigOpt = nodeMetricConfig[index] || {};
- return ` ${opt.label || m}: ${metric?.value || NaN} ${
- opt.unit || ""
- }
`;
+ return {
+ label: opt.label || m,
+ value: `${metric?.value || NaN} ${opt.unit || ""}`,
+ };
});
- const tipHtml = [
- `name: ${data.name}
layer: ${data.layer}
`,
- ...html,
- ].join(" ");
popover.value
.style("top", event.offsetY + 10 + "px")
.style("left", event.offsetX + 10 + "px")
.style("visibility", "visible")
- .html(tipHtml);
+ .html("");
+
+ const rows = [{ label: "name", value: data.name }, { label: "layer", value: data.layer }, ...metrics];
+
+ const row = popover.value.selectAll("div").data(rows).enter().append("div").attr("class", "mb-5");
+ row
+ .append("span")
+ .attr("class", "grey")
+ .text((d: { label: string }) => `${d.label}: `);
+ row.append("span").text((d: { value: unknown }) => `${d.value ?? ""}`);
}
function hideTip() {
diff --git a/src/views/dashboard/related/topology/service/HierarchyMap.vue b/src/views/dashboard/related/topology/service/HierarchyMap.vue
index e6a8a65a..7c2c81ef 100644
--- a/src/views/dashboard/related/topology/service/HierarchyMap.vue
+++ b/src/views/dashboard/related/topology/service/HierarchyMap.vue
@@ -130,26 +130,32 @@ limitations under the License. -->
).dashboard || {};
const exprssions = dashboard.expressions || [];
const nodeMetricConfig = dashboard.expressionsConfig || [];
- const html = exprssions.map((m: string, index: number) => {
+ const metrics = exprssions.map((m: string, index: number) => {
const metric =
topologyStore.hierarchyNodeMetrics[data.layer || ""][m].values.find(
(val: { id: string; value: unknown }) => val.id === data.id,
) || null;
const opt: MetricConfigOpt = nodeMetricConfig[index] || {};
- return ` ${opt.label || m}: ${metric?.value || NaN} ${
- opt.unit || ""
- }
`;
- });
- const tipHtml = [
- `name: ${data.name}
layer: ${data.layer}
`,
- ...html,
- ].join(" ");
+ return {
+ label: opt.label || m,
+ value: `${metric?.value || NaN} ${opt.unit || ""}`,
+ };
+ });
popover.value
.style("top", event.offsetY + 10 + "px")
.style("left", event.offsetX + 10 + "px")
.style("visibility", "visible")
- .html(tipHtml);
+ .html("");
+
+ const rows = [{ label: "name", value: data.name }, { label: "layer", value: data.layer }, ...metrics];
+
+ const row = popover.value.selectAll("div").data(rows).enter().append("div").attr("class", "mb-5");
+ row
+ .append("span")
+ .attr("class", "grey")
+ .text((d: { label: string }) => `${d.label}: `);
+ row.append("span").text((d: { value: unknown }) => `${d.value ?? ""}`);
}
function hideTip() {
diff --git a/src/views/dashboard/related/topology/service/ServiceMap.vue b/src/views/dashboard/related/topology/service/ServiceMap.vue
index eae23fbe..1c241d38 100644
--- a/src/views/dashboard/related/topology/service/ServiceMap.vue
+++ b/src/views/dashboard/related/topology/service/ServiceMap.vue
@@ -304,70 +304,103 @@ limitations under the License. -->
}
return Number(d[legendMQE.expression]) && d.isReal ? icons.CUBEERROR : icons.CUBE;
}
+
+ type TooltipRow = {
+ label: string;
+ value: unknown;
+ };
+
+ function isTooltipRow(row: TooltipRow | null): row is TooltipRow {
+ return Boolean(row);
+ }
+
+ function renderTooltipRows(rows: TooltipRow[]) {
+ tooltip.value.html("");
+ const row = tooltip.value.selectAll("div").data(rows).enter().append("div").attr("class", "mb-5");
+
+ row
+ .append("span")
+ .attr("class", "grey")
+ .text((d: TooltipRow) => `${d.label}: `);
+ row.append("span").text((d: TooltipRow) => `${d.value ?? ""}`);
+ }
+
function showNodeTip(event: MouseEvent, data: Node) {
const nodeMetrics: string[] = settings.value.nodeExpressions || [];
const nodeMetricConfig = settings.value.nodeMetricConfig || [];
- const html = nodeMetrics.map((m, index) => {
+ const metrics = nodeMetrics.map((m, index) => {
const metric =
topologyStore.nodeMetricValue[m]?.values?.find((val: { id: string; value: unknown }) => val.id === data.id) ||
null;
const opt: MetricConfigOpt = nodeMetricConfig[index] || {};
- return ` ${opt.label || m}: ${metric?.value || NaN} ${
- opt.unit || "unknown"
- }
`;
+
+ return {
+ label: opt.label || m,
+ value: `${metric?.value || NaN} ${opt.unit || "unknown"}`,
+ };
});
- let tipHtml = `name: ${
- data.name
- }
type: ${data.type || "UNKNOWN"}
`;
- if (data.isReal) {
- tipHtml = [tipHtml, ...html].join(" ");
- }
+ const rows = [
+ { label: "name", value: data.name },
+ { label: "type", value: data.type || "UNKNOWN" },
+ ];
+ const tipRows = data.isReal ? [...rows, ...metrics] : rows;
+
tooltip.value
.style("top", event.offsetY + 10 + "px")
.style("left", event.offsetX + 10 + "px")
- .style("visibility", "visible")
- .html(tipHtml);
+ .style("visibility", "visible");
+ renderTooltipRows(tipRows);
}
function showLinkTip(event: MouseEvent, data: Call) {
const linkClientMetrics: string[] = settings.value.linkClientExpressions || [];
const linkServerMetricConfig: MetricConfigOpt[] = settings.value.linkServerMetricConfig || [];
const linkClientMetricConfig: MetricConfigOpt[] = settings.value.linkClientMetricConfig || [];
const linkServerMetrics: string[] = settings.value.linkServerExpressions || [];
- const htmlServer = linkServerMetrics.map((m, index) => {
- const metric = topologyStore.linkServerMetrics[m]?.values?.find(
- (val: { id: string; value: unknown }) => val.id === data.id,
- );
- if (metric) {
- const opt: MetricConfigOpt = linkServerMetricConfig[index] || {};
- return ` ${opt.label || m}: ${metric.value || NaN} ${
- opt.unit || ""
- }
`;
- }
- });
- const htmlClient = linkClientMetrics.map((m: string, index: number) => {
- const opt: MetricConfigOpt = linkClientMetricConfig[index] || {};
- const metric = topologyStore.linkClientMetrics[m]?.values?.find(
- (val: { id: string; value: unknown }) => val.id === data.id,
- );
- if (metric) {
- return ` ${opt.label || m}: ${metric.value || NaN} ${
- opt.unit || ""
- }
`;
- }
- });
- const html = [
- ...htmlServer,
- ...htmlClient,
- `${t("detectPoint")}:${data.detectPoints.join(" | ")}
`,
- ].join(" ");
+ const serverRows = linkServerMetrics
+ .map((m, index): TooltipRow | null => {
+ const metric = topologyStore.linkServerMetrics[m]?.values?.find(
+ (val: { id: string; value: unknown }) => val.id === data.id,
+ );
+ if (metric) {
+ const opt: MetricConfigOpt = linkServerMetricConfig[index] || {};
+ return {
+ label: opt.label || m,
+ value: `${metric.value || NaN} ${opt.unit || ""}`,
+ };
+ }
+ return null;
+ })
+ .filter(isTooltipRow);
+ const clientRows = linkClientMetrics
+ .map((m: string, index: number): TooltipRow | null => {
+ const opt: MetricConfigOpt = linkClientMetricConfig[index] || {};
+ const metric = topologyStore.linkClientMetrics[m]?.values?.find(
+ (val: { id: string; value: unknown }) => val.id === data.id,
+ );
+ if (metric) {
+ return {
+ label: opt.label || m,
+ value: `${metric.value || NaN} ${opt.unit || ""}`,
+ };
+ }
+ return null;
+ })
+ .filter(isTooltipRow);
+ const rows = [
+ ...serverRows,
+ ...clientRows,
+ {
+ label: t("detectPoint"),
+ value: data.detectPoints.join(" | "),
+ },
+ ];
tooltip.value
.style("top", event.offsetY + "px")
.style("left", event.offsetX + "px")
- .style("visibility", "visible")
- .html(html);
+ .style("visibility", "visible");
+ renderTooltipRows(rows);
}
-
function hideTip() {
tooltip.value.style("visibility", "hidden");
}