diff --git a/app/vmui/packages/vmui/src/components/Home/Configurator/ExecutionControls.tsx b/app/vmui/packages/vmui/src/components/Home/Configurator/ExecutionControls.tsx
index 77e3877c1b..f04c7ce431 100644
--- a/app/vmui/packages/vmui/src/components/Home/Configurator/ExecutionControls.tsx
+++ b/app/vmui/packages/vmui/src/components/Home/Configurator/ExecutionControls.tsx
@@ -1,7 +1,5 @@
import React, {FC, useEffect, useState} from "react";
import {Box, FormControlLabel, IconButton, Switch, Tooltip} from "@material-ui/core";
-import PlayCircleOutlineIcon from "@material-ui/icons/PlayCircleOutline";
-
import EqualizerIcon from "@material-ui/icons/Equalizer";
import {useAppDispatch, useAppState} from "../../../state/common/StateContext";
import CircularProgressWithLabel from "../../common/CircularProgressWithLabel";
@@ -70,23 +68,19 @@ export const ExecutionControls: FC = () => {
};
return
-
-
- dispatch({type: "RUN_QUERY"})}>
-
-
-
-
{}
label="Auto-refresh"
/>}
{autoRefresh && <>
- {iterateDelays();}} />
-
- {iterateDelays();}}>
-
+ {iterateDelays();}} />
+
+
+ {iterateDelays();}}>
+
+
>}
;
};
\ No newline at end of file
diff --git a/app/vmui/packages/vmui/src/components/Home/Configurator/QueryConfigurator.tsx b/app/vmui/packages/vmui/src/components/Home/Configurator/QueryConfigurator.tsx
index 4200c11a80..d8789b93d6 100644
--- a/app/vmui/packages/vmui/src/components/Home/Configurator/QueryConfigurator.tsx
+++ b/app/vmui/packages/vmui/src/components/Home/Configurator/QueryConfigurator.tsx
@@ -1,4 +1,4 @@
-import React, {FC, useState} from "react";
+import React, {FC, useRef, useState} from "react";
import {
Accordion,
AccordionDetails,
@@ -7,7 +7,10 @@ import {
Grid,
IconButton,
TextField,
- Typography
+ Typography,
+ FormControlLabel,
+ Tooltip,
+ Switch,
} from "@material-ui/core";
import QueryEditor from "./QueryEditor";
import {TimeSelector} from "./TimeSelector";
@@ -15,14 +18,36 @@ import {useAppDispatch, useAppState} from "../../../state/common/StateContext";
import ExpandMoreIcon from "@material-ui/icons/ExpandMore";
import SecurityIcon from "@material-ui/icons/Security";
import {AuthDialog} from "./AuthDialog";
+import PlayCircleOutlineIcon from "@material-ui/icons/PlayCircleOutline";
+import Portal from "@material-ui/core/Portal";
+import Popover from "@material-ui/core/Popover";
+import SettingsIcon from "@material-ui/icons/Settings";
+import {saveToStorage} from "../../../utils/storage";
const QueryConfigurator: FC = () => {
const {serverUrl, query, time: {duration}} = useAppState();
const dispatch = useAppDispatch();
+ const {queryControls: {autocomplete}} = useAppState();
+ const onChangeAutocomplete = () => {
+ dispatch({type: "TOGGLE_AUTOCOMPLETE"});
+ saveToStorage("AUTOCOMPLETE", !autocomplete);
+ };
+
const [dialogOpen, setDialogOpen] = useState(false);
const [expanded, setExpanded] = useState(true);
+ const [popoverOpen, setPopoverOpen] = useState(false);
+ const refSettings = useRef(null);
+
+ const queryContainer = useRef(null);
+
+ const onSetDuration = (dur: string) => dispatch({type: "SET_DURATION", payload: dur});
+ const onRunQuery = () => dispatch({type: "RUN_QUERY"});
+ const onSetQuery = (query: string) => dispatch({type: "SET_QUERY", payload: query});
+ const onSetServer = ({target: {value}}: {target: {value: string}}) => {
+ dispatch({type: "SET_SERVER", payload: value});
+ };
return (
<>
@@ -35,31 +60,65 @@ const QueryConfigurator: FC = () => {
Query Configuration
- {!expanded && e.stopPropagation()} onFocusCapture={e => e.stopPropagation()}>
- dispatch({type: "SET_QUERY", payload: query})}/>
- }
+ e.stopPropagation()} onFocusCapture={e => e.stopPropagation()}>
+
+
+
+
-
+
dispatch({type: "SET_SERVER", payload: e.target.value})}/>
-
- setDialogOpen(true)}>
-
-
+ onChange={onSetServer}/>
+
+
+
+
+
+
+
+
+
+ setDialogOpen(true)}>
+
+
+
- dispatch({type: "SET_QUERY", payload: query})}/>
-
+
+
+ {/* for portal QueryEditor */}
+
+
+
+
+ setPopoverOpen(!popoverOpen)}>
+
+
+
+
setPopoverOpen(false)}
+ anchorEl={refSettings.current}>
+
+ {}
+ label="Autocomplete"
+ />}
+
+
+
+
-
+
{
height: "calc(100% - 18px)",
marginTop: "16px"
}}>
- dispatch({type: "SET_DURATION", payload: dur})} duration={duration}/>
+
diff --git a/app/vmui/packages/vmui/src/components/Home/Configurator/QueryEditor.tsx b/app/vmui/packages/vmui/src/components/Home/Configurator/QueryEditor.tsx
index 88d01b6d14..e7cba128d4 100644
--- a/app/vmui/packages/vmui/src/components/Home/Configurator/QueryEditor.tsx
+++ b/app/vmui/packages/vmui/src/components/Home/Configurator/QueryEditor.tsx
@@ -4,15 +4,20 @@ import {defaultKeymap} from "@codemirror/next/commands";
import React, {FC, useEffect, useRef, useState} from "react";
import { PromQLExtension } from "codemirror-promql";
import { basicSetup } from "@codemirror/next/basic-setup";
+import {isMacOs} from "../../../utils/detect-os";
export interface QueryEditorProps {
setQuery: (query: string) => void;
+ runQuery: () => void;
query: string;
server: string;
oneLiner?: boolean;
+ autocomplete: boolean
}
-const QueryEditor: FC = ({query, setQuery, server, oneLiner = false}) => {
+const QueryEditor: FC = ({
+ query, setQuery, runQuery, server, oneLiner = false, autocomplete
+}) => {
const ref = useRef(null);
@@ -33,28 +38,41 @@ const QueryEditor: FC = ({query, setQuery, server, oneLiner =
// update state on change of autocomplete server
useEffect(() => {
- const promQL = new PromQLExtension().setComplete({url: server});
+ const promQL = new PromQLExtension();
+ promQL.activateCompletion(autocomplete);
+ promQL.setComplete({url: server});
const listenerExtension = EditorView.updateListener.of(editorUpdate => {
if (editorUpdate.docChanged) {
- setQuery(
- editorUpdate.state.doc.toJSON().map(el => el.trim()).join("")
- );
+ setQuery(editorUpdate.state.doc.toJSON().map(el => el.trim()).join(""));
}
-
});
editorView?.setState(EditorState.create({
doc: query,
- extensions: [basicSetup, keymap(defaultKeymap), listenerExtension, promQL.asExtension()]
+ extensions: [
+ basicSetup,
+ keymap(defaultKeymap),
+ listenerExtension,
+ promQL.asExtension(),
+ keymap([
+ {
+ key: isMacOs() ? "Cmd-Enter" : "Ctrl-Enter",
+ run: (): boolean => {
+ runQuery();
+ return true;
+ },
+ },
+ ]),
+ ]
}));
- }, [server, editorView]);
+ }, [server, editorView, autocomplete]);
return (
<>
- {/*Class one-line-scroll and other codemirror stylings are declared in index.css*/}
-
+ {/*Class one-line-scroll and other codemirror styles are declared in index.css*/}
+
>
);
};
diff --git a/app/vmui/packages/vmui/src/components/Home/HomeLayout.tsx b/app/vmui/packages/vmui/src/components/Home/HomeLayout.tsx
index 2df5fa271e..309d764f36 100644
--- a/app/vmui/packages/vmui/src/components/Home/HomeLayout.tsx
+++ b/app/vmui/packages/vmui/src/components/Home/HomeLayout.tsx
@@ -21,7 +21,7 @@ const HomeLayout: FC = () => {
<>
-
+
VM
UI
@@ -43,7 +43,7 @@ const HomeLayout: FC = () => {
Create an issue
-
+
diff --git a/app/vmui/packages/vmui/src/components/LineChart/ChartTooltip.tsx b/app/vmui/packages/vmui/src/components/LineChart/ChartTooltip.tsx
index f288271954..62180d2901 100644
--- a/app/vmui/packages/vmui/src/components/LineChart/ChartTooltip.tsx
+++ b/app/vmui/packages/vmui/src/components/LineChart/ChartTooltip.tsx
@@ -37,7 +37,7 @@ export const ChartTooltip: React.FC = ({data, time}) => {
{data.metrics.map(({key, value}) =>
-
+
{key}:
{value}
)}
diff --git a/app/vmui/packages/vmui/src/index.css b/app/vmui/packages/vmui/src/index.css
index d68a476d0d..95fed9dbad 100644
--- a/app/vmui/packages/vmui/src/index.css
+++ b/app/vmui/packages/vmui/src/index.css
@@ -31,3 +31,9 @@ code {
.one-line-scroll .cm-wrap {
height: 24px;
}
+.cm-content, .cm-gutter { min-height: 51px; }
+
+.one-line-scroll .cm-content,
+.one-line-scroll .cm-gutter {
+ min-height: auto;
+}
diff --git a/app/vmui/packages/vmui/src/state/common/reducer.ts b/app/vmui/packages/vmui/src/state/common/reducer.ts
index d7941a4ff6..af981a6541 100644
--- a/app/vmui/packages/vmui/src/state/common/reducer.ts
+++ b/app/vmui/packages/vmui/src/state/common/reducer.ts
@@ -15,6 +15,7 @@ export interface AppState {
time: TimeState;
queryControls: {
autoRefresh: boolean;
+ autocomplete: boolean
}
}
@@ -28,6 +29,7 @@ export type Action =
| { type: "RUN_QUERY"}
| { type: "RUN_QUERY_TO_NOW"}
| { type: "TOGGLE_AUTOREFRESH"}
+ | { type: "TOGGLE_AUTOCOMPLETE"}
export const initialState: AppState = {
serverUrl: getFromStorage("PREFERRED_URL") as string || "https://", // https://demo.promlabs.com or https://play.victoriametrics.com/select/accounting/1/6a716b0f-38bc-4856-90ce-448fd713e3fe/prometheus",
@@ -38,7 +40,8 @@ export const initialState: AppState = {
period: getTimeperiodForDuration("1h")
},
queryControls: {
- autoRefresh: false
+ autoRefresh: false,
+ autocomplete: getFromStorage("AUTOCOMPLETE") as boolean || false
}
};
@@ -99,6 +102,14 @@ export function reducer(state: AppState, action: Action): AppState {
autoRefresh: !state.queryControls.autoRefresh
}
};
+ case "TOGGLE_AUTOCOMPLETE":
+ return {
+ ...state,
+ queryControls: {
+ ...state.queryControls,
+ autocomplete: !state.queryControls.autocomplete
+ }
+ };
case "RUN_QUERY":
return {
...state,
diff --git a/app/vmui/packages/vmui/src/utils/detect-os.ts b/app/vmui/packages/vmui/src/utils/detect-os.ts
new file mode 100644
index 0000000000..ee0ca40d26
--- /dev/null
+++ b/app/vmui/packages/vmui/src/utils/detect-os.ts
@@ -0,0 +1,13 @@
+const desktopOs = {
+ windows: "Windows",
+ mac: "Mac OS",
+ linux: "Linux"
+};
+
+export const getOs = () : string => {
+ return Object.values(desktopOs).find(os => navigator.userAgent.indexOf(os) >= 0) || "unknown";
+};
+
+export const isMacOs = (): boolean => {
+ return getOs() === desktopOs.mac;
+};
\ No newline at end of file
diff --git a/app/vmui/packages/vmui/src/utils/storage.ts b/app/vmui/packages/vmui/src/utils/storage.ts
index fdba0e0a40..7e7c3d1aa4 100644
--- a/app/vmui/packages/vmui/src/utils/storage.ts
+++ b/app/vmui/packages/vmui/src/utils/storage.ts
@@ -1,4 +1,9 @@
-export type StorageKeys = "PREFERRED_URL" | "LAST_QUERY" | "BASIC_AUTH_DATA" | "BEARER_AUTH_DATA" | "AUTH_TYPE";
+export type StorageKeys = "PREFERRED_URL"
+ | "LAST_QUERY"
+ | "BASIC_AUTH_DATA"
+ | "BEARER_AUTH_DATA"
+ | "AUTH_TYPE"
+ | "AUTOCOMPLETE";
export const saveToStorage = (key: StorageKeys, value: string | boolean | Record): void => {
if (value) {