mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2024-11-21 14:44:00 +00:00
vmui: change layout (#2054)
* fix: change query reset * feat: replace @codemirror to text field * feat: switch to Preact from React * fix: optimize mui imports * feat: move time selector to Header * checkout * fix: remove unused vars * update package-lock.json * fix: correct styles * app/vmselect/vmui: `make vmui-update` Co-authored-by: Aliaksandr Valialkin <valyala@victoriametrics.com>
This commit is contained in:
parent
c2a3911bb5
commit
fcd33fc409
25 changed files with 511 additions and 527 deletions
|
@ -1,12 +1,12 @@
|
||||||
{
|
{
|
||||||
"files": {
|
"files": {
|
||||||
"main.css": "./static/css/main.79ff1ad2.css",
|
"main.css": "./static/css/main.79ff1ad2.css",
|
||||||
"main.js": "./static/js/main.31aed9a0.js",
|
"main.js": "./static/js/main.2473acb3.js",
|
||||||
"static/js/27.cc1b69f7.chunk.js": "./static/js/27.cc1b69f7.chunk.js",
|
"static/js/27.cc1b69f7.chunk.js": "./static/js/27.cc1b69f7.chunk.js",
|
||||||
"index.html": "./index.html"
|
"index.html": "./index.html"
|
||||||
},
|
},
|
||||||
"entrypoints": [
|
"entrypoints": [
|
||||||
"static/css/main.79ff1ad2.css",
|
"static/css/main.79ff1ad2.css",
|
||||||
"static/js/main.31aed9a0.js"
|
"static/js/main.2473acb3.js"
|
||||||
]
|
]
|
||||||
}
|
}
|
|
@ -1 +1 @@
|
||||||
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="./favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="VM-UI is a metric explorer for Victoria Metrics"/><link rel="apple-touch-icon" href="./apple-touch-icon.png"/><link rel="icon" type="image/png" sizes="32x32" href="./favicon-32x32.png"><link rel="manifest" href="./manifest.json"/><title>VM UI</title><link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap"/><script defer="defer" src="./static/js/main.31aed9a0.js"></script><link href="./static/css/main.79ff1ad2.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
|
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="./favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="VM-UI is a metric explorer for Victoria Metrics"/><link rel="apple-touch-icon" href="./apple-touch-icon.png"/><link rel="icon" type="image/png" sizes="32x32" href="./favicon-32x32.png"><link rel="manifest" href="./manifest.json"/><title>VM UI</title><link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap"/><script defer="defer" src="./static/js/main.2473acb3.js"></script><link href="./static/css/main.79ff1ad2.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
|
2
app/vmselect/vmui/static/js/main.2473acb3.js
Normal file
2
app/vmselect/vmui/static/js/main.2473acb3.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -5,10 +5,11 @@ import Link from "@mui/material/Link";
|
||||||
import Toolbar from "@mui/material/Toolbar";
|
import Toolbar from "@mui/material/Toolbar";
|
||||||
import Typography from "@mui/material/Typography";
|
import Typography from "@mui/material/Typography";
|
||||||
import {ExecutionControls} from "../Home/Configurator/Time/ExecutionControls";
|
import {ExecutionControls} from "../Home/Configurator/Time/ExecutionControls";
|
||||||
import {DisplayTypeSwitch} from "../Home/Configurator/DisplayTypeSwitch";
|
|
||||||
import Logo from "../common/Logo";
|
import Logo from "../common/Logo";
|
||||||
import makeStyles from "@mui/styles/makeStyles";
|
import makeStyles from "@mui/styles/makeStyles";
|
||||||
import {setQueryStringWithoutPageReload} from "../../utils/query-string";
|
import {setQueryStringWithoutPageReload} from "../../utils/query-string";
|
||||||
|
import {TimeSelector} from "../Home/Configurator/Time/TimeSelector";
|
||||||
|
import GlobalSettings from "../Home/Configurator/Settings/GlobalSettings";
|
||||||
|
|
||||||
const useStyles = makeStyles({
|
const useStyles = makeStyles({
|
||||||
logo: {
|
logo: {
|
||||||
|
@ -22,8 +23,6 @@ const useStyles = makeStyles({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
issueLink: {
|
issueLink: {
|
||||||
position: "absolute",
|
|
||||||
bottom: "6px",
|
|
||||||
textAlign: "center",
|
textAlign: "center",
|
||||||
fontSize: "10px",
|
fontSize: "10px",
|
||||||
opacity: ".4",
|
opacity: ".4",
|
||||||
|
@ -45,7 +44,7 @@ const Header: FC = () => {
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
};
|
};
|
||||||
|
|
||||||
return <AppBar position="static">
|
return <AppBar position="static" sx={{px: 1, boxShadow: "none"}}>
|
||||||
<Toolbar>
|
<Toolbar>
|
||||||
<Box display="grid" alignItems="center" justifyContent="center">
|
<Box display="grid" alignItems="center" justifyContent="center">
|
||||||
<Box onClick={onClickLogo} className={classes.logo}>
|
<Box onClick={onClickLogo} className={classes.logo}>
|
||||||
|
@ -60,10 +59,11 @@ const Header: FC = () => {
|
||||||
create an issue
|
create an issue
|
||||||
</Link>
|
</Link>
|
||||||
</Box>
|
</Box>
|
||||||
<Box ml={4} flexGrow={1}>
|
<Box display="grid" gridTemplateColumns="repeat(3, auto)" gap={1} alignItems="center" ml="auto" mr={0}>
|
||||||
|
<TimeSelector/>
|
||||||
<ExecutionControls/>
|
<ExecutionControls/>
|
||||||
|
<GlobalSettings/>
|
||||||
</Box>
|
</Box>
|
||||||
<DisplayTypeSwitch/>
|
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
</AppBar>;
|
</AppBar>;
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,48 +2,39 @@ import React, {FC} from "preact/compat";
|
||||||
import TableChartIcon from "@mui/icons-material/TableChart";
|
import TableChartIcon from "@mui/icons-material/TableChart";
|
||||||
import ShowChartIcon from "@mui/icons-material/ShowChart";
|
import ShowChartIcon from "@mui/icons-material/ShowChart";
|
||||||
import CodeIcon from "@mui/icons-material/Code";
|
import CodeIcon from "@mui/icons-material/Code";
|
||||||
import ToggleButton from "@mui/material/ToggleButton";
|
import Tabs from "@mui/material/Tabs";
|
||||||
import ToggleButtonGroup from "@mui/material/ToggleButtonGroup";
|
import Tab from "@mui/material/Tab";
|
||||||
import {useAppDispatch, useAppState} from "../../../state/common/StateContext";
|
import {useAppDispatch, useAppState} from "../../../state/common/StateContext";
|
||||||
import withStyles from "@mui/styles/withStyles";
|
import {SyntheticEvent} from "react";
|
||||||
|
|
||||||
export type DisplayType = "table" | "chart" | "code";
|
export type DisplayType = "table" | "chart" | "code";
|
||||||
|
|
||||||
const StylizedToggleButton = withStyles({
|
const tabs = [
|
||||||
root: {
|
{value: "chart", icon: <ShowChartIcon/>, label: "Graph"},
|
||||||
display: "grid",
|
{value: "code", icon: <CodeIcon/>, label: "JSON"},
|
||||||
gridTemplateColumns: "18px auto",
|
{value: "table", icon: <TableChartIcon/>, label: "Table"}
|
||||||
gridGap: 6,
|
];
|
||||||
padding: "8px 12px",
|
|
||||||
color: "white",
|
|
||||||
lineHeight: "19px",
|
|
||||||
"&.Mui-selected": {
|
|
||||||
color: "white"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})(ToggleButton);
|
|
||||||
|
|
||||||
export const DisplayTypeSwitch: FC = () => {
|
export const DisplayTypeSwitch: FC = () => {
|
||||||
|
|
||||||
const {displayType} = useAppState();
|
const {displayType} = useAppState();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
return <ToggleButtonGroup
|
const handleChange = (event: SyntheticEvent, newValue: DisplayType) => {
|
||||||
|
dispatch({type: "SET_DISPLAY_TYPE", payload: newValue ?? displayType});
|
||||||
|
};
|
||||||
|
|
||||||
|
return <Tabs
|
||||||
value={displayType}
|
value={displayType}
|
||||||
exclusive
|
onChange={handleChange}
|
||||||
onChange={
|
sx={{minHeight: "0", marginBottom: "-1px"}}
|
||||||
(e, val) =>
|
>
|
||||||
// Toggle Button Group returns null in case of click on selected element, avoiding it
|
{tabs.map(t =>
|
||||||
dispatch({type: "SET_DISPLAY_TYPE", payload: val ?? displayType})
|
<Tab key={t.value}
|
||||||
}>
|
icon={t.icon}
|
||||||
<StylizedToggleButton value="chart" aria-label="display as chart">
|
iconPosition="start"
|
||||||
<ShowChartIcon/><span>Query Range as Chart</span>
|
label={t.label} value={t.value}
|
||||||
</StylizedToggleButton>
|
sx={{minHeight: "41px"}}
|
||||||
<StylizedToggleButton value="code" aria-label="display as code">
|
/>)}
|
||||||
<CodeIcon/><span>Instant Query as JSON</span>
|
</Tabs>;
|
||||||
</StylizedToggleButton>
|
|
||||||
<StylizedToggleButton value="table" aria-label="display as table">
|
|
||||||
<TableChartIcon/><span>Instant Query as Table</span>
|
|
||||||
</StylizedToggleButton>
|
|
||||||
</ToggleButtonGroup>;
|
|
||||||
};
|
};
|
|
@ -2,13 +2,14 @@ import SettingsIcon from "@mui/icons-material/Settings";
|
||||||
import React, {FC, useState} from "preact/compat";
|
import React, {FC, useState} from "preact/compat";
|
||||||
import AxesLimitsConfigurator from "./AxesLimitsConfigurator";
|
import AxesLimitsConfigurator from "./AxesLimitsConfigurator";
|
||||||
import Box from "@mui/material/Box";
|
import Box from "@mui/material/Box";
|
||||||
import Button from "@mui/material/Button";
|
|
||||||
import IconButton from "@mui/material/IconButton";
|
import IconButton from "@mui/material/IconButton";
|
||||||
import Paper from "@mui/material/Paper";
|
import Paper from "@mui/material/Paper";
|
||||||
import Popover from "@mui/material/Popover";
|
import Popper from "@mui/material/Popper";
|
||||||
|
import Tooltip from "@mui/material/Tooltip";
|
||||||
import Typography from "@mui/material/Typography";
|
import Typography from "@mui/material/Typography";
|
||||||
import makeStyles from "@mui/styles/makeStyles";
|
import makeStyles from "@mui/styles/makeStyles";
|
||||||
import CloseIcon from "@mui/icons-material/Close";
|
import CloseIcon from "@mui/icons-material/Close";
|
||||||
|
import ClickAwayListener from "@mui/material/ClickAwayListener";
|
||||||
|
|
||||||
const useStyles = makeStyles({
|
const useStyles = makeStyles({
|
||||||
popover: {
|
popover: {
|
||||||
|
@ -32,31 +33,29 @@ const useStyles = makeStyles({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const title = "Axes Settings";
|
||||||
|
|
||||||
const GraphSettings: FC = () => {
|
const GraphSettings: FC = () => {
|
||||||
const [anchorEl, setAnchorEl] = useState<HTMLButtonElement | null>(null);
|
const [anchorEl, setAnchorEl] = useState<HTMLButtonElement | null>(null);
|
||||||
const open = Boolean(anchorEl);
|
const open = Boolean(anchorEl);
|
||||||
|
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
|
|
||||||
return <Box display="flex" px={2}>
|
return <Box>
|
||||||
<Button variant="outlined" aria-describedby="settings-popover"
|
<Tooltip title={title}>
|
||||||
onClick={(e) => setAnchorEl(e.currentTarget)} >
|
<IconButton onClick={(e) => setAnchorEl(e.currentTarget)}>
|
||||||
<SettingsIcon sx={{fontSize: 16, marginRight: "4px"}}/>
|
<SettingsIcon/>
|
||||||
<span style={{lineHeight: 1, paddingTop: "1px"}}>{open ? "Hide" : "Show"} graph settings</span>
|
</IconButton>
|
||||||
</Button>
|
</Tooltip>
|
||||||
<Popover
|
<Popper
|
||||||
id="settings-popover"
|
|
||||||
open={open}
|
open={open}
|
||||||
anchorEl={anchorEl}
|
anchorEl={anchorEl}
|
||||||
onClose={() => setAnchorEl(null)}
|
placement="left-start"
|
||||||
anchorOrigin={{
|
modifiers={[{name: "offset", options: {offset: [0, 6]}}]}>
|
||||||
vertical: "top",
|
<ClickAwayListener onClickAway={() => setAnchorEl(null)}>
|
||||||
horizontal: anchorEl ? anchorEl.offsetWidth + 15 : 200
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Paper elevation={3} className={classes.popover}>
|
<Paper elevation={3} className={classes.popover}>
|
||||||
<div id="handle" className={classes.popoverHeader}>
|
<div id="handle" className={classes.popoverHeader}>
|
||||||
<Typography variant="body1"><b>Graph Settings</b></Typography>
|
<Typography variant="body1"><b>{title}</b></Typography>
|
||||||
<IconButton size="small" onClick={() => setAnchorEl(null)}>
|
<IconButton size="small" onClick={() => setAnchorEl(null)}>
|
||||||
<CloseIcon style={{color: "white"}}/>
|
<CloseIcon style={{color: "white"}}/>
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
@ -65,7 +64,8 @@ const GraphSettings: FC = () => {
|
||||||
<AxesLimitsConfigurator/>
|
<AxesLimitsConfigurator/>
|
||||||
</Box>
|
</Box>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Popover>
|
</ClickAwayListener>
|
||||||
|
</Popper>
|
||||||
</Box>;
|
</Box>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,21 +1,12 @@
|
||||||
import React, {FC, useEffect, useRef, useState} from "preact/compat";
|
import React, {FC, useEffect, useRef} from "preact/compat";
|
||||||
import Accordion from "@mui/material/Accordion";
|
|
||||||
import AccordionDetails from "@mui/material/AccordionDetails";
|
|
||||||
import AccordionSummary from "@mui/material/AccordionSummary";
|
|
||||||
import Box from "@mui/material/Box";
|
import Box from "@mui/material/Box";
|
||||||
import IconButton from "@mui/material/IconButton";
|
import IconButton from "@mui/material/IconButton";
|
||||||
import Typography from "@mui/material/Typography";
|
|
||||||
import Tooltip from "@mui/material/Tooltip";
|
import Tooltip from "@mui/material/Tooltip";
|
||||||
import Button from "@mui/material/Button";
|
|
||||||
import Portal from "@mui/material/Portal";
|
|
||||||
import QueryEditor from "./QueryEditor";
|
import QueryEditor from "./QueryEditor";
|
||||||
import {TimeSelector} from "../Time/TimeSelector";
|
|
||||||
import {useAppDispatch, useAppState} from "../../../../state/common/StateContext";
|
import {useAppDispatch, useAppState} from "../../../../state/common/StateContext";
|
||||||
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
|
|
||||||
import HighlightOffIcon from "@mui/icons-material/HighlightOff";
|
import HighlightOffIcon from "@mui/icons-material/HighlightOff";
|
||||||
import AddIcon from "@mui/icons-material/Add";
|
import AddCircleOutlineIcon from "@mui/icons-material/AddCircleOutline";
|
||||||
import PlayCircleOutlineIcon from "@mui/icons-material/PlayCircleOutline";
|
import PlayCircleOutlineIcon from "@mui/icons-material/PlayCircleOutline";
|
||||||
import ServerConfigurator from "./ServerConfigurator";
|
|
||||||
import AdditionalSettings from "./AdditionalSettings";
|
import AdditionalSettings from "./AdditionalSettings";
|
||||||
import {ErrorTypes} from "../../../../types";
|
import {ErrorTypes} from "../../../../types";
|
||||||
|
|
||||||
|
@ -28,15 +19,11 @@ const QueryConfigurator: FC<QueryConfiguratorProps> = ({error, queryOptions}) =>
|
||||||
|
|
||||||
const {query, queryHistory, queryControls: {autocomplete}} = useAppState();
|
const {query, queryHistory, queryControls: {autocomplete}} = useAppState();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const [expanded, setExpanded] = useState(true);
|
|
||||||
const queryContainer = useRef<HTMLDivElement>(null);
|
|
||||||
const queryRef = useRef(query);
|
const queryRef = useRef(query);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
queryRef.current = query;
|
queryRef.current = query;
|
||||||
}, [query]);
|
}, [query]);
|
||||||
|
|
||||||
const onSetDuration = (dur: string) => dispatch({type: "SET_DURATION", payload: dur});
|
|
||||||
|
|
||||||
const updateHistory = () => {
|
const updateHistory = () => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: "SET_QUERY_HISTORY", payload: query.map((q, i) => {
|
type: "SET_QUERY_HISTORY", payload: query.map((q, i) => {
|
||||||
|
@ -80,62 +67,34 @@ const QueryConfigurator: FC<QueryConfiguratorProps> = ({error, queryOptions}) =>
|
||||||
payload: {value: {values, index: newIndexHistory}, queryNumber: indexQuery}
|
payload: {value: {values, index: newIndexHistory}, queryNumber: indexQuery}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
return <Box boxShadow="rgba(99, 99, 99, 0.2) 0px 2px 8px 0px;" p={4} pb={2} m={-4} mb={2}>
|
||||||
return <>
|
<Box>
|
||||||
<Accordion expanded={expanded} onChange={() => setExpanded(prev => !prev)}>
|
|
||||||
<AccordionSummary
|
|
||||||
expandIcon={<IconButton><ExpandMoreIcon/></IconButton>}
|
|
||||||
aria-controls="panel1a-content"
|
|
||||||
id="panel1a-header"
|
|
||||||
sx={{alignItems: "flex-start", padding: "15px"}}
|
|
||||||
>
|
|
||||||
<Box mr={2}>
|
|
||||||
<Typography variant="h6" component="h2">Query Configuration</Typography>
|
|
||||||
</Box>
|
|
||||||
<Box flexGrow={1} onClick={e => e.stopPropagation()} onFocusCapture={e => e.stopPropagation()}>
|
|
||||||
<Portal disablePortal={!expanded} container={queryContainer.current}>
|
|
||||||
{query.map((q, i) =>
|
{query.map((q, i) =>
|
||||||
<Box key={i} display="grid" gridTemplateColumns="1fr auto" gap="4px" width="100%"
|
<Box key={i} display="grid" gridTemplateColumns="1fr auto auto" gap="4px" width="100%"
|
||||||
mb={i === query.length - 1 ? 0 : 2}>
|
mb={i === query.length - 1 ? 0 : 2.5}>
|
||||||
<QueryEditor query={query[i]} index={i} autocomplete={autocomplete} queryOptions={queryOptions}
|
<QueryEditor query={query[i]} index={i} autocomplete={autocomplete} queryOptions={queryOptions}
|
||||||
error={error} setHistoryIndex={setHistoryIndex} runQuery={onRunQuery} setQuery={onSetQuery}/>
|
error={error} setHistoryIndex={setHistoryIndex} runQuery={onRunQuery} setQuery={onSetQuery}/>
|
||||||
{i === 0 && <Tooltip title="Execute Query">
|
{i === 0 && <Tooltip title="Execute Query">
|
||||||
<IconButton onClick={onRunQuery}>
|
<IconButton onClick={onRunQuery} sx={{height: "49px", width: "49px"}}>
|
||||||
<PlayCircleOutlineIcon/>
|
<PlayCircleOutlineIcon/>
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Tooltip>}
|
</Tooltip>}
|
||||||
|
{query.length < 2 && <Tooltip title="Add Query">
|
||||||
|
<IconButton onClick={onAddQuery} sx={{height: "49px", width: "49px"}}>
|
||||||
|
<AddCircleOutlineIcon/>
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>}
|
||||||
{i > 0 && <Tooltip title="Remove Query">
|
{i > 0 && <Tooltip title="Remove Query">
|
||||||
<IconButton onClick={() => onRemoveQuery(i)}>
|
<IconButton onClick={() => onRemoveQuery(i)} sx={{height: "49px", width: "49px"}}>
|
||||||
<HighlightOffIcon/>
|
<HighlightOffIcon/>
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Tooltip>}
|
</Tooltip>}
|
||||||
</Box>)}
|
</Box>)}
|
||||||
</Portal>
|
|
||||||
</Box>
|
</Box>
|
||||||
</AccordionSummary>
|
<Box mt={3}>
|
||||||
<AccordionDetails>
|
|
||||||
<Box display="flex" flexWrap="wrap" gap={2}>
|
|
||||||
<Box flexGrow="2" minWidth="50%">
|
|
||||||
<ServerConfigurator error={error}/>
|
|
||||||
{/* for portal QueryEditor */}
|
|
||||||
<div ref={queryContainer}/>
|
|
||||||
{query.length < 2 && <Box display="inline-block" minHeight="40px" mt={2}>
|
|
||||||
<Button onClick={onAddQuery} variant="outlined">
|
|
||||||
<AddIcon sx={{fontSize: 16, marginRight: "4px"}}/>
|
|
||||||
<span style={{lineHeight: 1, paddingTop: "1px"}}>Query</span>
|
|
||||||
</Button>
|
|
||||||
</Box>}
|
|
||||||
</Box>
|
|
||||||
<Box flexGrow="1">
|
|
||||||
<TimeSelector setDuration={onSetDuration}/>
|
|
||||||
</Box>
|
|
||||||
<Box flexBasis="100%" pt={1}>
|
|
||||||
<AdditionalSettings/>
|
<AdditionalSettings/>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>;
|
||||||
</AccordionDetails>
|
|
||||||
</Accordion>
|
|
||||||
</>;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default QueryConfigurator;
|
export default QueryConfigurator;
|
|
@ -1,51 +0,0 @@
|
||||||
import React, {FC, useEffect, useState} from "preact/compat";
|
|
||||||
import Box from "@mui/material/Box";
|
|
||||||
import TextField from "@mui/material/TextField";
|
|
||||||
import Tooltip from "@mui/material/Tooltip";
|
|
||||||
import IconButton from "@mui/material/IconButton";
|
|
||||||
import SecurityIcon from "@mui/icons-material/Security";
|
|
||||||
import {useAppDispatch, useAppState} from "../../../../state/common/StateContext";
|
|
||||||
import {AuthDialog} from "../Auth/AuthDialog";
|
|
||||||
import {ErrorTypes} from "../../../../types";
|
|
||||||
import {getAppModeEnable, getAppModeParams} from "../../../../utils/app-mode";
|
|
||||||
|
|
||||||
export interface ServerConfiguratorProps {
|
|
||||||
error?: ErrorTypes | string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ServerConfigurator: FC<ServerConfiguratorProps> = ({error}) => {
|
|
||||||
|
|
||||||
const appModeEnable = getAppModeEnable();
|
|
||||||
const {serverURL: appServerUrl} = getAppModeParams();
|
|
||||||
|
|
||||||
const {serverUrl} = useAppState();
|
|
||||||
const dispatch = useAppDispatch();
|
|
||||||
|
|
||||||
const onSetServer = ({target: {value}}: {target: {value: string}}) => {
|
|
||||||
dispatch({type: "SET_SERVER", payload: value});
|
|
||||||
};
|
|
||||||
const [dialogOpen, setDialogOpen] = useState(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (appModeEnable) dispatch({type: "SET_SERVER", payload: appServerUrl});
|
|
||||||
}, [appServerUrl]);
|
|
||||||
|
|
||||||
return <>
|
|
||||||
<Box display="grid" gridTemplateColumns="1fr auto" gap="4px" alignItems="center" width="100%" mb={2} minHeight={50}>
|
|
||||||
<TextField variant="outlined" fullWidth label="Server URL" value={serverUrl || ""} disabled={appModeEnable}
|
|
||||||
error={error === ErrorTypes.validServer || error === ErrorTypes.emptyServer}
|
|
||||||
inputProps={{style: {fontFamily: "Monospace"}}}
|
|
||||||
onChange={onSetServer}/>
|
|
||||||
<Box>
|
|
||||||
<Tooltip title="Request Auth Settings">
|
|
||||||
<IconButton onClick={() => setDialogOpen(true)}>
|
|
||||||
<SecurityIcon/>
|
|
||||||
</IconButton>
|
|
||||||
</Tooltip>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
<AuthDialog open={dialogOpen} onClose={() => setDialogOpen(false)}/>
|
|
||||||
</>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ServerConfigurator;
|
|
|
@ -0,0 +1,82 @@
|
||||||
|
import React, {FC, useState} from "preact/compat";
|
||||||
|
import Tooltip from "@mui/material/Tooltip";
|
||||||
|
import SettingsIcon from "@mui/icons-material/Settings";
|
||||||
|
import Button from "@mui/material/Button";
|
||||||
|
import Box from "@mui/material/Box";
|
||||||
|
import Modal from "@mui/material/Modal";
|
||||||
|
import ServerConfigurator from "./ServerConfigurator";
|
||||||
|
import Typography from "@mui/material/Typography";
|
||||||
|
import CloseIcon from "@mui/icons-material/Close";
|
||||||
|
import IconButton from "@mui/material/IconButton";
|
||||||
|
import {useAppDispatch, useAppState} from "../../../../state/common/StateContext";
|
||||||
|
import {getAppModeEnable} from "../../../../utils/app-mode";
|
||||||
|
|
||||||
|
const modalStyle = {
|
||||||
|
position: "absolute" as const,
|
||||||
|
top: "50%",
|
||||||
|
left: "50%",
|
||||||
|
transform: "translate(-50%, -50%)",
|
||||||
|
bgcolor: "background.paper",
|
||||||
|
p: 3,
|
||||||
|
borderRadius: "4px",
|
||||||
|
width: "80%",
|
||||||
|
maxWidth: "800px"
|
||||||
|
};
|
||||||
|
|
||||||
|
const title = "Setting Server URL";
|
||||||
|
|
||||||
|
const GlobalSettings: FC = () => {
|
||||||
|
|
||||||
|
const appModeEnable = getAppModeEnable();
|
||||||
|
const {serverUrl} = useAppState();
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const [changedServerUrl, setChangedServerUrl] = useState(serverUrl);
|
||||||
|
|
||||||
|
const setServer = () => {
|
||||||
|
if (!appModeEnable) dispatch({type: "SET_SERVER", payload: changedServerUrl});
|
||||||
|
handleClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
const handleOpen = () => setOpen(true);
|
||||||
|
const handleClose = () => setOpen(false);
|
||||||
|
|
||||||
|
return <>
|
||||||
|
<Tooltip title={title}>
|
||||||
|
<Button variant="contained" color="primary"
|
||||||
|
sx={{
|
||||||
|
color: "white",
|
||||||
|
border: "1px solid rgba(0, 0, 0, 0.2)",
|
||||||
|
minWidth: "34px",
|
||||||
|
padding: "6px 8px",
|
||||||
|
boxShadow: "none",
|
||||||
|
}}
|
||||||
|
startIcon={<SettingsIcon style={{marginRight: "-8px", marginLeft: "4px"}}/>}
|
||||||
|
onClick={handleOpen}>
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
<Modal open={open} onClose={handleClose}>
|
||||||
|
<Box sx={modalStyle}>
|
||||||
|
<Box display="grid" gridTemplateColumns="1fr auto" alignItems="center" mb={4}>
|
||||||
|
<Typography id="modal-modal-title" variant="h6" component="h2">
|
||||||
|
{title}
|
||||||
|
</Typography>
|
||||||
|
<IconButton size="small" onClick={handleClose}>
|
||||||
|
<CloseIcon/>
|
||||||
|
</IconButton>
|
||||||
|
</Box>
|
||||||
|
<ServerConfigurator setServer={setChangedServerUrl}/>
|
||||||
|
<Box display="grid" gridTemplateColumns="auto auto" gap={1} justifyContent="end" mt={4}>
|
||||||
|
<Button variant="outlined" onClick={handleClose}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button variant="contained" onClick={setServer}>
|
||||||
|
apply
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Modal>
|
||||||
|
</>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default GlobalSettings;
|
|
@ -0,0 +1,41 @@
|
||||||
|
import React, {FC, useEffect, useState} from "preact/compat";
|
||||||
|
import TextField from "@mui/material/TextField";
|
||||||
|
import {useAppDispatch, useAppState} from "../../../../state/common/StateContext";
|
||||||
|
import {ErrorTypes} from "../../../../types";
|
||||||
|
import {getAppModeEnable, getAppModeParams} from "../../../../utils/app-mode";
|
||||||
|
import {ChangeEvent} from "react";
|
||||||
|
|
||||||
|
export interface ServerConfiguratorProps {
|
||||||
|
error?: ErrorTypes | string;
|
||||||
|
setServer: (url: string) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const ServerConfigurator: FC<ServerConfiguratorProps> = ({error, setServer}) => {
|
||||||
|
|
||||||
|
const appModeEnable = getAppModeEnable();
|
||||||
|
const {serverURL: appServerUrl} = getAppModeParams();
|
||||||
|
|
||||||
|
const {serverUrl} = useAppState();
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const [changedServerUrl, setChangedServerUrl] = useState(serverUrl);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (appModeEnable) {
|
||||||
|
dispatch({type: "SET_SERVER", payload: appServerUrl});
|
||||||
|
setChangedServerUrl(appServerUrl);
|
||||||
|
}
|
||||||
|
}, [appServerUrl]);
|
||||||
|
|
||||||
|
const onChangeServer = (e: ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => {
|
||||||
|
const value = e.target.value || "";
|
||||||
|
setChangedServerUrl(value);
|
||||||
|
setServer(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
return <TextField variant="outlined" fullWidth label="Server URL" value={changedServerUrl || ""} disabled={appModeEnable}
|
||||||
|
error={error === ErrorTypes.validServer || error === ErrorTypes.emptyServer}
|
||||||
|
inputProps={{style: {fontFamily: "Monospace"}}}
|
||||||
|
onChange={onChangeServer}/>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ServerConfigurator;
|
|
@ -1,92 +1,100 @@
|
||||||
import React, {FC, useEffect, useState} from "preact/compat";
|
import React, {FC, useEffect, useState} from "preact/compat";
|
||||||
import Box from "@mui/material/Box";
|
|
||||||
import FormControlLabel from "@mui/material/FormControlLabel";
|
|
||||||
import IconButton from "@mui/material/IconButton";
|
|
||||||
import Tooltip from "@mui/material/Tooltip";
|
import Tooltip from "@mui/material/Tooltip";
|
||||||
import EqualizerIcon from "@mui/icons-material/Equalizer";
|
|
||||||
import {useAppDispatch, useAppState} from "../../../../state/common/StateContext";
|
import {useAppDispatch, useAppState} from "../../../../state/common/StateContext";
|
||||||
import CircularProgressWithLabel from "../../../common/CircularProgressWithLabel";
|
import Button from "@mui/material/Button";
|
||||||
import makeStyles from "@mui/styles/makeStyles";
|
import Popper from "@mui/material/Popper";
|
||||||
import BasicSwitch from "../../../../theme/switch";
|
import Paper from "@mui/material/Paper";
|
||||||
|
import ClickAwayListener from "@mui/material/ClickAwayListener";
|
||||||
|
import AutorenewIcon from "@mui/icons-material/Autorenew";
|
||||||
|
import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown";
|
||||||
|
import List from "@mui/material/List";
|
||||||
|
import ListItem from "@mui/material/ListItem";
|
||||||
|
import ListItemText from "@mui/material/ListItemText";
|
||||||
|
|
||||||
const useStyles = makeStyles({
|
interface AutoRefreshOption {
|
||||||
colorizing: {
|
seconds: number
|
||||||
color: "white"
|
title: string
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
const delayOptions: AutoRefreshOption[] = [
|
||||||
|
{seconds: 0, title: "Off"},
|
||||||
|
{seconds: 1, title: "1s"},
|
||||||
|
{seconds: 2, title: "2s"},
|
||||||
|
{seconds: 5, title: "5s"},
|
||||||
|
{seconds: 10, title: "10s"},
|
||||||
|
{seconds: 30, title: "30s"},
|
||||||
|
{seconds: 60, title: "1m"},
|
||||||
|
{seconds: 300, title: "5m"},
|
||||||
|
{seconds: 900, title: "15m"},
|
||||||
|
{seconds: 1800, title: "30m"},
|
||||||
|
{seconds: 3600, title: "1h"},
|
||||||
|
{seconds: 7200, title: "2h"}
|
||||||
|
];
|
||||||
|
|
||||||
export const ExecutionControls: FC = () => {
|
export const ExecutionControls: FC = () => {
|
||||||
const classes = useStyles();
|
|
||||||
|
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const {queryControls: {autoRefresh}} = useAppState();
|
const {queryControls: {autoRefresh}} = useAppState();
|
||||||
|
|
||||||
const [delay, setDelay] = useState<(1|2|5)>(5);
|
const [selectedDelay, setSelectedDelay] = useState<AutoRefreshOption>(delayOptions[0]);
|
||||||
const [lastUpdate, setLastUpdate] = useState<number|undefined>();
|
|
||||||
const [progress, setProgress] = React.useState(100);
|
|
||||||
|
|
||||||
const handleChange = () => {
|
const handleChange = (d: AutoRefreshOption) => {
|
||||||
|
if ((autoRefresh && !d.seconds) || (!autoRefresh && d.seconds)) {
|
||||||
dispatch({type: "TOGGLE_AUTOREFRESH"});
|
dispatch({type: "TOGGLE_AUTOREFRESH"});
|
||||||
|
}
|
||||||
|
setSelectedDelay(d);
|
||||||
|
setAnchorEl(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
const delay = selectedDelay.seconds;
|
||||||
let timer: number;
|
let timer: number;
|
||||||
if (autoRefresh) {
|
if (autoRefresh) {
|
||||||
setLastUpdate(new Date().valueOf());
|
|
||||||
timer = setInterval(() => {
|
timer = setInterval(() => {
|
||||||
setLastUpdate(new Date().valueOf());
|
|
||||||
dispatch({type: "RUN_QUERY_TO_NOW"});
|
dispatch({type: "RUN_QUERY_TO_NOW"});
|
||||||
}, delay * 1000) as unknown as number;
|
}, delay * 1000) as unknown as number;
|
||||||
|
} else {
|
||||||
|
setSelectedDelay(delayOptions[0]);
|
||||||
}
|
}
|
||||||
return () => {
|
return () => {
|
||||||
timer && clearInterval(timer);
|
timer && clearInterval(timer);
|
||||||
};
|
};
|
||||||
}, [delay, autoRefresh]);
|
}, [selectedDelay, autoRefresh]);
|
||||||
|
|
||||||
useEffect(() => {
|
const [anchorEl, setAnchorEl] = useState<HTMLButtonElement | null>(null);
|
||||||
const timer = setInterval(() => {
|
const open = Boolean(anchorEl);
|
||||||
if (autoRefresh && lastUpdate) {
|
|
||||||
const delta = (new Date().valueOf() - lastUpdate) / 1000; //s
|
|
||||||
const nextValue = Math.floor(delta / delay * 100);
|
|
||||||
setProgress(nextValue);
|
|
||||||
}
|
|
||||||
}, 16);
|
|
||||||
return () => {
|
|
||||||
clearInterval(timer);
|
|
||||||
};
|
|
||||||
}, [autoRefresh, lastUpdate, delay]);
|
|
||||||
|
|
||||||
const iterateDelays = () => {
|
return <>
|
||||||
setDelay(prev => {
|
<Tooltip title="Auto-refresh control">
|
||||||
switch (prev) {
|
<Button variant="contained" color="primary"
|
||||||
case 1:
|
sx={{
|
||||||
return 2;
|
minWidth: "110px",
|
||||||
case 2:
|
color: "white",
|
||||||
return 5;
|
border: "1px solid rgba(0, 0, 0, 0.2)",
|
||||||
case 5:
|
justifyContent: "space-between",
|
||||||
return 1;
|
boxShadow: "none",
|
||||||
default:
|
}}
|
||||||
return 5;
|
startIcon={<AutorenewIcon/>}
|
||||||
}
|
endIcon={<KeyboardArrowDownIcon sx={{transform: open ? "rotate(180deg)" : "none"}}/>}
|
||||||
});
|
onClick={(e) => setAnchorEl(e.currentTarget)}
|
||||||
};
|
>
|
||||||
|
{selectedDelay.title}
|
||||||
return <Box display="flex" alignItems="center">
|
</Button>
|
||||||
{<FormControlLabel
|
|
||||||
control={<BasicSwitch className={classes.colorizing} checked={autoRefresh} onChange={handleChange} />}
|
|
||||||
label="Auto-refresh"
|
|
||||||
/>}
|
|
||||||
|
|
||||||
{autoRefresh && <>
|
|
||||||
<CircularProgressWithLabel className={classes.colorizing} label={delay} value={progress}
|
|
||||||
onClick={() => {iterateDelays();}} />
|
|
||||||
<Tooltip title="Change delay refresh">
|
|
||||||
<Box ml={1}>
|
|
||||||
<IconButton onClick={() => {iterateDelays();}}>
|
|
||||||
<EqualizerIcon style={{color: "white"}} />
|
|
||||||
</IconButton>
|
|
||||||
</Box>
|
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</>}
|
<Popper
|
||||||
</Box>;
|
open={open}
|
||||||
|
anchorEl={anchorEl}
|
||||||
|
placement="bottom-end"
|
||||||
|
modifiers={[{name: "offset", options: {offset: [0, 6]}}]}>
|
||||||
|
<ClickAwayListener onClickAway={() => setAnchorEl(null)}>
|
||||||
|
<Paper elevation={3}>
|
||||||
|
<List style={{minWidth: "110px",maxHeight: "208px", overflow: "auto", padding: "20px 0"}}>
|
||||||
|
{delayOptions.map(d =>
|
||||||
|
<ListItem key={d.seconds} button onClick={() => handleChange(d)}>
|
||||||
|
<ListItemText primary={d.title}/>
|
||||||
|
</ListItem>)}
|
||||||
|
</List>
|
||||||
|
</Paper>
|
||||||
|
</ClickAwayListener></Popper>
|
||||||
|
</>;
|
||||||
};
|
};
|
|
@ -1,31 +0,0 @@
|
||||||
import React, {FC} from "preact/compat";
|
|
||||||
import Paper from "@mui/material/Paper";
|
|
||||||
import Table from "@mui/material/Table";
|
|
||||||
import TableBody from "@mui/material/TableBody";
|
|
||||||
import TableCell from "@mui/material/TableCell";
|
|
||||||
import TableContainer from "@mui/material/TableContainer";
|
|
||||||
import TableHead from "@mui/material/TableHead";
|
|
||||||
import TableRow from "@mui/material/TableRow";
|
|
||||||
import {supportedDurations} from "../../../../utils/time";
|
|
||||||
|
|
||||||
export const TimeDurationPopover: FC = () => {
|
|
||||||
|
|
||||||
return <TableContainer component={Paper}>
|
|
||||||
<Table aria-label="simple table" size="small">
|
|
||||||
<TableHead>
|
|
||||||
<TableRow>
|
|
||||||
<TableCell>Long</TableCell>
|
|
||||||
<TableCell>Short</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
</TableHead>
|
|
||||||
<TableBody>
|
|
||||||
{supportedDurations.map((row, index) => (
|
|
||||||
<TableRow key={index}>
|
|
||||||
<TableCell component="th" scope="row">{row.long}</TableCell>
|
|
||||||
<TableCell>{row.short}</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
))}
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
</TableContainer>;
|
|
||||||
};
|
|
|
@ -1,103 +1,47 @@
|
||||||
import React, {FC, useEffect, useState} from "preact/compat";
|
import React, {FC} from "preact/compat";
|
||||||
import {ChangeEvent, MouseEvent, KeyboardEvent} from "react";
|
import List from "@mui/material/List";
|
||||||
import Box from "@mui/material/Box";
|
import ListItem from "@mui/material/ListItem";
|
||||||
import Popover from "@mui/material/Popover";
|
import ListItemText from "@mui/material/ListItemText";
|
||||||
import TextField from "@mui/material/TextField";
|
import dayjs from "dayjs";
|
||||||
import Typography from "@mui/material/Typography";
|
|
||||||
import {checkDurationLimit} from "../../../../utils/time";
|
|
||||||
import {TimeDurationPopover} from "./TimeDurationPopover";
|
|
||||||
import {InlineBtn} from "../../../common/InlineBtn";
|
|
||||||
import {useAppState} from "../../../../state/common/StateContext";
|
|
||||||
|
|
||||||
interface TimeDurationSelector {
|
interface TimeDurationSelector {
|
||||||
setDuration: (str: string) => void;
|
setDuration: (str: string, from: Date) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface DurationOption {
|
||||||
|
duration: string,
|
||||||
|
title?: string,
|
||||||
|
from?: () => Date,
|
||||||
|
}
|
||||||
|
|
||||||
|
const durationOptions: DurationOption[] = [
|
||||||
|
{duration: "5m", title: "Last 5 minutes"},
|
||||||
|
{duration: "15m", title: "Last 15 minutes"},
|
||||||
|
{duration: "30m", title: "Last 30 minutes"},
|
||||||
|
{duration: "1h", title: "Last 1 hour"},
|
||||||
|
{duration: "3h", title: "Last 3 hours"},
|
||||||
|
{duration: "6h", title: "Last 6 hours"},
|
||||||
|
{duration: "12h", title: "Last 12 hours"},
|
||||||
|
{duration: "24h", title: "Last 24 hours"},
|
||||||
|
{duration: "2d", title: "Last 2 days"},
|
||||||
|
{duration: "7d", title: "Last 7 days"},
|
||||||
|
{duration: "30d", title: "Last 30 days"},
|
||||||
|
{duration: "90d", title: "Last 90 days"},
|
||||||
|
{duration: "6m", title: "Last 6 months"},
|
||||||
|
{duration: "1y", title: "Last 1 year"},
|
||||||
|
{duration: "1d", from: () => dayjs().subtract(1, "day").endOf("day").toDate(), title: "Yesterday"},
|
||||||
|
{duration: "1d", from: () => dayjs().endOf("day").toDate(), title: "Today"},
|
||||||
|
];
|
||||||
|
|
||||||
const TimeDurationSelector: FC<TimeDurationSelector> = ({setDuration}) => {
|
const TimeDurationSelector: FC<TimeDurationSelector> = ({setDuration}) => {
|
||||||
const {time: {duration}} = useAppState();
|
// setDurationString("5m"))
|
||||||
|
|
||||||
const [anchorEl, setAnchorEl] = React.useState<Element | null>(null);
|
return <List style={{maxHeight: "168px", overflow: "auto", paddingRight: "15px"}}>
|
||||||
const [durationString, setDurationString] = useState<string>(duration);
|
{durationOptions.map(d =>
|
||||||
const [durationStringFocused, setFocused] = useState(false);
|
<ListItem key={d.duration} button onClick={() => setDuration(d.duration, d.from ? d.from() : new Date())}>
|
||||||
const open = Boolean(anchorEl);
|
<ListItemText primary={d.title || d.duration}/>
|
||||||
|
</ListItem>)}
|
||||||
const handleDurationChange = (event: ChangeEvent<HTMLInputElement>) => {
|
</List>;
|
||||||
setDurationString(event.target.value);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handlePopoverOpen = (event: MouseEvent<HTMLSpanElement>) => {
|
|
||||||
setAnchorEl(event.currentTarget);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handlePopoverClose = () => {
|
|
||||||
setAnchorEl(null);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onKeyUp = (event: KeyboardEvent<HTMLInputElement>) => {
|
|
||||||
if (event.key !== "Enter") return;
|
|
||||||
const target = event.target as HTMLInputElement;
|
|
||||||
target.blur();
|
|
||||||
setDurationString(target.value);
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setDurationString(duration);
|
|
||||||
}, [duration]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!durationStringFocused) {
|
|
||||||
const value = checkDurationLimit(durationString);
|
|
||||||
setDurationString(value);
|
|
||||||
setDuration(value);
|
|
||||||
}
|
|
||||||
}, [durationString, durationStringFocused]);
|
|
||||||
|
|
||||||
return <>
|
|
||||||
<Box>
|
|
||||||
<TextField label="Duration" value={durationString} onChange={handleDurationChange}
|
|
||||||
variant="standard"
|
|
||||||
fullWidth={true}
|
|
||||||
onKeyUp={onKeyUp}
|
|
||||||
onBlur={() => {
|
|
||||||
setFocused(false);
|
|
||||||
}}
|
|
||||||
onFocus={() => {
|
|
||||||
setFocused(true);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
<Box mt={2}>
|
|
||||||
<Typography variant="body2">
|
|
||||||
<span aria-owns={open ? "mouse-over-popover" : undefined}
|
|
||||||
aria-haspopup="true"
|
|
||||||
style={{cursor: "pointer"}}
|
|
||||||
onMouseEnter={handlePopoverOpen}
|
|
||||||
onMouseLeave={handlePopoverClose}>
|
|
||||||
Possible options:
|
|
||||||
</span>
|
|
||||||
<Popover
|
|
||||||
open={open}
|
|
||||||
anchorEl={anchorEl}
|
|
||||||
anchorOrigin={{
|
|
||||||
vertical: "bottom",
|
|
||||||
horizontal: "left",
|
|
||||||
}}
|
|
||||||
transformOrigin={{
|
|
||||||
vertical: "top",
|
|
||||||
horizontal: "left",
|
|
||||||
}}
|
|
||||||
style={{pointerEvents: "none"}} // important
|
|
||||||
onClose={handlePopoverClose}
|
|
||||||
disableRestoreFocus
|
|
||||||
>
|
|
||||||
<TimeDurationPopover/>
|
|
||||||
</Popover>
|
|
||||||
<InlineBtn handler={() => setDurationString("5m")} text="5m"/>,
|
|
||||||
<InlineBtn handler={() => setDurationString("1h")} text="1h"/>,
|
|
||||||
<InlineBtn handler={() => setDurationString("1h 30m")} text="1h 30m"/>
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
</>;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default TimeDurationSelector;
|
export default TimeDurationSelector;
|
|
@ -1,40 +1,32 @@
|
||||||
import React, {FC, useEffect, useState} from "preact/compat";
|
import React, {FC, useEffect, useState, useMemo} from "preact/compat";
|
||||||
import Box from "@mui/material/Box";
|
|
||||||
import TextField from "@mui/material/TextField";
|
|
||||||
import Typography from "@mui/material/Typography";
|
|
||||||
import DateTimePicker from "@mui/lab/DateTimePicker";
|
|
||||||
import {useAppDispatch, useAppState} from "../../../../state/common/StateContext";
|
import {useAppDispatch, useAppState} from "../../../../state/common/StateContext";
|
||||||
import {dateFromSeconds, formatDateForNativeInput} from "../../../../utils/time";
|
import {dateFromSeconds, formatDateForNativeInput} from "../../../../utils/time";
|
||||||
import {InlineBtn} from "../../../common/InlineBtn";
|
|
||||||
import makeStyles from "@mui/styles/makeStyles";
|
import makeStyles from "@mui/styles/makeStyles";
|
||||||
import TimeDurationSelector from "./TimeDurationSelector";
|
import TimeDurationSelector from "./TimeDurationSelector";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
|
import QueryBuilderIcon from "@mui/icons-material/QueryBuilder";
|
||||||
|
import Box from "@mui/material/Box";
|
||||||
|
import TextField from "@mui/material/TextField";
|
||||||
|
import DateTimePicker from "@mui/lab/DateTimePicker";
|
||||||
|
import Button from "@mui/material/Button";
|
||||||
|
import Popper from "@mui/material/Popper";
|
||||||
|
import Paper from "@mui/material/Paper";
|
||||||
|
import Divider from "@mui/material/Divider";
|
||||||
|
import ClickAwayListener from "@mui/material/ClickAwayListener";
|
||||||
|
import Tooltip from "@mui/material/Tooltip";
|
||||||
|
|
||||||
interface TimeSelectorProps {
|
const formatDate = "YYYY-MM-DD HH:mm:ss";
|
||||||
setDuration: (str: string) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const useStyles = makeStyles({
|
const useStyles = makeStyles({
|
||||||
container: {
|
container: {
|
||||||
display: "grid",
|
display: "grid",
|
||||||
gridTemplateColumns: "200px 1fr",
|
gridTemplateColumns: "200px auto 200px",
|
||||||
gridGap: "20px",
|
gridGap: "10px",
|
||||||
height: "100%",
|
|
||||||
padding: "20px",
|
padding: "20px",
|
||||||
borderRadius: "4px",
|
|
||||||
borderColor: "#b9b9b9",
|
|
||||||
borderStyle: "solid",
|
|
||||||
borderWidth: "1px"
|
|
||||||
},
|
},
|
||||||
timeControls: {
|
timeControls: {
|
||||||
display: "grid",
|
display: "grid",
|
||||||
gridTemplateColumns: "1fr",
|
gridTemplateRows: "auto 1fr auto",
|
||||||
gridTemplateRows: "auto 1fr",
|
|
||||||
gridGap: "16px 0",
|
|
||||||
},
|
|
||||||
datePickers: {
|
|
||||||
display: "grid",
|
|
||||||
gridTemplateColumns: "repeat(auto-fit, 200px)",
|
|
||||||
gridGap: "16px 0",
|
gridGap: "16px 0",
|
||||||
},
|
},
|
||||||
datePickerItem: {
|
datePickerItem: {
|
||||||
|
@ -42,7 +34,7 @@ const useStyles = makeStyles({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const TimeSelector: FC<TimeSelectorProps> = ({setDuration}) => {
|
export const TimeSelector: FC = () => {
|
||||||
|
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
|
|
||||||
|
@ -60,14 +52,46 @@ export const TimeSelector: FC<TimeSelectorProps> = ({setDuration}) => {
|
||||||
setFrom(formatDateForNativeInput(dateFromSeconds(start)));
|
setFrom(formatDateForNativeInput(dateFromSeconds(start)));
|
||||||
}, [start]);
|
}, [start]);
|
||||||
|
|
||||||
return <Box className={classes.container}>
|
const setDuration = (dur: string, from: Date) => {
|
||||||
{/*setup duration*/}
|
dispatch({type: "SET_UNTIL", payload: from});
|
||||||
<Box>
|
setAnchorEl(null);
|
||||||
<TimeDurationSelector setDuration={setDuration}/>
|
dispatch({type: "SET_DURATION", payload: dur});
|
||||||
</Box>
|
};
|
||||||
{/*setup end time*/}
|
|
||||||
|
const formatRange = useMemo(() => {
|
||||||
|
const startFormat = dayjs(dateFromSeconds(start)).format(formatDate);
|
||||||
|
const endFormat = dayjs(dateFromSeconds(end)).format(formatDate);
|
||||||
|
return {
|
||||||
|
start: startFormat,
|
||||||
|
end: endFormat
|
||||||
|
};
|
||||||
|
}, [start, end]);
|
||||||
|
|
||||||
|
const [anchorEl, setAnchorEl] = useState<HTMLButtonElement | null>(null);
|
||||||
|
const open = Boolean(anchorEl);
|
||||||
|
|
||||||
|
return <>
|
||||||
|
<Tooltip title="Time range controls">
|
||||||
|
<Button variant="contained" color="primary"
|
||||||
|
sx={{
|
||||||
|
color: "white",
|
||||||
|
border: "1px solid rgba(0, 0, 0, 0.2)",
|
||||||
|
boxShadow: "none"
|
||||||
|
}}
|
||||||
|
startIcon={<QueryBuilderIcon/>}
|
||||||
|
onClick={(e) => setAnchorEl(e.currentTarget)}>
|
||||||
|
{formatRange.start} - {formatRange.end}
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
<Popper
|
||||||
|
open={open}
|
||||||
|
anchorEl={anchorEl}
|
||||||
|
placement="bottom-end"
|
||||||
|
modifiers={[{name: "offset", options: {offset: [0, 6]}}]}>
|
||||||
|
<ClickAwayListener onClickAway={() => setAnchorEl(null)}>
|
||||||
|
<Paper elevation={3}>
|
||||||
|
<Box className={classes.container}>
|
||||||
<Box className={classes.timeControls}>
|
<Box className={classes.timeControls}>
|
||||||
<Box className={classes.datePickers}>
|
|
||||||
<Box className={classes.datePickerItem}>
|
<Box className={classes.datePickerItem}>
|
||||||
<DateTimePicker
|
<DateTimePicker
|
||||||
label="From"
|
label="From"
|
||||||
|
@ -75,31 +99,41 @@ export const TimeSelector: FC<TimeSelectorProps> = ({setDuration}) => {
|
||||||
value={from}
|
value={from}
|
||||||
onChange={date => dispatch({type: "SET_FROM", payload: date as unknown as Date})}
|
onChange={date => dispatch({type: "SET_FROM", payload: date as unknown as Date})}
|
||||||
onError={console.log}
|
onError={console.log}
|
||||||
inputFormat="DD/MM/YYYY HH:mm:ss"
|
inputFormat={formatDate}
|
||||||
mask="__/__/____ __:__:__"
|
mask="____-__-__ __:__:__"
|
||||||
renderInput={(params) => <TextField {...params} variant="standard"/>}
|
renderInput={(params) => <TextField {...params} variant="standard"/>}
|
||||||
maxDate={dayjs(until)}
|
maxDate={dayjs(until)}
|
||||||
/>
|
PopperProps={{disablePortal: true}}/>
|
||||||
</Box>
|
</Box>
|
||||||
<Box className={classes.datePickerItem}>
|
<Box className={classes.datePickerItem}>
|
||||||
<DateTimePicker
|
<DateTimePicker
|
||||||
label="Until"
|
label="To"
|
||||||
ampm={false}
|
ampm={false}
|
||||||
value={until}
|
value={until}
|
||||||
onChange={date => dispatch({type: "SET_UNTIL", payload: date as unknown as Date})}
|
onChange={date => dispatch({type: "SET_UNTIL", payload: date as unknown as Date})}
|
||||||
onError={console.log}
|
onError={console.log}
|
||||||
inputFormat="DD/MM/YYYY HH:mm:ss"
|
inputFormat={formatDate}
|
||||||
mask="__/__/____ __:__:__"
|
mask="____-__-__ __:__:__"
|
||||||
renderInput={(params) => <TextField {...params} variant="standard"/>}
|
renderInput={(params) => <TextField {...params} variant="standard"/>}
|
||||||
/>
|
PopperProps={{disablePortal: true}}/>
|
||||||
|
</Box>
|
||||||
|
<Box display="grid" gridTemplateColumns="auto 1fr" gap={1}>
|
||||||
|
<Button variant="outlined" onClick={() => setAnchorEl(null)}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button variant="contained" onClick={() => dispatch({type: "RUN_QUERY_TO_NOW"})}>
|
||||||
|
switch to now
|
||||||
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
{/*setup duration*/}
|
||||||
|
<Divider orientation="vertical" flexItem />
|
||||||
<Box>
|
<Box>
|
||||||
<Typography variant="body2">
|
<TimeDurationSelector setDuration={setDuration}/>
|
||||||
Will be changed to current time for auto-refresh mode.
|
|
||||||
<InlineBtn handler={() => dispatch({type: "RUN_QUERY_TO_NOW"})} text="Switch to now"/>
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>;
|
</Paper>
|
||||||
|
</ClickAwayListener>
|
||||||
|
</Popper>
|
||||||
|
</>;
|
||||||
};
|
};
|
||||||
|
|
|
@ -10,6 +10,8 @@ import QueryConfigurator from "./Configurator/Query/QueryConfigurator";
|
||||||
import {useFetchQuery} from "./Configurator/Query/useFetchQuery";
|
import {useFetchQuery} from "./Configurator/Query/useFetchQuery";
|
||||||
import JsonView from "./Views/JsonView";
|
import JsonView from "./Views/JsonView";
|
||||||
import Header from "../Header/Header";
|
import Header from "../Header/Header";
|
||||||
|
import {DisplayTypeSwitch} from "./Configurator/DisplayTypeSwitch";
|
||||||
|
import GraphSettings from "./Configurator/Graph/GraphSettings";
|
||||||
|
|
||||||
const HomeLayout: FC = () => {
|
const HomeLayout: FC = () => {
|
||||||
|
|
||||||
|
@ -20,18 +22,16 @@ const HomeLayout: FC = () => {
|
||||||
return (
|
return (
|
||||||
<Box id="homeLayout">
|
<Box id="homeLayout">
|
||||||
<Header/>
|
<Header/>
|
||||||
<Box p={4} display="grid" gridTemplateRows="auto 1fr" gap={"20px"} style={{minHeight: "calc(100vh - 64px)"}}>
|
<Box p={4} display="grid" gridTemplateRows="auto 1fr" style={{minHeight: "calc(100vh - 64px)"}}>
|
||||||
<Box>
|
|
||||||
<QueryConfigurator error={error} queryOptions={queryOptions}/>
|
<QueryConfigurator error={error} queryOptions={queryOptions}/>
|
||||||
</Box>
|
<Box height="100%">
|
||||||
<Box height={"100%"}>
|
|
||||||
{isLoading && <Fade in={isLoading} style={{
|
{isLoading && <Fade in={isLoading} style={{
|
||||||
transitionDelay: isLoading ? "300ms" : "0ms",
|
transitionDelay: isLoading ? "300ms" : "0ms",
|
||||||
}}>
|
}}>
|
||||||
<Box alignItems="center" justifyContent="center" flexDirection="column" display="flex"
|
<Box alignItems="center" justifyContent="center" flexDirection="column" display="flex"
|
||||||
style={{
|
style={{
|
||||||
width: "100%",
|
width: "100%",
|
||||||
maxWidth: "calc(100vw - 32px)",
|
maxWidth: "calc(100vw - 64px)",
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
height: "50%",
|
height: "50%",
|
||||||
background: "linear-gradient(rgba(255,255,255,.7), rgba(255,255,255,.7), rgba(255,255,255,0))"
|
background: "linear-gradient(rgba(255,255,255,.7), rgba(255,255,255,.7), rgba(255,255,255,0))"
|
||||||
|
@ -40,12 +40,16 @@ const HomeLayout: FC = () => {
|
||||||
</Box>
|
</Box>
|
||||||
</Fade>}
|
</Fade>}
|
||||||
{<Box height={"100%"} bgcolor={"#fff"}>
|
{<Box height={"100%"} bgcolor={"#fff"}>
|
||||||
{error &&
|
<Box display="grid" gridTemplateColumns="1fr auto" alignItems="center" mx={-4} px={4} mb={2}
|
||||||
<Alert color="error" severity="error" style={{fontSize: "14px", whiteSpace: "pre-wrap"}}>
|
borderBottom={1} borderColor="divider">
|
||||||
|
<DisplayTypeSwitch/>
|
||||||
|
{displayType === "chart" && <GraphSettings/>}
|
||||||
|
</Box>
|
||||||
|
{error && <Alert color="error" severity="error"
|
||||||
|
style={{fontSize: "14px", whiteSpace: "pre-wrap", marginTop: "20px"}}>
|
||||||
{error}
|
{error}
|
||||||
</Alert>}
|
</Alert>}
|
||||||
{graphData && period && (displayType === "chart") &&
|
{graphData && period && (displayType === "chart") && <GraphView data={graphData}/>}
|
||||||
<GraphView data={graphData}/>}
|
|
||||||
{liveData && (displayType === "code") && <JsonView data={liveData}/>}
|
{liveData && (displayType === "code") && <JsonView data={liveData}/>}
|
||||||
{liveData && (displayType === "table") && <TableView data={liveData}/>}
|
{liveData && (displayType === "table") && <TableView data={liveData}/>}
|
||||||
</Box>}
|
</Box>}
|
||||||
|
|
|
@ -7,7 +7,6 @@ import {useGraphDispatch, useGraphState} from "../../../state/graph/GraphStateCo
|
||||||
import {getHideSeries, getLegendItem, getSeriesItem} from "../../../utils/uplot/series";
|
import {getHideSeries, getLegendItem, getSeriesItem} from "../../../utils/uplot/series";
|
||||||
import {getLimitsYAxis, getTimeSeries} from "../../../utils/uplot/axes";
|
import {getLimitsYAxis, getTimeSeries} from "../../../utils/uplot/axes";
|
||||||
import {LegendItem} from "../../../utils/uplot/types";
|
import {LegendItem} from "../../../utils/uplot/types";
|
||||||
import GraphSettings from "../Configurator/Graph/GraphSettings";
|
|
||||||
import {useAppState} from "../../../state/common/StateContext";
|
import {useAppState} from "../../../state/common/StateContext";
|
||||||
|
|
||||||
export interface GraphViewProps {
|
export interface GraphViewProps {
|
||||||
|
@ -82,7 +81,6 @@ const GraphView: FC<GraphViewProps> = ({data = []}) => {
|
||||||
return <>
|
return <>
|
||||||
{(data.length > 0)
|
{(data.length > 0)
|
||||||
? <div>
|
? <div>
|
||||||
<GraphSettings/>
|
|
||||||
<LineChart data={dataChart} series={series} metrics={data}/>
|
<LineChart data={dataChart} series={series} metrics={data}/>
|
||||||
<Legend labels={legend} onChange={onChangeLegend}/>
|
<Legend labels={legend} onChange={onChangeLegend}/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -15,18 +15,24 @@ const JsonView: FC<JsonViewProps> = ({data}) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box position="relative">
|
<Box position="relative">
|
||||||
<Box flexDirection="column" justifyContent="flex-end" display="flex"
|
<Box
|
||||||
style={{
|
style={{
|
||||||
position: "fixed",
|
position: "sticky",
|
||||||
right: "16px"
|
top: "16px",
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "flex-end",
|
||||||
}}>
|
}}>
|
||||||
<Button variant="outlined" onClick={(e) => {
|
<Button variant="outlined"
|
||||||
|
fullWidth={false}
|
||||||
|
onClick={(e) => {
|
||||||
navigator.clipboard.writeText(formattedJson);
|
navigator.clipboard.writeText(formattedJson);
|
||||||
showInfoMessage("Formatted JSON has been copied");
|
showInfoMessage("Formatted JSON has been copied");
|
||||||
e.preventDefault(); // needed to avoid snackbar immediate disappearing
|
e.preventDefault(); // needed to avoid snackbar immediate disappearing
|
||||||
}}>Copy JSON</Button>
|
}}>
|
||||||
|
Copy JSON
|
||||||
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
<pre>{formattedJson}</pre>
|
<pre style={{margin: 0}}>{formattedJson}</pre>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import React, {FC, useMemo} from "preact/compat";
|
import React, {FC, useMemo} from "preact/compat";
|
||||||
import {InstantMetricResult} from "../../../api/types";
|
import {InstantMetricResult} from "../../../api/types";
|
||||||
import {InstantDataSeries} from "../../../types";
|
import {InstantDataSeries} from "../../../types";
|
||||||
import Paper from "@mui/material/Paper";
|
|
||||||
import Table from "@mui/material/Table";
|
import Table from "@mui/material/Table";
|
||||||
import TableBody from "@mui/material/TableBody";
|
import TableBody from "@mui/material/TableBody";
|
||||||
import TableCell from "@mui/material/TableCell";
|
import TableCell from "@mui/material/TableCell";
|
||||||
|
@ -37,7 +36,7 @@ const TableView: FC<GraphViewProps> = ({data}) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{(rows.length > 0)
|
{(rows.length > 0)
|
||||||
? <TableContainer component={Paper}>
|
? <TableContainer>
|
||||||
<Table aria-label="simple table">
|
<Table aria-label="simple table">
|
||||||
<TableHead>
|
<TableHead>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
|
@ -48,7 +47,7 @@ const TableView: FC<GraphViewProps> = ({data}) => {
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{rows.map((row, index) => (
|
{rows.map((row, index) => (
|
||||||
<TableRow key={index}>
|
<TableRow key={index} hover>
|
||||||
{row.metadata.map((rowMeta, index2) => {
|
{row.metadata.map((rowMeta, index2) => {
|
||||||
const prevRowValue = rows[index - 1] && rows[index - 1].metadata[index2];
|
const prevRowValue = rows[index - 1] && rows[index - 1].metadata[index2];
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -1,26 +0,0 @@
|
||||||
import Box from "@mui/material/Box";
|
|
||||||
import CircularProgress, {CircularProgressProps} from "@mui/material/CircularProgress";
|
|
||||||
import Typography from "@mui/material/Typography";
|
|
||||||
import React, {FC} from "preact/compat";
|
|
||||||
|
|
||||||
const CircularProgressWithLabel: FC<CircularProgressProps & { label: number }> = (props) => {
|
|
||||||
return (
|
|
||||||
<Box position="relative" display="inline-flex">
|
|
||||||
<CircularProgress variant="determinate" {...props} />
|
|
||||||
<Box
|
|
||||||
top={0}
|
|
||||||
left={0}
|
|
||||||
bottom={0}
|
|
||||||
right={0}
|
|
||||||
position="absolute"
|
|
||||||
display="flex"
|
|
||||||
alignItems="center"
|
|
||||||
justifyContent="center"
|
|
||||||
>
|
|
||||||
<Typography variant="caption" component="div">{`${props.label}s`}</Typography>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default CircularProgressWithLabel;
|
|
|
@ -1,19 +0,0 @@
|
||||||
import makeStyles from "@mui/styles/makeStyles";
|
|
||||||
import React from "preact/compat";
|
|
||||||
import Link from "@mui/material/Link";
|
|
||||||
|
|
||||||
const useStyles = makeStyles({
|
|
||||||
inlineBtn: {
|
|
||||||
"&:hover": {
|
|
||||||
cursor: "pointer"
|
|
||||||
},
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export const InlineBtn: React.FC<{handler: () => void; text: string}> = ({handler, text}) => {
|
|
||||||
const classes = useStyles();
|
|
||||||
return <Link component="span" className={classes.inlineBtn}
|
|
||||||
onClick={handler}>
|
|
||||||
{text}
|
|
||||||
</Link>;
|
|
||||||
};
|
|
|
@ -1,5 +1,26 @@
|
||||||
import { useState, useEffect } from "preact/compat";
|
import { useState, useEffect } from "preact/compat";
|
||||||
|
|
||||||
|
const getScrollbarWidth = () => {
|
||||||
|
// Creating invisible container
|
||||||
|
const outer = document.createElement("div");
|
||||||
|
outer.style.visibility = "hidden";
|
||||||
|
outer.style.overflow = "scroll"; // forcing scrollbar to appear
|
||||||
|
document.body.appendChild(outer);
|
||||||
|
|
||||||
|
// Creating inner element and placing it in the container
|
||||||
|
const inner = document.createElement("div");
|
||||||
|
outer.appendChild(inner);
|
||||||
|
|
||||||
|
// Calculating difference between container's full width and the child width
|
||||||
|
const scrollbarWidth = (outer.offsetWidth - inner.offsetWidth);
|
||||||
|
|
||||||
|
// Removing temporary elements from the DOM
|
||||||
|
inner.remove();
|
||||||
|
outer.remove();
|
||||||
|
|
||||||
|
return scrollbarWidth;
|
||||||
|
};
|
||||||
|
|
||||||
const useResize = (node: HTMLElement | null): {width: number, height: number} => {
|
const useResize = (node: HTMLElement | null): {width: number, height: number} => {
|
||||||
const [windowSize, setWindowSize] = useState({
|
const [windowSize, setWindowSize] = useState({
|
||||||
width: 0,
|
width: 0,
|
||||||
|
@ -9,7 +30,7 @@ const useResize = (node: HTMLElement | null): {width: number, height: number} =>
|
||||||
if (!node) return;
|
if (!node) return;
|
||||||
const handleResize = () => {
|
const handleResize = () => {
|
||||||
setWindowSize({
|
setWindowSize({
|
||||||
width: node.offsetWidth,
|
width: node.offsetWidth - getScrollbarWidth(),
|
||||||
height: node.offsetHeight,
|
height: node.offsetHeight,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -59,7 +59,7 @@ const query = getQueryArray();
|
||||||
|
|
||||||
export const initialState: AppState = {
|
export const initialState: AppState = {
|
||||||
serverUrl: getDefaultServer(),
|
serverUrl: getDefaultServer(),
|
||||||
displayType: getQueryStringValue("tab", "chart") as DisplayType,
|
displayType: getQueryStringValue("g0.tab", "chart") as DisplayType,
|
||||||
query: query, // demo_memory_usage_bytes
|
query: query, // demo_memory_usage_bytes
|
||||||
queryHistory: query.map(q => ({index: 0, values: [q]})),
|
queryHistory: query.map(q => ({index: 0, values: [q]})),
|
||||||
time: {
|
time: {
|
||||||
|
|
|
@ -49,17 +49,27 @@ const THEME = createTheme({
|
||||||
MuiAccordion: {
|
MuiAccordion: {
|
||||||
styleOverrides: {
|
styleOverrides: {
|
||||||
root: {
|
root: {
|
||||||
boxShadow: "rgba(0, 0, 0, 0.16) 0px 1px 4px;"
|
boxShadow: "rgba(0, 0, 0, 0.16) 0px 1px 4px"
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
MuiPaper: {
|
MuiPaper: {
|
||||||
styleOverrides: {
|
styleOverrides: {
|
||||||
elevation3: {
|
root: {
|
||||||
boxShadow: "rgba(0, 0, 0, 0.2) 0px 3px 8px;"
|
boxShadow: "rgba(0, 0, 0, 0.2) 0px 3px 8px"
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
MuiButton: {
|
||||||
|
styleOverrides: {
|
||||||
|
contained: {
|
||||||
|
boxShadow: "rgba(17, 17, 26, 0.1) 0px 0px 16px",
|
||||||
|
"&:hover": {
|
||||||
|
boxShadow: "rgba(0, 0, 0, 0.1) 0px 4px 12px",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
MuiIconButton: {
|
MuiIconButton: {
|
||||||
defaultProps: {
|
defaultProps: {
|
||||||
size: "large",
|
size: "large",
|
||||||
|
@ -77,6 +87,20 @@ const THEME = createTheme({
|
||||||
borderRadius: "20%",
|
borderRadius: "20%",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
MuiTooltip: {
|
||||||
|
styleOverrides: {
|
||||||
|
tooltip: {
|
||||||
|
fontSize: "10px"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
MuiAlert: {
|
||||||
|
styleOverrides: {
|
||||||
|
root: {
|
||||||
|
boxShadow: "rgba(0, 0, 0, 0.08) 0px 4px 12px"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
typography: {
|
typography: {
|
||||||
|
|
Loading…
Reference in a new issue