diff --git a/src/components/Telemetry/ChartControlBar.jsx b/src/components/Telemetry/ChartControlBar.jsx
new file mode 100644
index 000000000..a5f6e029c
--- /dev/null
+++ b/src/components/Telemetry/ChartControlBar.jsx
@@ -0,0 +1,230 @@
+import React, { useEffect, useState } from 'react';
+import PropTypes from 'prop-types';
+import {
+ Autocomplete,
+ Box,
+ Button,
+ Checkbox,
+ Drawer,
+ IconButton,
+ ListItem,
+ MenuItem,
+ Select,
+ TextField,
+ Typography,
+ useMediaQuery,
+} from '@mui/material';
+import { useClient } from '../../context/client-context';
+import { Close } from '@mui/icons-material';
+import TelemetryEditorWindow from './Editor';
+import { bigIntJSON } from '../../common/bigIntJSON';
+
+const query = `// Graph Rendering:
+// Graphs are rendered based on numerical outputs obtained from the specified JSON paths.
+// Only metrics that return numeric values are used to create visual representations.
+// This ensures that the graphs are meaningful and accurately reflect the system's status.
+
+// JSON Path:
+// The JSON paths listed here are extracted from the /telemetry?details_level=10 endpoint.
+// They represent specific fields within the JSON response structure returned by the API.
+// For example, paths like 'collections.collections[0].shards[0].local.segments[0].info.num_indexed_vectors'
+// indicate nested structures where specific values are accessed using indices or keys.
+// This path extracts the number of indexed vectors in the first segment of the first shard of the first collection,
+// which is crucial for understanding the indexing performance of the collection.
+
+// Reload Interval:
+// The reload_interval is set to 2 seconds by default.
+// This means that the graphs and data metrics on the page will be updated every 2 seconds,
+// providing near real-time monitoring of the system's performance.
+
+//Time Window:
+// The time window for the graphs is set to 1 min by default.
+
+
+{
+ "reload_interval": 2,
+ "paths": [
+ "requests.rest.responses['OPTIONS /telemetry'][200].avg_duration_micros",
+ "app.system.disk_size",
+ "app.system.ram_size",
+ "collections.collections[0].shards[0].local.segments[0].info.num_indexed_vectors",
+ "requests.rest.responses['GET /telemetry'][200].count"
+ ]
+}`;
+
+const ChartControlBar = ({ setChartSpecsText, timeWindow, handleTimeWindowChange, chartSpecsText }) => {
+ const matchesMdMedia = useMediaQuery('(max-width: 992px)');
+ const [reloadInterval, setReloadInterval] = useState(2);
+ const [open, setOpen] = useState(false);
+ const [jsonPaths, setJsonPaths] = useState([]);
+ const [selectedPath, setSelectedPath] = useState([]);
+ const { client: qdrantClient } = useClient();
+ const [code, setCode] = useState(query);
+
+ function extractNumericalPaths(obj, currentPath = '') {
+ let paths = [];
+
+ if (typeof obj === 'number') {
+ paths.push(`requests.${currentPath}`);
+ } else if (typeof obj === 'object' && obj !== null) {
+ for (const key in obj) {
+ if (Object.prototype.hasOwnProperty.call(obj, key)) {
+ const newPath = currentPath ? `${currentPath}.${key}` : key;
+ paths = [...paths, ...extractNumericalPaths(obj[key], newPath)];
+ }
+ }
+ }
+
+ return paths;
+ }
+
+ useEffect(() => {
+ const fetchTelemetryData = async () => {
+ try {
+ const response = await qdrantClient.api('service').telemetry({ details_level: 10 });
+ setJsonPaths(extractNumericalPaths(response.data.result.requests));
+ } catch (error) {
+ console.error('Failed to fetch telemetry data', error);
+ }
+ };
+ fetchTelemetryData();
+ }, []);
+
+ const generateChartSpecs = (newSelectedPath, newReloadInterval) => {
+ const chartSpecs = {
+ reload_interval: newReloadInterval,
+ paths: newSelectedPath,
+ refresh: true,
+ };
+ if (newReloadInterval !== reloadInterval) {
+ setReloadInterval(newReloadInterval);
+ }
+ if (newSelectedPath !== selectedPath) {
+ setSelectedPath(newSelectedPath);
+ }
+
+ return JSON.stringify(chartSpecs, null, 2);
+ };
+
+ useEffect(() => {
+ if (chartSpecsText) {
+ try {
+ const chartSpecs = bigIntJSON.parse(chartSpecsText);
+ if (chartSpecs.reload_interval !== reloadInterval) {
+ setReloadInterval(chartSpecs.reload_interval);
+ }
+ if (chartSpecs.paths !== selectedPath) {
+ setSelectedPath(chartSpecs.paths);
+ }
+ } catch (e) {
+ console.error('Failed to parse chartSpecsText', e);
+ }
+ }
+ }, [chartSpecsText]);
+
+ return (
+
+ (
+
+ )}
+ onChange={(event, value) => {
+ setChartSpecsText(generateChartSpecs(value, reloadInterval));
+ }}
+ renderOption={(props, option, { selected }) => (
+
+
+
+
+ Request: {option.split('.')[3]}
+ Status Code: {option.split('.')[4]}
+ Metric: {option.split('.')[5]}
+
+
+
+ )}
+ />
+ Reload Interval:
+
+ Time Window:
+
+
+ setOpen(false)}
+ sx={{
+ '& .MuiDrawer-paper': {
+ minWidth: matchesMdMedia ? '100vw' : '680px',
+ width: matchesMdMedia ? '100vw' : '55vw',
+ padding: '1rem',
+ pt: '6rem',
+ },
+ '& .MuiBackdrop-root.MuiModal-backdrop': {
+ opacity: '0 !important',
+ },
+ }}
+ >
+
+ Add Charts
+
+
+ setOpen(false)}>
+
+
+
+
+
+
+
+ );
+};
+ChartControlBar.propTypes = {
+ timeWindow: PropTypes.number.isRequired,
+ handleTimeWindowChange: PropTypes.func.isRequired,
+ setChartSpecsText: PropTypes.func.isRequired,
+ chartSpecsText: PropTypes.string.isRequired,
+};
+
+export default ChartControlBar;
diff --git a/src/components/Telemetry/Charts.jsx b/src/components/Telemetry/Charts.jsx
index a346d682f..c679664af 100644
--- a/src/components/Telemetry/Charts.jsx
+++ b/src/components/Telemetry/Charts.jsx
@@ -1,24 +1,13 @@
import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
-import {
- Alert,
- Box,
- Button,
- Collapse,
- Link,
- MenuItem,
- Paper,
- Select,
- Tooltip,
- Typography,
- useTheme,
-} from '@mui/material';
+import { Alert, Box, Button, Collapse, Grid, Link, Paper, Tooltip, Typography, useTheme } from '@mui/material';
import { bigIntJSON } from '../../common/bigIntJSON';
import { useClient } from '../../context/client-context';
import _ from 'lodash';
import { Chart } from 'chart.js';
import StreamingPlugin from '@robloche/chartjs-plugin-streaming';
import 'chartjs-adapter-luxon';
+import ChartControlBar from './ChartControlBar';
Chart.register(StreamingPlugin);
@@ -69,7 +58,6 @@ const Charts = ({ chartSpecsText, setChartSpecsText }) => {
const [telemetryData, setTelemetryData] = useState({});
const { client: qdrantClient } = useClient();
const [chartInstances, setChartInstances] = useState({});
- const [reloadInterval, setReloadInterval] = useState(2);
const [intervalId, setIntervalId] = useState(null);
const theme = useTheme();
const [timeWindow, setTimeWindow] = useState(60000);
@@ -170,7 +158,7 @@ const Charts = ({ chartSpecsText, setChartSpecsText }) => {
console.error('Invalid reload interval:', requestBody.reload_interval);
return;
} else if (requestBody.paths && requestBody.reload_interval && typeof requestBody.reload_interval === 'number') {
- const paths = _.union(requestBody.paths, chartLabels);
+ const paths = requestBody.refresh ? requestBody.paths : _.union(requestBody.paths, chartLabels);
const fetchTelemetryData = async () => {
try {
const response = await qdrantClient.api('service').telemetry({ details_level: 10 });
@@ -193,8 +181,6 @@ const Charts = ({ chartSpecsText, setChartSpecsText }) => {
await fetchTelemetryData();
setChartLabels(paths);
- setReloadInterval(requestBody.reload_interval);
-
if (requestBody.reload_interval) {
if (intervalId) {
clearInterval(intervalId);
@@ -232,7 +218,7 @@ const Charts = ({ chartSpecsText, setChartSpecsText }) => {
});
setChartInstances({});
chartLabels.forEach(createChart);
- }, [reloadInterval, chartLabels]);
+ }, [chartLabels]);
useEffect(() => {
Object.keys(chartInstances).forEach((path) => {
@@ -328,67 +314,61 @@ const Charts = ({ chartSpecsText, setChartSpecsText }) => {
display: 'flex',
alignItems: 'center',
p: 1,
- borderRadius: 0,
justifyContent: 'space-between',
}}
>
Telemetry
-
- Time Window:
-
-
+
{alerts.map((alert, index) => (
))}
-
- {chartLabels.map((path) => (
-
-
-
- {path.length > 50 ? (
-
- {path.substring(0, 50)}...
-
- ) : (
- {path}
- )}
-
-
-
-
-
-
-
- ))}
+
+ {chartLabels.map((path) => (
+
+
+
+
+ {path.length > 50 ? (
+
+ {path.substring(0, 50)}...
+
+ ) : (
+ {path}
+ )}
+
+
+
+
+
+
+
+
+ ))}
+
>
);
};
diff --git a/src/components/Telemetry/Editor.jsx b/src/components/Telemetry/Editor.jsx
index d96bb951b..4a7d5d248 100644
--- a/src/components/Telemetry/Editor.jsx
+++ b/src/components/Telemetry/Editor.jsx
@@ -68,13 +68,26 @@ const CodeEditorWindow = ({ onChange, code, onChangeResult }) => {
onChangeResult(data);
});
}
+
+ editor.onKeyDown(() => {
+ const isInBlockedRange =
+ editor.getSelections()?.findIndex((range) => new monaco.Range(0, 0, 20, 70).intersectRanges(range)) !== -1; // Block lines 1 to 3
+ if (isInBlockedRange) {
+ editor.updateOptions({
+ readOnly: true,
+ readOnlyMessage: { value: 'Cannot Edit the instruction' },
+ });
+ }
+ else {
+ editor.updateOptions({
+ readOnly: false,
+ });
+
+ }
+ });
});
}
- // function handleEditorWillMount(monaco) {
- // autocomplete(monaco, qdrantClient, collectionName).then((autocomplete) => {
- // autocompleteRef.current = monaco.languages.registerCompletionItemProvider('custom-language', autocomplete);
- // });
- // }
+
return (
{
+ const response = await fetch(import.meta.env.BASE_URL + './openapi.json');
+ const openapi = await response.json();
+
+ let collections = [];
+ try {
+ collections = (await qdrantClient.getCollections()).collections.map((c) => c.name);
+ } catch (e) {
+ console.error(e);
+ }
+
+ const autocomplete = new OpenapiAutocomplete(openapi, collections);
+
+ return {
+ provideCompletionItems: (model, position) => {
+ // Reuse parsed code blocks to avoid parsing the same code block multiple times
+ const selectedCodeBlock = monaco.editor.selectedCodeBlock;
+
+ if (!selectedCodeBlock) {
+ return { suggestions: [] };
+ }
+
+ const relativeLine = position.lineNumber - selectedCodeBlock.blockStartLine;
+
+ if (relativeLine < 0) {
+ // Something went wrong
+ return { suggestions: [] };
+ }
+
+ if (relativeLine === 0) {
+ // Autocomplete for request headers
+ const header = selectedCodeBlock.blockText.slice(0, position.column - 1);
+
+ let suggestions = autocomplete.completeRequestHeader(header);
+
+ suggestions = suggestions.map((s) => {
+ return {
+ label: s,
+ kind: 17,
+ insertText: s,
+ };
+ });
+
+ return { suggestions: suggestions };
+ } else {
+ // Autocomplete for request body
+ const requestLines = selectedCodeBlock.blockText.split(/\r?\n/);
+
+ const lastLine = requestLines[relativeLine].slice(0, position.column - 1);
+
+ const requestHeader = requestLines.shift();
+
+ const requestBodyLines = requestLines.slice(0, relativeLine - 1);
+
+ requestBodyLines.push(lastLine);
+
+ const requestBody = requestBodyLines.join('\n');
+
+ let suggestions = autocomplete.completeRequestBody(requestHeader, requestBody);
+
+ suggestions = suggestions.map((s) => {
+ return {
+ label: s,
+ kind: 17,
+ insertText: s,
+ };
+ });
+
+ return { suggestions: suggestions };
+ }
+ },
+ };
+};
diff --git a/src/pages/Telemetry.jsx b/src/pages/Telemetry.jsx
index a80e3e2b7..118fcc9f6 100644
--- a/src/pages/Telemetry.jsx
+++ b/src/pages/Telemetry.jsx
@@ -1,49 +1,11 @@
import React, { useState } from 'react';
import { Box, Grid } from '@mui/material';
-import { alpha, useTheme } from '@mui/material/styles';
-import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels';
-import TelemetryEditorWindow from '../components/Telemetry/Editor';
import Charts from '../components/Telemetry/Charts';
-const query = `
-// Graph Rendering:
-// Graphs are rendered based on numerical outputs obtained from the specified JSON paths.
-// Only metrics that return numeric values are used to create visual representations.
-// This ensures that the graphs are meaningful and accurately reflect the system's status.
-// JSON Path:
-// The JSON paths listed here are extracted from the /telemetry?details_level=10 endpoint.
-// They represent specific fields within the JSON response structure returned by the API.
-// For example, paths like 'collections.collections[0].shards[0].local.segments[0].info.num_indexed_vectors'
-// indicate nested structures where specific values are accessed using indices or keys.
-// This path extracts the number of indexed vectors in the first segment of the first shard of the first collection,
-// which is crucial for understanding the indexing performance of the collection.
-
-// Reload Interval:
-// The reload_interval is set to 2 seconds by default.
-// This means that the graphs and data metrics on the page will be updated every 2 seconds,
-// providing near real-time monitoring of the system's performance.
-
-//Time Window:
-// The time window for the graphs is set to 1 min by default.
-
-
-{
- "reload_interval": 2,
- "paths": [
- "requests.rest.responses['OPTIONS /telemetry'][200].avg_duration_micros",
- "app.system.disk_size",
- "app.system.ram_size",
- "collections.collections[0].shards[0].local.segments[0].info.num_indexed_vectors",
- "requests.rest.responses['GET /telemetry'][200].count"
- ]
-}`;
const defaultResult = ``;
function Telemetry() {
- const [code, setCode] = useState(query);
const [result, setResult] = useState(defaultResult);
- const theme = useTheme();
-
return (
<>
@@ -52,43 +14,11 @@ function Telemetry() {
xs={12}
item
sx={{
- display: 'flex',
- flexDirection: 'column',
+ overflow: 'auto',
height: 'calc(100vh - 64px)',
}}
>
-
-
-
-
-
-
-
-
- ⋮
-
-
-
-
-
-
+