mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2024-11-21 14:44:00 +00:00
vmui: enhancements multiline field editing (#4294)
* fix: change textarea for relabel page * feat: add comment for monaco theme * fix: change behavior of multiline fields * vmui: merge master
This commit is contained in:
parent
f086a5ba63
commit
15e1d16afc
18 changed files with 323 additions and 37 deletions
38
app/vmui/packages/vmui/package-lock.json
generated
38
app/vmui/packages/vmui/package-lock.json
generated
|
@ -8,6 +8,7 @@
|
||||||
"name": "vmui",
|
"name": "vmui",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@monaco-editor/react": "^4.5.1",
|
||||||
"@types/lodash.debounce": "^4.0.6",
|
"@types/lodash.debounce": "^4.0.6",
|
||||||
"@types/lodash.get": "^4.4.6",
|
"@types/lodash.get": "^4.4.6",
|
||||||
"@types/lodash.throttle": "^4.1.6",
|
"@types/lodash.throttle": "^4.1.6",
|
||||||
|
@ -26,7 +27,7 @@
|
||||||
"preact": "^10.7.1",
|
"preact": "^10.7.1",
|
||||||
"qs": "^6.10.3",
|
"qs": "^6.10.3",
|
||||||
"react-input-mask": "^2.0.4",
|
"react-input-mask": "^2.0.4",
|
||||||
"react-router-dom": "^6.3.0",
|
"react-router-dom": "^6.10.0",
|
||||||
"sass": "^1.56.0",
|
"sass": "^1.56.0",
|
||||||
"source-map-explorer": "^2.5.3",
|
"source-map-explorer": "^2.5.3",
|
||||||
"typescript": "~4.6.2",
|
"typescript": "~4.6.2",
|
||||||
|
@ -3432,6 +3433,30 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"peer": true
|
"peer": true
|
||||||
},
|
},
|
||||||
|
"node_modules/@monaco-editor/loader": {
|
||||||
|
"version": "1.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@monaco-editor/loader/-/loader-1.3.3.tgz",
|
||||||
|
"integrity": "sha512-6KKF4CTzcJiS8BJwtxtfyYt9shBiEv32ateQ9T4UVogwn4HM/uPo9iJd2Dmbkpz8CM6Y0PDUpjnZzCwC+eYo2Q==",
|
||||||
|
"dependencies": {
|
||||||
|
"state-local": "^1.0.6"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"monaco-editor": ">= 0.21.0 < 1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@monaco-editor/react": {
|
||||||
|
"version": "4.5.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@monaco-editor/react/-/react-4.5.1.tgz",
|
||||||
|
"integrity": "sha512-NNDFdP+2HojtNhCkRfE6/D6ro6pBNihaOzMbGK84lNWzRu+CfBjwzGt4jmnqimLuqp5yE5viHS2vi+QOAnD5FQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@monaco-editor/loader": "^1.3.3"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"monaco-editor": ">= 0.25.0 < 1",
|
||||||
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0",
|
||||||
|
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@nicolo-ribaudo/eslint-scope-5-internals": {
|
"node_modules/@nicolo-ribaudo/eslint-scope-5-internals": {
|
||||||
"version": "5.1.1-v1",
|
"version": "5.1.1-v1",
|
||||||
"resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz",
|
"resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz",
|
||||||
|
@ -13171,6 +13196,12 @@
|
||||||
"mkdirp": "bin/cmd.js"
|
"mkdirp": "bin/cmd.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/monaco-editor": {
|
||||||
|
"version": "0.40.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.40.0.tgz",
|
||||||
|
"integrity": "sha512-1wymccLEuFSMBvCk/jT1YDW/GuxMLYwnFwF9CDyYCxoTw2Pt379J3FUhwy9c43j51JdcxVPjwk0jm0EVDsBS2g==",
|
||||||
|
"peer": true
|
||||||
|
},
|
||||||
"node_modules/ms": {
|
"node_modules/ms": {
|
||||||
"version": "2.1.2",
|
"version": "2.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||||
|
@ -17066,6 +17097,11 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"peer": true
|
"peer": true
|
||||||
},
|
},
|
||||||
|
"node_modules/state-local": {
|
||||||
|
"version": "1.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/state-local/-/state-local-1.0.7.tgz",
|
||||||
|
"integrity": "sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w=="
|
||||||
|
},
|
||||||
"node_modules/statuses": {
|
"node_modules/statuses": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
"private": true,
|
"private": true,
|
||||||
"homepage": "./",
|
"homepage": "./",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@monaco-editor/react": "^4.5.1",
|
||||||
"@types/lodash.debounce": "^4.0.6",
|
"@types/lodash.debounce": "^4.0.6",
|
||||||
"@types/lodash.get": "^4.4.6",
|
"@types/lodash.get": "^4.4.6",
|
||||||
"@types/lodash.throttle": "^4.1.6",
|
"@types/lodash.throttle": "^4.1.6",
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
export const getExpandWithExprUrl = (server: string, query: string): string =>
|
export const getExpandWithExprUrl = (server: string, query: string): string =>
|
||||||
`${server}/expand-with-exprs?query=${query}&format=json`;
|
`${server}/expand-with-exprs?query=${encodeURIComponent(query)}&format=json`;
|
||||||
|
|
|
@ -48,6 +48,9 @@ const QueryEditor: FC<QueryEditorProps> = ({
|
||||||
const handleKeyDown = (e: KeyboardEvent) => {
|
const handleKeyDown = (e: KeyboardEvent) => {
|
||||||
const { key, ctrlKey, metaKey, shiftKey } = e;
|
const { key, ctrlKey, metaKey, shiftKey } = e;
|
||||||
|
|
||||||
|
const value = (e.target as HTMLTextAreaElement).value || "";
|
||||||
|
const isMultiline = value.split("\n").length > 1;
|
||||||
|
|
||||||
const ctrlMetaKey = ctrlKey || metaKey;
|
const ctrlMetaKey = ctrlKey || metaKey;
|
||||||
const arrowUp = key === "ArrowUp";
|
const arrowUp = key === "ArrowUp";
|
||||||
const arrowDown = key === "ArrowDown";
|
const arrowDown = key === "ArrowDown";
|
||||||
|
@ -65,12 +68,21 @@ const QueryEditor: FC<QueryEditorProps> = ({
|
||||||
onArrowDown();
|
onArrowDown();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (enter && openAutocomplete) {
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
|
||||||
// execute query
|
// execute query
|
||||||
if (enter && !shiftKey && !openAutocomplete) {
|
if (enter && !shiftKey && (!isMultiline || ctrlMetaKey) && !openAutocomplete) {
|
||||||
|
e.preventDefault();
|
||||||
onEnter();
|
onEnter();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleChangeFoundOptions = (val: string[]) => {
|
||||||
|
setOpenAutocomplete(!!val.length);
|
||||||
|
};
|
||||||
|
|
||||||
return <div
|
return <div
|
||||||
className="vm-query-editor"
|
className="vm-query-editor"
|
||||||
ref={autocompleteAnchorEl}
|
ref={autocompleteAnchorEl}
|
||||||
|
@ -93,7 +105,7 @@ const QueryEditor: FC<QueryEditorProps> = ({
|
||||||
options={options}
|
options={options}
|
||||||
anchor={autocompleteAnchorEl}
|
anchor={autocompleteAnchorEl}
|
||||||
onSelect={handleSelect}
|
onSelect={handleSelect}
|
||||||
onOpenAutocomplete={setOpenAutocomplete}
|
onFoundOptions={handleChangeFoundOptions}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{showSeriesFetchedWarning && (
|
{showSeriesFetchedWarning && (
|
||||||
|
|
|
@ -21,6 +21,7 @@ interface AutocompleteProps {
|
||||||
disabledFullScreen?: boolean
|
disabledFullScreen?: boolean
|
||||||
onSelect: (val: string) => void
|
onSelect: (val: string) => void
|
||||||
onOpenAutocomplete?: (val: boolean) => void
|
onOpenAutocomplete?: (val: boolean) => void
|
||||||
|
onFoundOptions?: (val: string[]) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const Autocomplete: FC<AutocompleteProps> = ({
|
const Autocomplete: FC<AutocompleteProps> = ({
|
||||||
|
@ -36,7 +37,8 @@ const Autocomplete: FC<AutocompleteProps> = ({
|
||||||
label,
|
label,
|
||||||
disabledFullScreen,
|
disabledFullScreen,
|
||||||
onSelect,
|
onSelect,
|
||||||
onOpenAutocomplete
|
onOpenAutocomplete,
|
||||||
|
onFoundOptions
|
||||||
}) => {
|
}) => {
|
||||||
const { isMobile } = useDeviceDetect();
|
const { isMobile } = useDeviceDetect();
|
||||||
const wrapperEl = useRef<HTMLDivElement>(null);
|
const wrapperEl = useRef<HTMLDivElement>(null);
|
||||||
|
@ -120,6 +122,10 @@ const Autocomplete: FC<AutocompleteProps> = ({
|
||||||
onOpenAutocomplete && onOpenAutocomplete(openAutocomplete);
|
onOpenAutocomplete && onOpenAutocomplete(openAutocomplete);
|
||||||
}, [openAutocomplete]);
|
}, [openAutocomplete]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
onFoundOptions && onFoundOptions(foundOptions);
|
||||||
|
}, [foundOptions]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Popper
|
<Popper
|
||||||
open={openAutocomplete}
|
open={openAutocomplete}
|
||||||
|
|
|
@ -59,9 +59,13 @@ const TextField: FC<TextFieldProps> = ({
|
||||||
|
|
||||||
const handleKeyDown = (e: KeyboardEvent<HTMLInputElement | HTMLTextAreaElement>) => {
|
const handleKeyDown = (e: KeyboardEvent<HTMLInputElement | HTMLTextAreaElement>) => {
|
||||||
onKeyDown && onKeyDown(e);
|
onKeyDown && onKeyDown(e);
|
||||||
if (e.key === "Enter" && !e.shiftKey) {
|
|
||||||
|
const { key, ctrlKey, metaKey } = e;
|
||||||
|
const isEnter = key === "Enter";
|
||||||
|
const runByEnter = type !== "textarea" ? isEnter : isEnter && (metaKey || ctrlKey);
|
||||||
|
if (runByEnter && onEnter) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
onEnter && onEnter();
|
onEnter();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -139,7 +143,8 @@ const TextField: FC<TextFieldProps> = ({
|
||||||
{helperText}
|
{helperText}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</label>;
|
</label>
|
||||||
|
;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default TextField;
|
export default TextField;
|
||||||
|
|
|
@ -128,4 +128,13 @@
|
||||||
left: auto;
|
left: auto;
|
||||||
right: $padding-small;
|
right: $padding-small;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__controls-info {
|
||||||
|
position: absolute;
|
||||||
|
bottom: $padding-small;
|
||||||
|
right: $padding-global;
|
||||||
|
color: $color-text-secondary;
|
||||||
|
font-size: $font-size-small;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
import React, { FC } from "preact/compat";
|
||||||
|
import Editor, { useMonaco } from "@monaco-editor/react";
|
||||||
|
import useMonacoTheme from "./hooks/useMonacoTheme";
|
||||||
|
import useLabelsSyntax from "./hooks/useLabelsSyntax";
|
||||||
|
import useKeybindings from "./hooks/useKeybindings";
|
||||||
|
import "./style.scss";
|
||||||
|
import classNames from "classnames";
|
||||||
|
|
||||||
|
interface MonacoEditorProps {
|
||||||
|
value: string;
|
||||||
|
label?: string;
|
||||||
|
language?: string;
|
||||||
|
disabled?: boolean;
|
||||||
|
resize?: "vertical" | "horizontal" | "both" | "none";
|
||||||
|
onChange: (val: string | undefined) => void;
|
||||||
|
onEnter?: (val: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const MonacoEditor: FC<MonacoEditorProps> = ({ value, label, language, disabled, resize = "none", onChange, onEnter }) => {
|
||||||
|
const monaco = useMonaco();
|
||||||
|
useMonacoTheme(monaco);
|
||||||
|
useLabelsSyntax(monaco);
|
||||||
|
useKeybindings(monaco, onEnter);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="vm-text-field vm-monaco-editor">
|
||||||
|
<Editor
|
||||||
|
className={classNames({
|
||||||
|
"vm-text-field__input": true,
|
||||||
|
"vm-monaco-editor__input": true,
|
||||||
|
[`vm-monaco-editor__input_resize-${resize}`]: resize,
|
||||||
|
})}
|
||||||
|
defaultLanguage={language}
|
||||||
|
value={value}
|
||||||
|
theme={"vm-theme"}
|
||||||
|
options={{
|
||||||
|
readOnly: disabled,
|
||||||
|
scrollBeyondLastLine: false,
|
||||||
|
automaticLayout: true,
|
||||||
|
lineNumbers: "off",
|
||||||
|
minimap: {
|
||||||
|
enabled: false
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
onChange={onChange}
|
||||||
|
/>
|
||||||
|
{label && <span className="vm-text-field__label">{label}</span>}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MonacoEditor;
|
|
@ -0,0 +1,22 @@
|
||||||
|
import { Monaco } from "@monaco-editor/react";
|
||||||
|
import { useEffect } from "preact/compat";
|
||||||
|
import * as monaco from "monaco-editor";
|
||||||
|
|
||||||
|
const useKeybindings = (monaco: Monaco | null, onEnter?: (val: string) => void) => {
|
||||||
|
|
||||||
|
const handleRunEnter = (e: monaco.editor.ICodeEditor) => {
|
||||||
|
onEnter && onEnter(e.getValue() || "");
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!monaco) return;
|
||||||
|
monaco.editor.addEditorAction({
|
||||||
|
id: "execute-ctrl-enter",
|
||||||
|
label: "Execute",
|
||||||
|
keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.Enter],
|
||||||
|
run: handleRunEnter
|
||||||
|
});
|
||||||
|
}, [monaco, onEnter]);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useKeybindings;
|
|
@ -0,0 +1,84 @@
|
||||||
|
import { useEffect } from "preact/compat";
|
||||||
|
import { Monaco } from "@monaco-editor/react";
|
||||||
|
import * as monaco from "monaco-editor";
|
||||||
|
|
||||||
|
const languageId = "vm-labels";
|
||||||
|
|
||||||
|
export const language = {
|
||||||
|
ignoreCase: false,
|
||||||
|
defaultToken: "",
|
||||||
|
tokenizer: {
|
||||||
|
root: [
|
||||||
|
// labels
|
||||||
|
[ /[a-z_]\w*(?=\s*(=|!=|=~|!~))/, "tag" ],
|
||||||
|
|
||||||
|
// strings
|
||||||
|
[ /"([^"\\]|\\.)*$/, "string.invalid" ],
|
||||||
|
[ /'([^'\\]|\\.)*$/, "string.invalid" ],
|
||||||
|
[ /"/, "string", "@string_double" ],
|
||||||
|
[ /'/, "string", "@string_single" ],
|
||||||
|
[ /`/, "string", "@string_backtick" ],
|
||||||
|
|
||||||
|
// delimiters and operators
|
||||||
|
[ /[{}()[]]/, "@brackets" ],
|
||||||
|
],
|
||||||
|
|
||||||
|
string_double: [
|
||||||
|
[ /[^\\"]+/, "string" ],
|
||||||
|
[ /\\./, "string.escape.invalid" ],
|
||||||
|
[ /"/, "string", "@pop" ]
|
||||||
|
],
|
||||||
|
|
||||||
|
string_single: [
|
||||||
|
[ /[^\\']+/, "string" ],
|
||||||
|
[ /\\./, "string.escape.invalid" ],
|
||||||
|
[ /'/, "string", "@pop" ]
|
||||||
|
],
|
||||||
|
|
||||||
|
string_backtick: [
|
||||||
|
[ /[^\\`$]+/, "string" ],
|
||||||
|
[ /\\./, "string.escape.invalid" ],
|
||||||
|
[ /`/, "string", "@pop" ]
|
||||||
|
],
|
||||||
|
},
|
||||||
|
} as monaco.languages.IMonarchLanguage;
|
||||||
|
|
||||||
|
export const languageConfiguration: monaco.languages.LanguageConfiguration = {
|
||||||
|
wordPattern: /(-?\d*\.\d\w*)|([^`~!#%^&*()\-=+[{\]}\\|;:'",.<>/?\s]+)/g,
|
||||||
|
comments: {
|
||||||
|
lineComment: "#",
|
||||||
|
},
|
||||||
|
brackets: [
|
||||||
|
[ "{", "}" ],
|
||||||
|
[ "[", "]" ],
|
||||||
|
[ "(", ")" ],
|
||||||
|
],
|
||||||
|
autoClosingPairs: [
|
||||||
|
{ open: "{", close: "}" },
|
||||||
|
{ open: "[", close: "]" },
|
||||||
|
{ open: "(", close: ")" },
|
||||||
|
{ open: "\"", close: "\"" },
|
||||||
|
{ open: "'", close: "'" },
|
||||||
|
],
|
||||||
|
surroundingPairs: [
|
||||||
|
{ open: "{", close: "}" },
|
||||||
|
{ open: "[", close: "]" },
|
||||||
|
{ open: "(", close: ")" },
|
||||||
|
{ open: "\"", close: "\"" },
|
||||||
|
{ open: "'", close: "'" },
|
||||||
|
{ open: "<", close: ">" },
|
||||||
|
],
|
||||||
|
folding: {}
|
||||||
|
};
|
||||||
|
|
||||||
|
const useLabelsSyntax = (monaco: Monaco | null) => {
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!monaco) return;
|
||||||
|
monaco.languages.register({ id: languageId });
|
||||||
|
monaco.languages.setMonarchTokensProvider(languageId, language);
|
||||||
|
monaco.languages.setLanguageConfiguration(languageId, languageConfiguration);
|
||||||
|
}, [monaco]);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useLabelsSyntax;
|
|
@ -0,0 +1,25 @@
|
||||||
|
import { useEffect } from "preact/compat";
|
||||||
|
import { useAppState } from "../../../state/common/StateContext";
|
||||||
|
import { Monaco } from "@monaco-editor/react";
|
||||||
|
|
||||||
|
const useMonacoTheme = (monaco: Monaco | null) => {
|
||||||
|
const { isDarkTheme } = useAppState();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!monaco) return;
|
||||||
|
monaco.editor.defineTheme("vm-theme", {
|
||||||
|
base: isDarkTheme ? "vs-dark" : "vs",
|
||||||
|
inherit: true,
|
||||||
|
rules: [],
|
||||||
|
colors: {
|
||||||
|
// #00000000 - for transparent
|
||||||
|
"editor.background": "#00000000",
|
||||||
|
"editor.lineHighlightBackground": "#00000000",
|
||||||
|
"editor.lineHighlightBorder": "#00000000"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
monaco.editor.setTheme("vm-theme");
|
||||||
|
}, [monaco, isDarkTheme]);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useMonacoTheme;
|
|
@ -0,0 +1,27 @@
|
||||||
|
@use "src/styles/variables" as *;
|
||||||
|
|
||||||
|
.vm-monaco-editor {
|
||||||
|
display: grid;
|
||||||
|
height: 100%;
|
||||||
|
min-height: inherit;
|
||||||
|
|
||||||
|
&__input {
|
||||||
|
height: 100%;
|
||||||
|
padding: $padding-small 0;
|
||||||
|
resize: none;
|
||||||
|
|
||||||
|
&_resize {
|
||||||
|
&-vertical {
|
||||||
|
resize: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-horizontal {
|
||||||
|
resize: horizontal;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-both {
|
||||||
|
resize: both;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,5 @@
|
||||||
import React, { FC, useEffect } from "preact/compat";
|
import React, { FC, useCallback, useEffect } from "preact/compat";
|
||||||
import "./style.scss";
|
import "./style.scss";
|
||||||
import TextField from "../../components/Main/TextField/TextField";
|
|
||||||
import Button from "../../components/Main/Button/Button";
|
import Button from "../../components/Main/Button/Button";
|
||||||
import { InfoIcon, PlayIcon, WikiIcon } from "../../components/Main/Icons";
|
import { InfoIcon, PlayIcon, WikiIcon } from "../../components/Main/Icons";
|
||||||
import "./style.scss";
|
import "./style.scss";
|
||||||
|
@ -9,6 +8,7 @@ import Spinner from "../../components/Main/Spinner/Spinner";
|
||||||
import Alert from "../../components/Main/Alert/Alert";
|
import Alert from "../../components/Main/Alert/Alert";
|
||||||
import { useSearchParams } from "react-router-dom";
|
import { useSearchParams } from "react-router-dom";
|
||||||
import useStateSearchParams from "../../hooks/useStateSearchParams";
|
import useStateSearchParams from "../../hooks/useStateSearchParams";
|
||||||
|
import MonacoEditor from "../../components/MonacoEditor/MonacoEditor";
|
||||||
|
|
||||||
const example = {
|
const example = {
|
||||||
config: `- if: '{bar_label=~"b.*"}'
|
config: `- if: '{bar_label=~"b.*"}'
|
||||||
|
@ -30,20 +30,20 @@ const Relabel: FC = () => {
|
||||||
const [config, setConfig] = useStateSearchParams("", "config");
|
const [config, setConfig] = useStateSearchParams("", "config");
|
||||||
const [labels, setLabels] = useStateSearchParams("", "labels");
|
const [labels, setLabels] = useStateSearchParams("", "labels");
|
||||||
|
|
||||||
const handleChangeConfig = (val: string) => {
|
const handleChangeConfig = (val?: string) => {
|
||||||
setConfig(val);
|
setConfig(val || "");
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleChangeLabels = (val: string) => {
|
const handleChangeLabels = (val?: string) => {
|
||||||
setLabels(val);
|
setLabels(val || "");
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRunQuery = () => {
|
const handleRunQuery = useCallback(() => {
|
||||||
fetchData(config, labels);
|
fetchData(config, labels);
|
||||||
searchParams.set("config", config);
|
searchParams.set("config", config);
|
||||||
searchParams.set("labels", labels);
|
searchParams.set("labels", labels);
|
||||||
setSearchParams(searchParams);
|
setSearchParams(searchParams);
|
||||||
};
|
}, [config, labels]);
|
||||||
|
|
||||||
const handleRunExample = () => {
|
const handleRunExample = () => {
|
||||||
const { config, labels } = example;
|
const { config, labels } = example;
|
||||||
|
@ -69,21 +69,24 @@ const Relabel: FC = () => {
|
||||||
<section className="vm-relabeling">
|
<section className="vm-relabeling">
|
||||||
{loading && <Spinner/>}
|
{loading && <Spinner/>}
|
||||||
<div className="vm-relabeling-header vm-block">
|
<div className="vm-relabeling-header vm-block">
|
||||||
<div className="vm-relabeling-header__configs">
|
<div className="vm-relabeling-header-configs">
|
||||||
<TextField
|
<MonacoEditor
|
||||||
type="textarea"
|
|
||||||
label="Relabel configs"
|
label="Relabel configs"
|
||||||
value={config}
|
value={config}
|
||||||
autofocus
|
language={"yaml"}
|
||||||
|
resize={"vertical"}
|
||||||
onChange={handleChangeConfig}
|
onChange={handleChangeConfig}
|
||||||
|
onEnter={handleRunQuery}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="vm-relabeling-header__labels">
|
<div className="vm-relabeling-header__labels">
|
||||||
<TextField
|
<MonacoEditor
|
||||||
type="textarea"
|
|
||||||
label="Labels"
|
label="Labels"
|
||||||
value={labels}
|
value={labels}
|
||||||
|
language={"vm-labels"}
|
||||||
|
resize={"vertical"}
|
||||||
onChange={handleChangeLabels}
|
onChange={handleChangeLabels}
|
||||||
|
onEnter={handleRunQuery}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="vm-relabeling-header-bottom">
|
<div className="vm-relabeling-header-bottom">
|
||||||
|
|
|
@ -10,23 +10,13 @@
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
&__configs {
|
&-configs {
|
||||||
textarea {
|
|
||||||
min-height: 200px;
|
min-height: 200px;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
&__labels {
|
&__labels {
|
||||||
textarea {
|
|
||||||
min-height: 60px;
|
min-height: 60px;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
textarea {
|
|
||||||
overflow: auto;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-bottom {
|
&-bottom {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
|
@ -99,6 +99,7 @@ const JsonForm: FC<JsonFormProps> = ({
|
||||||
error={error}
|
error={error}
|
||||||
autofocus
|
autofocus
|
||||||
onChange={handleChangeJson}
|
onChange={handleChangeJson}
|
||||||
|
onEnter={handleApply}
|
||||||
disabled={!editable}
|
disabled={!editable}
|
||||||
/>
|
/>
|
||||||
<div className="vm-json-form-footer">
|
<div className="vm-json-form-footer">
|
||||||
|
|
|
@ -2,15 +2,19 @@ import { useAppState } from "../../../state/common/StateContext";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { ErrorTypes } from "../../../types";
|
import { ErrorTypes } from "../../../types";
|
||||||
import { getExpandWithExprUrl } from "../../../api/expand-with-exprs";
|
import { getExpandWithExprUrl } from "../../../api/expand-with-exprs";
|
||||||
|
import { useSearchParams } from "react-router-dom";
|
||||||
|
|
||||||
export const useExpandWithExprs = () => {
|
export const useExpandWithExprs = () => {
|
||||||
const { serverUrl } = useAppState();
|
const { serverUrl } = useAppState();
|
||||||
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
|
|
||||||
const [data, setData] = useState("");
|
const [data, setData] = useState("");
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [error, setError] = useState<ErrorTypes | string>();
|
const [error, setError] = useState<ErrorTypes | string>();
|
||||||
|
|
||||||
const fetchData = async (query: string) => {
|
const fetchData = async (query: string) => {
|
||||||
|
searchParams.set("expr", query);
|
||||||
|
setSearchParams(searchParams);
|
||||||
const fetchUrl = getExpandWithExprUrl(serverUrl, query);
|
const fetchUrl = getExpandWithExprUrl(serverUrl, query);
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -1,16 +1,19 @@
|
||||||
import React, { FC } from "preact/compat";
|
import React, { FC } from "preact/compat";
|
||||||
import "./style.scss";
|
import "./style.scss";
|
||||||
import TextField from "../../components/Main/TextField/TextField";
|
import TextField from "../../components/Main/TextField/TextField";
|
||||||
import { useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import Button from "../../components/Main/Button/Button";
|
import Button from "../../components/Main/Button/Button";
|
||||||
import { PlayIcon } from "../../components/Main/Icons";
|
import { PlayIcon } from "../../components/Main/Icons";
|
||||||
import WithTemplateTutorial from "./WithTemplateTutorial/WithTemplateTutorial";
|
import WithTemplateTutorial from "./WithTemplateTutorial/WithTemplateTutorial";
|
||||||
import { useExpandWithExprs } from "./hooks/useExpandWithExprs";
|
import { useExpandWithExprs } from "./hooks/useExpandWithExprs";
|
||||||
import Spinner from "../../components/Main/Spinner/Spinner";
|
import Spinner from "../../components/Main/Spinner/Spinner";
|
||||||
|
import { useSearchParams } from "react-router-dom";
|
||||||
|
|
||||||
const WithTemplate: FC = () => {
|
const WithTemplate: FC = () => {
|
||||||
|
const [searchParams] = useSearchParams();
|
||||||
|
|
||||||
const { data, loading, error, expand } = useExpandWithExprs();
|
const { data, loading, error, expand } = useExpandWithExprs();
|
||||||
const [expr, setExpr] = useState("");
|
const [expr, setExpr] = useState(searchParams.get("expr") || "");
|
||||||
|
|
||||||
const handleChangeInput = (val: string) => {
|
const handleChangeInput = (val: string) => {
|
||||||
setExpr(val);
|
setExpr(val);
|
||||||
|
@ -20,6 +23,10 @@ const WithTemplate: FC = () => {
|
||||||
expand(expr);
|
expand(expr);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (expr) expand(expr);
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="vm-with-template">
|
<section className="vm-with-template">
|
||||||
{loading && <Spinner />}
|
{loading && <Spinner />}
|
||||||
|
@ -32,6 +39,7 @@ const WithTemplate: FC = () => {
|
||||||
value={expr}
|
value={expr}
|
||||||
error={error}
|
error={error}
|
||||||
autofocus
|
autofocus
|
||||||
|
onEnter={handleRunQuery}
|
||||||
onChange={handleChangeInput}
|
onChange={handleChangeInput}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -26,6 +26,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
textarea {
|
textarea {
|
||||||
|
font-family: $font-family-monospace;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
Loading…
Reference in a new issue