mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2025-03-21 15:45:01 +00:00
vmui: support for drag'n'drop in the "Trace analyzer" page (#3971)
vmui: add drag-and-drop support for the trace analyzer page
This commit is contained in:
parent
ba167df617
commit
95b60c2777
7 changed files with 215 additions and 36 deletions
|
@ -1,4 +1,5 @@
|
||||||
import React, { FC, useState } from "preact/compat";
|
import React, { FC, useEffect, useRef, useState } from "preact/compat";
|
||||||
|
import { MouseEvent } from "react";
|
||||||
import LineProgress from "../../Main/LineProgress/LineProgress";
|
import LineProgress from "../../Main/LineProgress/LineProgress";
|
||||||
import Trace from "../Trace";
|
import Trace from "../Trace";
|
||||||
import { ArrowDownIcon } from "../../Main/Icons";
|
import { ArrowDownIcon } from "../../Main/Icons";
|
||||||
|
@ -6,6 +7,7 @@ import "./style.scss";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { useAppState } from "../../../state/common/StateContext";
|
import { useAppState } from "../../../state/common/StateContext";
|
||||||
import useDeviceDetect from "../../../hooks/useDeviceDetect";
|
import useDeviceDetect from "../../../hooks/useDeviceDetect";
|
||||||
|
import Button from "../../Main/Button/Button";
|
||||||
|
|
||||||
interface RecursiveProps {
|
interface RecursiveProps {
|
||||||
trace: Trace;
|
trace: Trace;
|
||||||
|
@ -20,6 +22,23 @@ const NestedNav: FC<RecursiveProps> = ({ trace, totalMsec }) => {
|
||||||
const { isDarkTheme } = useAppState();
|
const { isDarkTheme } = useAppState();
|
||||||
const { isMobile } = useDeviceDetect();
|
const { isMobile } = useDeviceDetect();
|
||||||
const [openLevels, setOpenLevels] = useState({} as OpenLevels);
|
const [openLevels, setOpenLevels] = useState({} as OpenLevels);
|
||||||
|
const messageRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
const [isExpanded, setIsExpanded] = useState(false);
|
||||||
|
const [showFullMessage, setShowFullMessage] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!messageRef.current) return;
|
||||||
|
const contentElement = messageRef.current;
|
||||||
|
const child = messageRef.current.children[0];
|
||||||
|
const { height } = child.getBoundingClientRect();
|
||||||
|
setIsExpanded(height > contentElement.clientHeight);
|
||||||
|
}, [trace]);
|
||||||
|
|
||||||
|
const handleClickShowMore = (e: MouseEvent<HTMLButtonElement>) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
setShowFullMessage(prev => !prev);
|
||||||
|
};
|
||||||
|
|
||||||
const handleListClick = (level: number) => () => {
|
const handleListClick = (level: number) => () => {
|
||||||
setOpenLevels((prevState:OpenLevels) => {
|
setOpenLevels((prevState:OpenLevels) => {
|
||||||
|
@ -54,11 +73,28 @@ const NestedNav: FC<RecursiveProps> = ({ trace, totalMsec }) => {
|
||||||
<div className="vm-nested-nav-header__progress">
|
<div className="vm-nested-nav-header__progress">
|
||||||
<LineProgress value={progress}/>
|
<LineProgress value={progress}/>
|
||||||
</div>
|
</div>
|
||||||
<div className="vm-nested-nav-header__message">
|
<div
|
||||||
{trace.message}
|
className={classNames({
|
||||||
|
"vm-nested-nav-header__message": true,
|
||||||
|
"vm-nested-nav-header__message_show-full": showFullMessage,
|
||||||
|
})}
|
||||||
|
ref={messageRef}
|
||||||
|
>
|
||||||
|
<span>{trace.message}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="vm-nested-nav-header__duration">
|
<div className="vm-nested-nav-header-bottom">
|
||||||
{`duration: ${trace.duration} ms`}
|
<div className="vm-nested-nav-header-bottom__duration">
|
||||||
|
{`duration: ${trace.duration} ms`}
|
||||||
|
</div>
|
||||||
|
{(isExpanded || showFullMessage) && (
|
||||||
|
<Button
|
||||||
|
variant="text"
|
||||||
|
size="small"
|
||||||
|
onClick={handleClickShowMore}
|
||||||
|
>
|
||||||
|
{showFullMessage ? "Hide" : "Show more"}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{openLevels[trace.idValue] && <div>
|
{openLevels[trace.idValue] && <div>
|
||||||
|
|
|
@ -43,12 +43,33 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
&__message {
|
&__message {
|
||||||
|
position: relative;
|
||||||
grid-column: 2;
|
grid-column: 2;
|
||||||
|
line-height: 130%;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
display: -moz-box;
|
||||||
|
-moz-box-orient: vertical;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 3;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
line-clamp: 3;
|
||||||
|
|
||||||
|
&_show-full {
|
||||||
|
display: block;
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__duration {
|
&-bottom {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr auto;
|
||||||
|
align-items: center;
|
||||||
grid-column: 2;
|
grid-column: 2;
|
||||||
color: $color-text-secondary;
|
|
||||||
|
&__duration {
|
||||||
|
color: $color-text-secondary;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
68
app/vmui/packages/vmui/src/hooks/useDropzone.ts
Normal file
68
app/vmui/packages/vmui/src/hooks/useDropzone.ts
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
import { useState, useEffect } from "preact/compat";
|
||||||
|
|
||||||
|
const useDropzone = (node: HTMLElement | null): {dragging: boolean, files: File[]} => {
|
||||||
|
const [files, setFiles] = useState<File[]>([]);
|
||||||
|
const [dragging, setDragging] = useState(false);
|
||||||
|
|
||||||
|
const handleAddFiles = (fileList: FileList) => {
|
||||||
|
const filesArray = Array.from(fileList || []);
|
||||||
|
setFiles(filesArray);
|
||||||
|
};
|
||||||
|
|
||||||
|
// handle drag events
|
||||||
|
const handleDrag = (e: DragEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
if (e.type === "dragenter" || e.type === "dragover") {
|
||||||
|
setDragging(true);
|
||||||
|
} else if (e.type === "dragleave") {
|
||||||
|
setDragging(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// triggers when file is dropped
|
||||||
|
const handleDrop = (e: DragEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
setDragging(false);
|
||||||
|
if (e?.dataTransfer?.files && e.dataTransfer.files[0]) {
|
||||||
|
handleAddFiles(e.dataTransfer.files);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// triggers when file is pasted
|
||||||
|
const handlePaste = (e: ClipboardEvent) => {
|
||||||
|
const items = e.clipboardData?.items;
|
||||||
|
if (!items) return;
|
||||||
|
const jsonFiles = Array.from(items)
|
||||||
|
.filter(item => item.type === "application/json")
|
||||||
|
.map(item => item.getAsFile())
|
||||||
|
.filter(file => file !== null) as File[];
|
||||||
|
setFiles(jsonFiles);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
node?.addEventListener("dragenter", handleDrag);
|
||||||
|
node?.addEventListener("dragleave", handleDrag);
|
||||||
|
node?.addEventListener("dragover", handleDrag);
|
||||||
|
node?.addEventListener("drop", handleDrop);
|
||||||
|
node?.addEventListener("paste", handlePaste);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
node?.removeEventListener("dragenter", handleDrag);
|
||||||
|
node?.removeEventListener("dragleave", handleDrag);
|
||||||
|
node?.removeEventListener("dragover", handleDrag);
|
||||||
|
node?.removeEventListener("drop", handleDrop);
|
||||||
|
node?.removeEventListener("paste", handlePaste);
|
||||||
|
};
|
||||||
|
}, [node]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
files,
|
||||||
|
dragging,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useDropzone;
|
|
@ -0,0 +1,35 @@
|
||||||
|
import React, { FC } from "preact/compat";
|
||||||
|
import Button from "../../../components/Main/Button/Button";
|
||||||
|
import Tooltip from "../../../components/Main/Tooltip/Tooltip";
|
||||||
|
import { ChangeEvent } from "react";
|
||||||
|
|
||||||
|
interface TraceUploadButtonsProps {
|
||||||
|
onOpenModal: () => void;
|
||||||
|
onChange: (e: ChangeEvent<HTMLInputElement>) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TraceUploadButtons: FC<TraceUploadButtonsProps> = ({ onOpenModal, onChange }) => (
|
||||||
|
<div className="vm-trace-page-controls">
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
onClick={onOpenModal}
|
||||||
|
>
|
||||||
|
Paste JSON
|
||||||
|
</Button>
|
||||||
|
<Tooltip title="The file must contain tracing information in JSON format">
|
||||||
|
<Button>
|
||||||
|
Upload Files
|
||||||
|
<input
|
||||||
|
id="json"
|
||||||
|
type="file"
|
||||||
|
accept="application/json"
|
||||||
|
multiple
|
||||||
|
title=" "
|
||||||
|
onChange={onChange}
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default TraceUploadButtons;
|
|
@ -2,7 +2,6 @@ import React, { FC, useEffect, useMemo, useState } from "preact/compat";
|
||||||
import { ChangeEvent } from "react";
|
import { ChangeEvent } from "react";
|
||||||
import Trace from "../../components/TraceQuery/Trace";
|
import Trace from "../../components/TraceQuery/Trace";
|
||||||
import TracingsView from "../../components/TraceQuery/TracingsView";
|
import TracingsView from "../../components/TraceQuery/TracingsView";
|
||||||
import Tooltip from "../../components/Main/Tooltip/Tooltip";
|
|
||||||
import Button from "../../components/Main/Button/Button";
|
import Button from "../../components/Main/Button/Button";
|
||||||
import Alert from "../../components/Main/Alert/Alert";
|
import Alert from "../../components/Main/Alert/Alert";
|
||||||
import "./style.scss";
|
import "./style.scss";
|
||||||
|
@ -11,6 +10,8 @@ import Modal from "../../components/Main/Modal/Modal";
|
||||||
import JsonForm from "./JsonForm/JsonForm";
|
import JsonForm from "./JsonForm/JsonForm";
|
||||||
import { ErrorTypes } from "../../types";
|
import { ErrorTypes } from "../../types";
|
||||||
import { useSearchParams } from "react-router-dom";
|
import { useSearchParams } from "react-router-dom";
|
||||||
|
import useDropzone from "../../hooks/useDropzone";
|
||||||
|
import TraceUploadButtons from "./TraceUploadButtons/TraceUploadButtons";
|
||||||
|
|
||||||
const TracePage: FC = () => {
|
const TracePage: FC = () => {
|
||||||
const [openModal, setOpenModal] = useState(false);
|
const [openModal, setOpenModal] = useState(false);
|
||||||
|
@ -46,9 +47,7 @@ const TracePage: FC = () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
|
const handleReadFiles = (files: File[]) => {
|
||||||
setErrors([]);
|
|
||||||
const files = Array.from(e.target.files || []);
|
|
||||||
files.map(f => {
|
files.map(f => {
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
const filename = f?.name || "";
|
const filename = f?.name || "";
|
||||||
|
@ -58,6 +57,12 @@ const TracePage: FC = () => {
|
||||||
};
|
};
|
||||||
reader.readAsText(f);
|
reader.readAsText(f);
|
||||||
});
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setErrors([]);
|
||||||
|
const files = Array.from(e.target.files || []);
|
||||||
|
handleReadFiles(files);
|
||||||
e.target.value = "";
|
e.target.value = "";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -78,29 +83,12 @@ const TracePage: FC = () => {
|
||||||
setSearchParams({});
|
setSearchParams({});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const UploadButtons = () => (
|
|
||||||
<div className="vm-trace-page-controls">
|
const { files, dragging } = useDropzone(document.body);
|
||||||
<Button
|
|
||||||
variant="outlined"
|
useEffect(() => {
|
||||||
onClick={handleOpenModal}
|
handleReadFiles(files);
|
||||||
>
|
}, [files]);
|
||||||
Paste JSON
|
|
||||||
</Button>
|
|
||||||
<Tooltip title="The file must contain tracing information in JSON format">
|
|
||||||
<Button>
|
|
||||||
Upload Files
|
|
||||||
<input
|
|
||||||
id="json"
|
|
||||||
type="file"
|
|
||||||
accept="application/json"
|
|
||||||
multiple
|
|
||||||
title=" "
|
|
||||||
onChange={handleChange}
|
|
||||||
/>
|
|
||||||
</Button>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="vm-trace-page">
|
<div className="vm-trace-page">
|
||||||
|
@ -126,7 +114,12 @@ const TracePage: FC = () => {
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{hasTraces && <UploadButtons/>}
|
{hasTraces && (
|
||||||
|
<TraceUploadButtons
|
||||||
|
onOpenModal={handleOpenModal}
|
||||||
|
onChange={handleChange}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -158,8 +151,13 @@ const TracePage: FC = () => {
|
||||||
</a>
|
</a>
|
||||||
{"\n"}
|
{"\n"}
|
||||||
Tracing graph will be displayed after file upload.
|
Tracing graph will be displayed after file upload.
|
||||||
|
{"\n"}
|
||||||
|
Attach files by dragging & dropping, selecting or pasting them.
|
||||||
</p>
|
</p>
|
||||||
<UploadButtons/>
|
<TraceUploadButtons
|
||||||
|
onOpenModal={handleOpenModal}
|
||||||
|
onChange={handleChange}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
@ -177,6 +175,10 @@ const TracePage: FC = () => {
|
||||||
/>
|
/>
|
||||||
</Modal>
|
</Modal>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{dragging && (
|
||||||
|
<div className="vm-trace-page__dropzone"/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -75,4 +75,19 @@
|
||||||
line-height: 1.8;
|
line-height: 1.8;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__dropzone {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
box-shadow: inset $color-primary 0 0 10px;
|
||||||
|
opacity: 0.5;
|
||||||
|
z-index: 100;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,8 @@ created by v1.90.0 or newer versions. The solution is to upgrade to v1.90.0 or n
|
||||||
* FEATURE: [vmagent](https://docs.victoriametrics.com/vmagent.html): add support for [VictoriaMetrics remote write protocol](https://docs.victoriametrics.com/vmagent.html#victoriametrics-remote-write-protocol) when [sending / receiving data to / from Kafka](https://docs.victoriametrics.com/vmagent.html#kafka-integration). This protocol allows saving egress network bandwidth costs when sending data from `vmagent` to `Kafka` located in another datacenter or availability zone. See [this feature request](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1225).
|
* FEATURE: [vmagent](https://docs.victoriametrics.com/vmagent.html): add support for [VictoriaMetrics remote write protocol](https://docs.victoriametrics.com/vmagent.html#victoriametrics-remote-write-protocol) when [sending / receiving data to / from Kafka](https://docs.victoriametrics.com/vmagent.html#kafka-integration). This protocol allows saving egress network bandwidth costs when sending data from `vmagent` to `Kafka` located in another datacenter or availability zone. See [this feature request](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1225).
|
||||||
* FEATURE: [vmagent](https://docs.victoriametrics.com/vmagent.html): add `--kafka.consumer.topic.concurrency` command-line flag. It controls the number of Kafka consumer workers to use by `vmagent`. It should eliminate the need to start multiple `vmagent` instances to improve data transfer rate. See [this feature request](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1957).
|
* FEATURE: [vmagent](https://docs.victoriametrics.com/vmagent.html): add `--kafka.consumer.topic.concurrency` command-line flag. It controls the number of Kafka consumer workers to use by `vmagent`. It should eliminate the need to start multiple `vmagent` instances to improve data transfer rate. See [this feature request](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1957).
|
||||||
* FEATURE: [vmagent](https://docs.victoriametrics.com/vmagent.html): add support for [Kafka producer and consumer](https://docs.victoriametrics.com/vmagent.html#kafka-integration) on `arm64` machines. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2271).
|
* FEATURE: [vmagent](https://docs.victoriametrics.com/vmagent.html): add support for [Kafka producer and consumer](https://docs.victoriametrics.com/vmagent.html#kafka-integration) on `arm64` machines. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2271).
|
||||||
|
* FEATURE: [vmui](https://docs.victoriametrics.com/#vmui): add support for drag'n'drop and paste from clipboard in the "Trace analyzer" page. See [this pull request](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/3971).
|
||||||
|
* FEATURE: [vmui](https://docs.victoriametrics.com/#vmui): Hide messages longer than 3 lines in the trace. You can view the full message by clicking on the `show more` button. See [this pull request](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/3971).
|
||||||
* FEATURE: [vmui](https://docs.victoriametrics.com/#vmui): Add the ability to manually input date and time when selecting a time range. See [this pull request](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/3968).
|
* FEATURE: [vmui](https://docs.victoriametrics.com/#vmui): Add the ability to manually input date and time when selecting a time range. See [this pull request](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/3968).
|
||||||
|
|
||||||
* BUGFIX: prevent from slow [snapshot creating](https://docs.victoriametrics.com/#how-to-work-with-snapshots) under high data ingestion rate. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3551).
|
* BUGFIX: prevent from slow [snapshot creating](https://docs.victoriametrics.com/#how-to-work-with-snapshots) under high data ingestion rate. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3551).
|
||||||
|
|
Loading…
Reference in a new issue