mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2024-11-21 14:44:00 +00:00
vmui: improve mobile ui (#3848)
* feat: improve mobile ui * feat: improve mobile ui * fix: change style server url * fix: improve ExploreMetrics mobile * fix: display global settings on all pages
This commit is contained in:
parent
a02cf92fd1
commit
d4fc0ed874
106 changed files with 1554 additions and 445 deletions
|
@ -1,11 +1,15 @@
|
||||||
import React, { FC } from "preact/compat";
|
import React, { FC, useRef, useState } from "preact/compat";
|
||||||
import { useCustomPanelDispatch, useCustomPanelState } from "../../../state/customPanel/CustomPanelStateContext";
|
import { useCustomPanelDispatch, useCustomPanelState } from "../../../state/customPanel/CustomPanelStateContext";
|
||||||
import { useQueryDispatch, useQueryState } from "../../../state/query/QueryStateContext";
|
import { useQueryDispatch, useQueryState } from "../../../state/query/QueryStateContext";
|
||||||
import "./style.scss";
|
import "./style.scss";
|
||||||
import Switch from "../../Main/Switch/Switch";
|
import Switch from "../../Main/Switch/Switch";
|
||||||
|
import useDeviceDetect from "../../../hooks/useDeviceDetect";
|
||||||
|
import Popper from "../../Main/Popper/Popper";
|
||||||
|
import { TuneIcon } from "../../Main/Icons";
|
||||||
|
import Button from "../../Main/Button/Button";
|
||||||
|
import classNames from "classnames";
|
||||||
|
|
||||||
const AdditionalSettings: FC = () => {
|
const AdditionalSettingsControls: FC<{isMobile?: boolean}> = ({ isMobile }) => {
|
||||||
|
|
||||||
const { autocomplete } = useQueryState();
|
const { autocomplete } = useQueryState();
|
||||||
const queryDispatch = useQueryDispatch();
|
const queryDispatch = useQueryDispatch();
|
||||||
|
|
||||||
|
@ -24,23 +28,72 @@ const AdditionalSettings: FC = () => {
|
||||||
queryDispatch({ type: "TOGGLE_AUTOCOMPLETE" });
|
queryDispatch({ type: "TOGGLE_AUTOCOMPLETE" });
|
||||||
};
|
};
|
||||||
|
|
||||||
return <div className="vm-additional-settings">
|
return (
|
||||||
|
<div
|
||||||
|
className={classNames({
|
||||||
|
"vm-additional-settings": true,
|
||||||
|
"vm-additional-settings_mobile": isMobile
|
||||||
|
})}
|
||||||
|
>
|
||||||
<Switch
|
<Switch
|
||||||
label={"Autocomplete"}
|
label={"Autocomplete"}
|
||||||
value={autocomplete}
|
value={autocomplete}
|
||||||
onChange={onChangeAutocomplete}
|
onChange={onChangeAutocomplete}
|
||||||
|
fullWidth={isMobile}
|
||||||
/>
|
/>
|
||||||
<Switch
|
<Switch
|
||||||
label={"Disable cache"}
|
label={"Disable cache"}
|
||||||
value={nocache}
|
value={nocache}
|
||||||
onChange={onChangeCache}
|
onChange={onChangeCache}
|
||||||
|
fullWidth={isMobile}
|
||||||
/>
|
/>
|
||||||
<Switch
|
<Switch
|
||||||
label={"Trace query"}
|
label={"Trace query"}
|
||||||
value={isTracingEnabled}
|
value={isTracingEnabled}
|
||||||
onChange={onChangeQueryTracing}
|
onChange={onChangeQueryTracing}
|
||||||
|
fullWidth={isMobile}
|
||||||
/>
|
/>
|
||||||
</div>;
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const AdditionalSettings: FC = () => {
|
||||||
|
const { isMobile } = useDeviceDetect();
|
||||||
|
const [openList, setOpenList] = useState(false);
|
||||||
|
const targetRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
const handleToggleList = () => {
|
||||||
|
setOpenList(prev => !prev);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCloseList = () => {
|
||||||
|
setOpenList(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isMobile) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div ref={targetRef}>
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
startIcon={<TuneIcon/>}
|
||||||
|
onClick={handleToggleList}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Popper
|
||||||
|
open={openList}
|
||||||
|
buttonRef={targetRef}
|
||||||
|
placement="bottom-left"
|
||||||
|
onClose={handleCloseList}
|
||||||
|
title={"Query settings"}
|
||||||
|
>
|
||||||
|
<AdditionalSettingsControls isMobile={isMobile}/>
|
||||||
|
</Popper>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <AdditionalSettingsControls/>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default AdditionalSettings;
|
export default AdditionalSettings;
|
||||||
|
|
|
@ -5,10 +5,19 @@
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 24px;
|
gap: $padding-global;
|
||||||
|
|
||||||
&__input {
|
&__input {
|
||||||
flex-basis: 160px;
|
flex-basis: 160px;
|
||||||
margin-bottom: -6px;
|
margin-bottom: -6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&_mobile {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
align-items: flex-start;
|
||||||
|
padding: 0 $padding-global;
|
||||||
|
gap: $padding-medium;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,13 +2,15 @@ import React, { FC, useMemo, useRef } from "preact/compat";
|
||||||
import { useCardinalityState, useCardinalityDispatch } from "../../../state/cardinality/CardinalityStateContext";
|
import { useCardinalityState, useCardinalityDispatch } from "../../../state/cardinality/CardinalityStateContext";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import Button from "../../Main/Button/Button";
|
import Button from "../../Main/Button/Button";
|
||||||
import { CalendarIcon } from "../../Main/Icons";
|
import { ArrowDownIcon, CalendarIcon } from "../../Main/Icons";
|
||||||
import Tooltip from "../../Main/Tooltip/Tooltip";
|
import Tooltip from "../../Main/Tooltip/Tooltip";
|
||||||
import { getAppModeEnable } from "../../../utils/app-mode";
|
import { getAppModeEnable } from "../../../utils/app-mode";
|
||||||
import { DATE_FORMAT } from "../../../constants/date";
|
import { DATE_FORMAT } from "../../../constants/date";
|
||||||
import DatePicker from "../../Main/DatePicker/DatePicker";
|
import DatePicker from "../../Main/DatePicker/DatePicker";
|
||||||
|
import useDeviceDetect from "../../../hooks/useDeviceDetect";
|
||||||
|
|
||||||
const CardinalityDatePicker: FC = () => {
|
const CardinalityDatePicker: FC = () => {
|
||||||
|
const { isMobile } = useDeviceDetect();
|
||||||
const appModeEnable = getAppModeEnable();
|
const appModeEnable = getAppModeEnable();
|
||||||
const buttonRef = useRef<HTMLDivElement>(null);
|
const buttonRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
@ -24,6 +26,16 @@ const CardinalityDatePicker: FC = () => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div ref={buttonRef}>
|
<div ref={buttonRef}>
|
||||||
|
{isMobile ? (
|
||||||
|
<div className="vm-mobile-option">
|
||||||
|
<span className="vm-mobile-option__icon"><CalendarIcon/></span>
|
||||||
|
<div className="vm-mobile-option-text">
|
||||||
|
<span className="vm-mobile-option-text__label">Date control</span>
|
||||||
|
<span className="vm-mobile-option-text__value">{dateFormatted}</span>
|
||||||
|
</div>
|
||||||
|
<span className="vm-mobile-option__arrow"><ArrowDownIcon/></span>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
<Tooltip title="Date control">
|
<Tooltip title="Date control">
|
||||||
<Button
|
<Button
|
||||||
className={appModeEnable ? "" : "vm-header-button"}
|
className={appModeEnable ? "" : "vm-header-button"}
|
||||||
|
@ -34,8 +46,10 @@ const CardinalityDatePicker: FC = () => {
|
||||||
{dateFormatted}
|
{dateFormatted}
|
||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<DatePicker
|
<DatePicker
|
||||||
|
label="Date control"
|
||||||
date={date || ""}
|
date={date || ""}
|
||||||
format={DATE_FORMAT}
|
format={DATE_FORMAT}
|
||||||
onChange={handleChangeDate}
|
onChange={handleChangeDate}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import React, { FC, useEffect, useState } from "preact/compat";
|
import React, { FC, useEffect, useState } from "preact/compat";
|
||||||
import ServerConfigurator from "./ServerConfigurator/ServerConfigurator";
|
import ServerConfigurator from "./ServerConfigurator/ServerConfigurator";
|
||||||
import { useAppDispatch, useAppState } from "../../../state/common/StateContext";
|
import { useAppDispatch, useAppState } from "../../../state/common/StateContext";
|
||||||
import { SettingsIcon } from "../../Main/Icons";
|
import { ArrowDownIcon, SettingsIcon } from "../../Main/Icons";
|
||||||
import Button from "../../Main/Button/Button";
|
import Button from "../../Main/Button/Button";
|
||||||
import Modal from "../../Main/Modal/Modal";
|
import Modal from "../../Main/Modal/Modal";
|
||||||
import "./style.scss";
|
import "./style.scss";
|
||||||
|
@ -18,7 +18,7 @@ import useDeviceDetect from "../../../hooks/useDeviceDetect";
|
||||||
|
|
||||||
const title = "Settings";
|
const title = "Settings";
|
||||||
|
|
||||||
const GlobalSettings: FC<{showTitle?: boolean}> = ({ showTitle }) => {
|
const GlobalSettings: FC = () => {
|
||||||
const { isMobile } = useDeviceDetect();
|
const { isMobile } = useDeviceDetect();
|
||||||
|
|
||||||
const appModeEnable = getAppModeEnable();
|
const appModeEnable = getAppModeEnable();
|
||||||
|
@ -42,7 +42,6 @@ const GlobalSettings: FC<{showTitle?: boolean}> = ({ showTitle }) => {
|
||||||
dispatch({ type: "SET_SERVER", payload: serverUrl });
|
dispatch({ type: "SET_SERVER", payload: serverUrl });
|
||||||
timeDispatch({ type: "SET_TIMEZONE", payload: timezone });
|
timeDispatch({ type: "SET_TIMEZONE", payload: timezone });
|
||||||
customPanelDispatch({ type: "SET_SERIES_LIMITS", payload: limits });
|
customPanelDispatch({ type: "SET_SERIES_LIMITS", payload: limits });
|
||||||
handleClose();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -51,10 +50,19 @@ const GlobalSettings: FC<{showTitle?: boolean}> = ({ showTitle }) => {
|
||||||
}, [stateServerUrl]);
|
}, [stateServerUrl]);
|
||||||
|
|
||||||
return <>
|
return <>
|
||||||
<Tooltip
|
{isMobile ? (
|
||||||
open={showTitle === true ? false : undefined}
|
<div
|
||||||
title={title}
|
className="vm-mobile-option"
|
||||||
|
onClick={handleOpen}
|
||||||
>
|
>
|
||||||
|
<span className="vm-mobile-option__icon"><SettingsIcon/></span>
|
||||||
|
<div className="vm-mobile-option-text">
|
||||||
|
<span className="vm-mobile-option-text__label">{title}</span>
|
||||||
|
</div>
|
||||||
|
<span className="vm-mobile-option__arrow"><ArrowDownIcon/></span>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<Tooltip title={title}>
|
||||||
<Button
|
<Button
|
||||||
className={classNames({
|
className={classNames({
|
||||||
"vm-header-button": !appModeEnable
|
"vm-header-button": !appModeEnable
|
||||||
|
@ -63,10 +71,9 @@ const GlobalSettings: FC<{showTitle?: boolean}> = ({ showTitle }) => {
|
||||||
color="primary"
|
color="primary"
|
||||||
startIcon={<SettingsIcon/>}
|
startIcon={<SettingsIcon/>}
|
||||||
onClick={handleOpen}
|
onClick={handleOpen}
|
||||||
>
|
/>
|
||||||
{showTitle && title}
|
|
||||||
</Button>
|
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
)}
|
||||||
{open && (
|
{open && (
|
||||||
<Modal
|
<Modal
|
||||||
title={title}
|
title={title}
|
||||||
|
@ -84,6 +91,7 @@ const GlobalSettings: FC<{showTitle?: boolean}> = ({ showTitle }) => {
|
||||||
serverUrl={serverUrl}
|
serverUrl={serverUrl}
|
||||||
onChange={setServerUrl}
|
onChange={setServerUrl}
|
||||||
onEnter={handlerApply}
|
onEnter={handlerApply}
|
||||||
|
onBlur={handlerApply}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
@ -105,21 +113,6 @@ const GlobalSettings: FC<{showTitle?: boolean}> = ({ showTitle }) => {
|
||||||
<ThemeControl/>
|
<ThemeControl/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="vm-server-configurator__footer">
|
|
||||||
<Button
|
|
||||||
variant="outlined"
|
|
||||||
color="error"
|
|
||||||
onClick={handleClose}
|
|
||||||
>
|
|
||||||
Cancel
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant="contained"
|
|
||||||
onClick={handlerApply}
|
|
||||||
>
|
|
||||||
apply
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -6,6 +6,8 @@ import { InfoIcon, RestartIcon } from "../../../Main/Icons";
|
||||||
import Button from "../../../Main/Button/Button";
|
import Button from "../../../Main/Button/Button";
|
||||||
import { DEFAULT_MAX_SERIES } from "../../../../constants/graph";
|
import { DEFAULT_MAX_SERIES } from "../../../../constants/graph";
|
||||||
import "./style.scss";
|
import "./style.scss";
|
||||||
|
import classNames from "classnames";
|
||||||
|
import useDeviceDetect from "../../../../hooks/useDeviceDetect";
|
||||||
|
|
||||||
export interface ServerConfiguratorProps {
|
export interface ServerConfiguratorProps {
|
||||||
limits: SeriesLimits
|
limits: SeriesLimits
|
||||||
|
@ -20,6 +22,7 @@ const fields: {label: string, type: DisplayType}[] = [
|
||||||
];
|
];
|
||||||
|
|
||||||
const LimitsConfigurator: FC<ServerConfiguratorProps> = ({ limits, onChange , onEnter }) => {
|
const LimitsConfigurator: FC<ServerConfiguratorProps> = ({ limits, onChange , onEnter }) => {
|
||||||
|
const { isMobile } = useDeviceDetect();
|
||||||
|
|
||||||
const [error, setError] = useState({
|
const [error, setError] = useState({
|
||||||
table: "",
|
table: "",
|
||||||
|
@ -68,7 +71,12 @@ const LimitsConfigurator: FC<ServerConfiguratorProps> = ({ limits, onChange , on
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="vm-limits-configurator__inputs">
|
<div
|
||||||
|
className={classNames({
|
||||||
|
"vm-limits-configurator__inputs": true,
|
||||||
|
"vm-limits-configurator__inputs_mobile": isMobile
|
||||||
|
})}
|
||||||
|
>
|
||||||
{fields.map(f => (
|
{fields.map(f => (
|
||||||
<div key={f.type}>
|
<div key={f.type}>
|
||||||
<TextField
|
<TextField
|
||||||
|
|
|
@ -18,6 +18,10 @@
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
gap: $padding-global;
|
gap: $padding-global;
|
||||||
|
|
||||||
|
&_mobile {
|
||||||
|
gap: $padding-small;
|
||||||
|
}
|
||||||
|
|
||||||
div {
|
div {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,9 +7,10 @@ export interface ServerConfiguratorProps {
|
||||||
serverUrl: string
|
serverUrl: string
|
||||||
onChange: (url: string) => void
|
onChange: (url: string) => void
|
||||||
onEnter: () => void
|
onEnter: () => void
|
||||||
|
onBlur: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const ServerConfigurator: FC<ServerConfiguratorProps> = ({ serverUrl, onChange , onEnter }) => {
|
const ServerConfigurator: FC<ServerConfiguratorProps> = ({ serverUrl, onChange , onEnter, onBlur }) => {
|
||||||
|
|
||||||
const [error, setError] = useState("");
|
const [error, setError] = useState("");
|
||||||
|
|
||||||
|
@ -29,6 +30,8 @@ const ServerConfigurator: FC<ServerConfiguratorProps> = ({ serverUrl, onChange ,
|
||||||
error={error}
|
error={error}
|
||||||
onChange={onChangeServer}
|
onChange={onChangeServer}
|
||||||
onEnter={onEnter}
|
onEnter={onEnter}
|
||||||
|
onBlur={onBlur}
|
||||||
|
inputmode="url"
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -41,7 +41,7 @@ const TenantsConfiguration: FC<{accountIds: string[]}> = ({ accountIds }) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const showTenantSelector = useMemo(() => {
|
const showTenantSelector = useMemo(() => {
|
||||||
const id = getTenantIdFromUrl(serverUrl);
|
const id = true; //getTenantIdFromUrl(serverUrl);
|
||||||
return accountIds.length > 1 && id;
|
return accountIds.length > 1 && id;
|
||||||
}, [accountIds, serverUrl]);
|
}, [accountIds, serverUrl]);
|
||||||
|
|
||||||
|
@ -81,13 +81,26 @@ const TenantsConfiguration: FC<{accountIds: string[]}> = ({ accountIds }) => {
|
||||||
<div className="vm-tenant-input">
|
<div className="vm-tenant-input">
|
||||||
<Tooltip title="Define Tenant ID if you need request to another storage">
|
<Tooltip title="Define Tenant ID if you need request to another storage">
|
||||||
<div ref={optionsButtonRef}>
|
<div ref={optionsButtonRef}>
|
||||||
|
{isMobile ? (
|
||||||
|
<div
|
||||||
|
className="vm-mobile-option"
|
||||||
|
onClick={toggleOpenOptions}
|
||||||
|
>
|
||||||
|
<span className="vm-mobile-option__icon"><StorageIcon/></span>
|
||||||
|
<div className="vm-mobile-option-text">
|
||||||
|
<span className="vm-mobile-option-text__label">Tenant ID</span>
|
||||||
|
<span className="vm-mobile-option-text__value">{tenantIdState}</span>
|
||||||
|
</div>
|
||||||
|
<span className="vm-mobile-option__arrow"><ArrowDownIcon/></span>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
<Button
|
<Button
|
||||||
className={appModeEnable ? "" : "vm-header-button"}
|
className={appModeEnable ? "" : "vm-header-button"}
|
||||||
variant="contained"
|
variant="contained"
|
||||||
color="primary"
|
color="primary"
|
||||||
fullWidth
|
fullWidth
|
||||||
startIcon={<StorageIcon/>}
|
startIcon={<StorageIcon/>}
|
||||||
endIcon={!isMobile ? (
|
endIcon={(
|
||||||
<div
|
<div
|
||||||
className={classNames({
|
className={classNames({
|
||||||
"vm-execution-controls-buttons__arrow": true,
|
"vm-execution-controls-buttons__arrow": true,
|
||||||
|
@ -96,11 +109,12 @@ const TenantsConfiguration: FC<{accountIds: string[]}> = ({ accountIds }) => {
|
||||||
>
|
>
|
||||||
<ArrowDownIcon/>
|
<ArrowDownIcon/>
|
||||||
</div>
|
</div>
|
||||||
) : undefined}
|
)}
|
||||||
onClick={toggleOpenOptions}
|
onClick={toggleOpenOptions}
|
||||||
>
|
>
|
||||||
{!isMobile && tenantIdState}
|
{tenantIdState}
|
||||||
</Button>
|
</Button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Popper
|
<Popper
|
||||||
|
@ -108,20 +122,28 @@ const TenantsConfiguration: FC<{accountIds: string[]}> = ({ accountIds }) => {
|
||||||
placement="bottom-right"
|
placement="bottom-right"
|
||||||
onClose={handleCloseOptions}
|
onClose={handleCloseOptions}
|
||||||
buttonRef={optionsButtonRef}
|
buttonRef={optionsButtonRef}
|
||||||
|
title={isMobile ? "Define Tenant ID" : undefined}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={classNames({
|
||||||
|
"vm-list vm-tenant-input-list": true,
|
||||||
|
"vm-list vm-tenant-input-list_mobile": isMobile,
|
||||||
|
})}
|
||||||
>
|
>
|
||||||
<div className="vm-list vm-tenant-input-list">
|
|
||||||
<div className="vm-tenant-input-list__search">
|
<div className="vm-tenant-input-list__search">
|
||||||
<TextField
|
<TextField
|
||||||
autofocus
|
autofocus
|
||||||
label="Search"
|
label="Search"
|
||||||
value={search}
|
value={search}
|
||||||
onChange={setSearch}
|
onChange={setSearch}
|
||||||
|
type="search"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{accountIdsFiltered.map(id => (
|
{accountIdsFiltered.map(id => (
|
||||||
<div
|
<div
|
||||||
className={classNames({
|
className={classNames({
|
||||||
"vm-list-item": true,
|
"vm-list-item": true,
|
||||||
|
"vm-list-item_mobile": isMobile,
|
||||||
"vm-list-item_active": id === tenantIdState
|
"vm-list-item_active": id === tenantIdState
|
||||||
})}
|
})}
|
||||||
key={id}
|
key={id}
|
||||||
|
|
|
@ -9,10 +9,18 @@
|
||||||
overscroll-behavior: none;
|
overscroll-behavior: none;
|
||||||
border-radius: $border-radius-medium;
|
border-radius: $border-radius-medium;
|
||||||
|
|
||||||
|
&_mobile {
|
||||||
|
max-height: calc(($vh * 100) - 70px);
|
||||||
|
}
|
||||||
|
|
||||||
|
&_mobile &__search {
|
||||||
|
padding: 0 $padding-global $padding-small;
|
||||||
|
}
|
||||||
|
|
||||||
&__search {
|
&__search {
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: 0;
|
top: 0;
|
||||||
padding: $padding-small;
|
padding: $padding-small $padding-global;
|
||||||
background-color: $color-background-block;
|
background-color: $color-background-block;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import dayjs from "dayjs";
|
||||||
import TextField from "../../../Main/TextField/TextField";
|
import TextField from "../../../Main/TextField/TextField";
|
||||||
import { Timezone } from "../../../../types";
|
import { Timezone } from "../../../../types";
|
||||||
import "./style.scss";
|
import "./style.scss";
|
||||||
|
import useDeviceDetect from "../../../../hooks/useDeviceDetect";
|
||||||
|
|
||||||
interface TimezonesProps {
|
interface TimezonesProps {
|
||||||
timezoneState: string
|
timezoneState: string
|
||||||
|
@ -15,7 +16,7 @@ interface TimezonesProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
const Timezones: FC<TimezonesProps> = ({ timezoneState, onChange }) => {
|
const Timezones: FC<TimezonesProps> = ({ timezoneState, onChange }) => {
|
||||||
|
const { isMobile } = useDeviceDetect();
|
||||||
const timezones = getTimezoneList();
|
const timezones = getTimezoneList();
|
||||||
|
|
||||||
const [openList, setOpenList] = useState(false);
|
const [openList, setOpenList] = useState(false);
|
||||||
|
@ -92,8 +93,14 @@ const Timezones: FC<TimezonesProps> = ({ timezoneState, onChange }) => {
|
||||||
placement="bottom-left"
|
placement="bottom-left"
|
||||||
onClose={handleCloseList}
|
onClose={handleCloseList}
|
||||||
fullWidth
|
fullWidth
|
||||||
|
title={isMobile ? "Time zone" : undefined}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={classNames({
|
||||||
|
"vm-timezones-list": true,
|
||||||
|
"vm-timezones-list_mobile": isMobile,
|
||||||
|
})}
|
||||||
>
|
>
|
||||||
<div className="vm-timezones-list">
|
|
||||||
<div className="vm-timezones-list-header">
|
<div className="vm-timezones-list-header">
|
||||||
<div className="vm-timezones-list-header__search">
|
<div className="vm-timezones-list-header__search">
|
||||||
<TextField
|
<TextField
|
||||||
|
|
|
@ -51,6 +51,14 @@
|
||||||
border-radius: $border-radius-medium;
|
border-radius: $border-radius-medium;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
|
||||||
|
&_mobile {
|
||||||
|
max-height: calc(($vh * 100) - 70px);
|
||||||
|
}
|
||||||
|
|
||||||
|
&_mobile &-header__search {
|
||||||
|
padding: 0 $padding-global 0;
|
||||||
|
}
|
||||||
|
|
||||||
&-header {
|
&-header {
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: $padding-medium;
|
gap: $padding-medium;
|
||||||
width: 600px;
|
width: 600px;
|
||||||
|
padding-bottom: $padding-medium;
|
||||||
|
|
||||||
&_mobile {
|
&_mobile {
|
||||||
grid-auto-rows: min-content;
|
grid-auto-rows: min-content;
|
||||||
|
@ -20,12 +21,6 @@
|
||||||
|
|
||||||
&__input {
|
&__input {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
&_server {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr auto;
|
|
||||||
gap: 0 $padding-small;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&__title {
|
&__title {
|
||||||
|
@ -37,20 +32,4 @@
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
margin-bottom: $padding-global;
|
margin-bottom: $padding-global;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__footer {
|
|
||||||
display: inline-grid;
|
|
||||||
grid-template-columns: repeat(2, 1fr);
|
|
||||||
align-items: center;
|
|
||||||
justify-content: flex-end;
|
|
||||||
gap: $padding-small;
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&_mobile &__footer {
|
|
||||||
align-items: flex-end;
|
|
||||||
flex-grow: 1;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,8 @@ import { AxisRange, YaxisState } from "../../../../state/graph/reducer";
|
||||||
import "./style.scss";
|
import "./style.scss";
|
||||||
import TextField from "../../../Main/TextField/TextField";
|
import TextField from "../../../Main/TextField/TextField";
|
||||||
import Switch from "../../../Main/Switch/Switch";
|
import Switch from "../../../Main/Switch/Switch";
|
||||||
|
import useDeviceDetect from "../../../../hooks/useDeviceDetect";
|
||||||
|
import classNames from "classnames";
|
||||||
|
|
||||||
interface AxesLimitsConfiguratorProps {
|
interface AxesLimitsConfiguratorProps {
|
||||||
yaxis: YaxisState,
|
yaxis: YaxisState,
|
||||||
|
@ -12,6 +14,7 @@ interface AxesLimitsConfiguratorProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
const AxesLimitsConfigurator: FC<AxesLimitsConfiguratorProps> = ({ yaxis, setYaxisLimits, toggleEnableLimits }) => {
|
const AxesLimitsConfigurator: FC<AxesLimitsConfiguratorProps> = ({ yaxis, setYaxisLimits, toggleEnableLimits }) => {
|
||||||
|
const { isMobile } = useDeviceDetect();
|
||||||
|
|
||||||
const axes = useMemo(() => Object.keys(yaxis.limits.range), [yaxis.limits.range]);
|
const axes = useMemo(() => Object.keys(yaxis.limits.range), [yaxis.limits.range]);
|
||||||
|
|
||||||
|
@ -27,11 +30,17 @@ const AxesLimitsConfigurator: FC<AxesLimitsConfiguratorProps> = ({ yaxis, setYax
|
||||||
debouncedOnChangeLimit(val, axis, index);
|
debouncedOnChangeLimit(val, axis, index);
|
||||||
};
|
};
|
||||||
|
|
||||||
return <div className="vm-axes-limits">
|
return <div
|
||||||
|
className={classNames({
|
||||||
|
"vm-axes-limits": true,
|
||||||
|
"vm-axes-limits_mobile": isMobile
|
||||||
|
})}
|
||||||
|
>
|
||||||
<Switch
|
<Switch
|
||||||
value={yaxis.limits.enable}
|
value={yaxis.limits.enable}
|
||||||
onChange={toggleEnableLimits}
|
onChange={toggleEnableLimits}
|
||||||
label="Fix the limits for y-axis"
|
label="Fix the limits for y-axis"
|
||||||
|
fullWidth={isMobile}
|
||||||
/>
|
/>
|
||||||
<div className="vm-axes-limits-list">
|
<div className="vm-axes-limits-list">
|
||||||
{axes.map(axis => (
|
{axes.map(axis => (
|
||||||
|
|
|
@ -6,6 +6,16 @@
|
||||||
gap: $padding-global;
|
gap: $padding-global;
|
||||||
max-width: 300px;
|
max-width: 300px;
|
||||||
|
|
||||||
|
&_mobile {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
gap: $padding-medium;
|
||||||
|
}
|
||||||
|
|
||||||
|
&_mobile &-list__inputs {
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
&-list {
|
&-list {
|
||||||
display: grid;
|
display: grid;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
import React, { FC, useRef, useState } from "preact/compat";
|
import React, { FC, useRef, useState } from "preact/compat";
|
||||||
import AxesLimitsConfigurator from "./AxesLimitsConfigurator/AxesLimitsConfigurator";
|
import AxesLimitsConfigurator from "./AxesLimitsConfigurator/AxesLimitsConfigurator";
|
||||||
import { AxisRange, YaxisState } from "../../../state/graph/reducer";
|
import { AxisRange, YaxisState } from "../../../state/graph/reducer";
|
||||||
import { CloseIcon, SettingsIcon } from "../../Main/Icons";
|
import { SettingsIcon } from "../../Main/Icons";
|
||||||
import Button from "../../Main/Button/Button";
|
import Button from "../../Main/Button/Button";
|
||||||
import useClickOutside from "../../../hooks/useClickOutside";
|
|
||||||
import Popper from "../../Main/Popper/Popper";
|
import Popper from "../../Main/Popper/Popper";
|
||||||
import "./style.scss";
|
import "./style.scss";
|
||||||
import Tooltip from "../../Main/Tooltip/Tooltip";
|
import Tooltip from "../../Main/Tooltip/Tooltip";
|
||||||
|
@ -20,7 +19,6 @@ const GraphSettings: FC<GraphSettingsProps> = ({ yaxis, setYaxisLimits, toggleEn
|
||||||
const popperRef = useRef<HTMLDivElement>(null);
|
const popperRef = useRef<HTMLDivElement>(null);
|
||||||
const [openPopper, setOpenPopper] = useState(false);
|
const [openPopper, setOpenPopper] = useState(false);
|
||||||
const buttonRef = useRef<HTMLDivElement>(null);
|
const buttonRef = useRef<HTMLDivElement>(null);
|
||||||
useClickOutside(popperRef, () => setOpenPopper(false), buttonRef);
|
|
||||||
|
|
||||||
const toggleOpen = () => {
|
const toggleOpen = () => {
|
||||||
setOpenPopper(prev => !prev);
|
setOpenPopper(prev => !prev);
|
||||||
|
@ -46,22 +44,12 @@ const GraphSettings: FC<GraphSettingsProps> = ({ yaxis, setYaxisLimits, toggleEn
|
||||||
buttonRef={buttonRef}
|
buttonRef={buttonRef}
|
||||||
placement="bottom-right"
|
placement="bottom-right"
|
||||||
onClose={handleClose}
|
onClose={handleClose}
|
||||||
|
title={title}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="vm-graph-settings-popper"
|
className="vm-graph-settings-popper"
|
||||||
ref={popperRef}
|
ref={popperRef}
|
||||||
>
|
>
|
||||||
<div className="vm-popper-header">
|
|
||||||
<h3 className="vm-popper-header__title">
|
|
||||||
{title}
|
|
||||||
</h3>
|
|
||||||
<Button
|
|
||||||
size="small"
|
|
||||||
variant="text"
|
|
||||||
startIcon={<CloseIcon/>}
|
|
||||||
onClick={handleClose}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="vm-graph-settings-popper__body">
|
<div className="vm-graph-settings-popper__body">
|
||||||
<AxesLimitsConfigurator
|
<AxesLimitsConfigurator
|
||||||
yaxis={yaxis}
|
yaxis={yaxis}
|
||||||
|
|
|
@ -78,9 +78,11 @@ const QueryEditor: FC<QueryEditorProps> = ({
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
inputmode={"search"}
|
||||||
/>
|
/>
|
||||||
{autocomplete && (
|
{autocomplete && (
|
||||||
<Autocomplete
|
<Autocomplete
|
||||||
|
disabledFullScreen
|
||||||
value={value}
|
value={value}
|
||||||
options={options}
|
options={options}
|
||||||
anchor={autocompleteAnchorEl}
|
anchor={autocompleteAnchorEl}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import React, { FC, useEffect, useRef, useState } from "preact/compat";
|
import React, { FC, useEffect, useRef, useState } from "preact/compat";
|
||||||
import { RestartIcon, TimelineIcon } from "../../Main/Icons";
|
import { ArrowDownIcon, RestartIcon, TimelineIcon } from "../../Main/Icons";
|
||||||
import TextField from "../../Main/TextField/TextField";
|
import TextField from "../../Main/TextField/TextField";
|
||||||
import Button from "../../Main/Button/Button";
|
import Button from "../../Main/Button/Button";
|
||||||
import Tooltip from "../../Main/Tooltip/Tooltip";
|
import Tooltip from "../../Main/Tooltip/Tooltip";
|
||||||
|
@ -11,9 +11,12 @@ import usePrevious from "../../../hooks/usePrevious";
|
||||||
import "./style.scss";
|
import "./style.scss";
|
||||||
import { getAppModeEnable } from "../../../utils/app-mode";
|
import { getAppModeEnable } from "../../../utils/app-mode";
|
||||||
import Popper from "../../Main/Popper/Popper";
|
import Popper from "../../Main/Popper/Popper";
|
||||||
|
import useDeviceDetect from "../../../hooks/useDeviceDetect";
|
||||||
|
import classNames from "classnames";
|
||||||
|
|
||||||
const StepConfigurator: FC = () => {
|
const StepConfigurator: FC = () => {
|
||||||
const appModeEnable = getAppModeEnable();
|
const appModeEnable = getAppModeEnable();
|
||||||
|
const { isMobile } = useDeviceDetect();
|
||||||
|
|
||||||
const { customStep: value } = useGraphState();
|
const { customStep: value } = useGraphState();
|
||||||
const { period: { step: defaultStep } } = useTimeState();
|
const { period: { step: defaultStep } } = useTimeState();
|
||||||
|
@ -103,6 +106,19 @@ const StepConfigurator: FC = () => {
|
||||||
className="vm-step-control"
|
className="vm-step-control"
|
||||||
ref={buttonRef}
|
ref={buttonRef}
|
||||||
>
|
>
|
||||||
|
{isMobile ? (
|
||||||
|
<div
|
||||||
|
className="vm-mobile-option"
|
||||||
|
onClick={toggleOpenOptions}
|
||||||
|
>
|
||||||
|
<span className="vm-mobile-option__icon"><TimelineIcon/></span>
|
||||||
|
<div className="vm-mobile-option-text">
|
||||||
|
<span className="vm-mobile-option-text__label">Step</span>
|
||||||
|
<span className="vm-mobile-option-text__value">{customStep}</span>
|
||||||
|
</div>
|
||||||
|
<span className="vm-mobile-option__arrow"><ArrowDownIcon/></span>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
<Tooltip title="Query resolution step width">
|
<Tooltip title="Query resolution step width">
|
||||||
<Button
|
<Button
|
||||||
className={appModeEnable ? "" : "vm-header-button"}
|
className={appModeEnable ? "" : "vm-header-button"}
|
||||||
|
@ -119,13 +135,20 @@ const StepConfigurator: FC = () => {
|
||||||
</p>
|
</p>
|
||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
)}
|
||||||
<Popper
|
<Popper
|
||||||
open={openOptions}
|
open={openOptions}
|
||||||
placement="bottom-right"
|
placement="bottom-right"
|
||||||
onClose={handleCloseOptions}
|
onClose={handleCloseOptions}
|
||||||
buttonRef={buttonRef}
|
buttonRef={buttonRef}
|
||||||
|
title={isMobile ? "Query resolution step width" : undefined}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={classNames({
|
||||||
|
"vm-step-control-popper": true,
|
||||||
|
"vm-step-control-popper_mobile": isMobile,
|
||||||
|
})}
|
||||||
>
|
>
|
||||||
<div className="vm-step-control-popper">
|
|
||||||
<TextField
|
<TextField
|
||||||
autofocus
|
autofocus
|
||||||
label="Step value"
|
label="Step value"
|
||||||
|
|
|
@ -11,10 +11,6 @@
|
||||||
&__value {
|
&__value {
|
||||||
display: inline;
|
display: inline;
|
||||||
margin-left: 3px;
|
margin-left: 3px;
|
||||||
|
|
||||||
@media (max-width: 500px) {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&-popper {
|
&-popper {
|
||||||
|
@ -26,6 +22,16 @@
|
||||||
padding: $padding-global;
|
padding: $padding-global;
|
||||||
font-size: $font-size;
|
font-size: $font-size;
|
||||||
|
|
||||||
|
&_mobile {
|
||||||
|
padding: 0 $padding-global $padding-small;
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: calc(($vh * 100) - 70px);
|
||||||
|
}
|
||||||
|
|
||||||
|
&_mobile &-info {
|
||||||
|
font-size: $font-size;
|
||||||
|
}
|
||||||
|
|
||||||
&-info {
|
&-info {
|
||||||
font-size: $font-size-small;
|
font-size: $font-size-small;
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
|
|
|
@ -10,5 +10,6 @@
|
||||||
|
|
||||||
&_mobile &__toggle {
|
&_mobile &__toggle {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
min-width: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,12 +2,12 @@ import React, { FC, useEffect, useRef, useState } from "preact/compat";
|
||||||
import { useTimeDispatch } from "../../../../state/time/TimeStateContext";
|
import { useTimeDispatch } from "../../../../state/time/TimeStateContext";
|
||||||
import { getAppModeEnable } from "../../../../utils/app-mode";
|
import { getAppModeEnable } from "../../../../utils/app-mode";
|
||||||
import Button from "../../../Main/Button/Button";
|
import Button from "../../../Main/Button/Button";
|
||||||
import { ArrowDownIcon, RefreshIcon } from "../../../Main/Icons";
|
import { ArrowDownIcon, RefreshIcon, RestartIcon } from "../../../Main/Icons";
|
||||||
import Popper from "../../../Main/Popper/Popper";
|
import Popper from "../../../Main/Popper/Popper";
|
||||||
import "./style.scss";
|
import "./style.scss";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import Tooltip from "../../../Main/Tooltip/Tooltip";
|
import Tooltip from "../../../Main/Tooltip/Tooltip";
|
||||||
import useResize from "../../../../hooks/useResize";
|
import useDeviceDetect from "../../../../hooks/useDeviceDetect";
|
||||||
|
|
||||||
interface AutoRefreshOption {
|
interface AutoRefreshOption {
|
||||||
seconds: number
|
seconds: number
|
||||||
|
@ -30,7 +30,7 @@ const delayOptions: AutoRefreshOption[] = [
|
||||||
];
|
];
|
||||||
|
|
||||||
export const ExecutionControls: FC = () => {
|
export const ExecutionControls: FC = () => {
|
||||||
const windowSize = useResize(document.body);
|
const { isMobile } = useDeviceDetect();
|
||||||
|
|
||||||
const dispatch = useTimeDispatch();
|
const dispatch = useTimeDispatch();
|
||||||
const appModeEnable = getAppModeEnable();
|
const appModeEnable = getAppModeEnable();
|
||||||
|
@ -85,11 +85,11 @@ export const ExecutionControls: FC = () => {
|
||||||
<div
|
<div
|
||||||
className={classNames({
|
className={classNames({
|
||||||
"vm-execution-controls-buttons": true,
|
"vm-execution-controls-buttons": true,
|
||||||
|
"vm-execution-controls-buttons_mobile": isMobile,
|
||||||
"vm-header-button": !appModeEnable,
|
"vm-header-button": !appModeEnable,
|
||||||
"vm-execution-controls-buttons_short": windowSize.width <= 360
|
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
{windowSize.width > 360 && (
|
{!isMobile && (
|
||||||
<Tooltip title="Refresh dashboard">
|
<Tooltip title="Refresh dashboard">
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
|
@ -99,6 +99,19 @@ export const ExecutionControls: FC = () => {
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
|
{isMobile ? (
|
||||||
|
<div
|
||||||
|
className="vm-mobile-option"
|
||||||
|
onClick={toggleOpenOptions}
|
||||||
|
>
|
||||||
|
<span className="vm-mobile-option__icon"><RestartIcon/></span>
|
||||||
|
<div className="vm-mobile-option-text">
|
||||||
|
<span className="vm-mobile-option-text__label">Auto-refresh</span>
|
||||||
|
<span className="vm-mobile-option-text__value">{selectedDelay.title}</span>
|
||||||
|
</div>
|
||||||
|
<span className="vm-mobile-option__arrow"><ArrowDownIcon/></span>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
<Tooltip title="Auto-refresh control">
|
<Tooltip title="Auto-refresh control">
|
||||||
<div ref={optionsButtonRef}>
|
<div ref={optionsButtonRef}>
|
||||||
<Button
|
<Button
|
||||||
|
@ -121,6 +134,7 @@ export const ExecutionControls: FC = () => {
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Popper
|
<Popper
|
||||||
|
@ -128,12 +142,19 @@ export const ExecutionControls: FC = () => {
|
||||||
placement="bottom-right"
|
placement="bottom-right"
|
||||||
onClose={handleCloseOptions}
|
onClose={handleCloseOptions}
|
||||||
buttonRef={optionsButtonRef}
|
buttonRef={optionsButtonRef}
|
||||||
|
title={isMobile ? "Auto-refresh duration" : undefined}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={classNames({
|
||||||
|
"vm-execution-controls-list": true,
|
||||||
|
"vm-execution-controls-list_mobile": isMobile,
|
||||||
|
})}
|
||||||
>
|
>
|
||||||
<div className="vm-execution-controls-list">
|
|
||||||
{delayOptions.map(d => (
|
{delayOptions.map(d => (
|
||||||
<div
|
<div
|
||||||
className={classNames({
|
className={classNames({
|
||||||
"vm-list-item": true,
|
"vm-list-item": true,
|
||||||
|
"vm-list-item_mobile": isMobile,
|
||||||
"vm-list-item_active": d.seconds === selectedDelay.seconds
|
"vm-list-item_active": d.seconds === selectedDelay.seconds
|
||||||
})}
|
})}
|
||||||
key={d.seconds}
|
key={d.seconds}
|
||||||
|
|
|
@ -9,8 +9,9 @@
|
||||||
border-radius: calc($button-radius + 1px);
|
border-radius: calc($button-radius + 1px);
|
||||||
min-width: 107px;
|
min-width: 107px;
|
||||||
|
|
||||||
&_short {
|
&_mobile {
|
||||||
min-width: auto;
|
flex-direction: column;
|
||||||
|
gap: $padding-medium;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__arrow {
|
&__arrow {
|
||||||
|
@ -32,5 +33,11 @@
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
padding: $padding-small 0;
|
padding: $padding-small 0;
|
||||||
font-size: $font-size;
|
font-size: $font-size;
|
||||||
|
|
||||||
|
&_mobile {
|
||||||
|
width: 100%;
|
||||||
|
max-height: calc(($vh * 100) - 70px);
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ import React, { FC } from "preact/compat";
|
||||||
import { relativeTimeOptions } from "../../../../utils/time";
|
import { relativeTimeOptions } from "../../../../utils/time";
|
||||||
import "./style.scss";
|
import "./style.scss";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
|
import useDeviceDetect from "../../../../hooks/useDeviceDetect";
|
||||||
|
|
||||||
interface TimeDurationSelector {
|
interface TimeDurationSelector {
|
||||||
setDuration: ({ duration, until, id }: {duration: string, until: Date, id: string}) => void;
|
setDuration: ({ duration, until, id }: {duration: string, until: Date, id: string}) => void;
|
||||||
|
@ -9,17 +10,24 @@ interface TimeDurationSelector {
|
||||||
}
|
}
|
||||||
|
|
||||||
const TimeDurationSelector: FC<TimeDurationSelector> = ({ relativeTime, setDuration }) => {
|
const TimeDurationSelector: FC<TimeDurationSelector> = ({ relativeTime, setDuration }) => {
|
||||||
|
const { isMobile } = useDeviceDetect();
|
||||||
|
|
||||||
const createHandlerClick = (value: { duration: string, until: Date, id: string }) => () => {
|
const createHandlerClick = (value: { duration: string, until: Date, id: string }) => () => {
|
||||||
setDuration(value);
|
setDuration(value);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="vm-time-duration">
|
<div
|
||||||
|
className={classNames({
|
||||||
|
"vm-time-duration": true,
|
||||||
|
"vm-time-duration_mobile": isMobile,
|
||||||
|
})}
|
||||||
|
>
|
||||||
{relativeTimeOptions.map(({ id, duration, until, title }) => (
|
{relativeTimeOptions.map(({ id, duration, until, title }) => (
|
||||||
<div
|
<div
|
||||||
className={classNames({
|
className={classNames({
|
||||||
"vm-list-item": true,
|
"vm-list-item": true,
|
||||||
|
"vm-list-item_mobile": isMobile,
|
||||||
"vm-list-item_active": id === relativeTime
|
"vm-list-item_active": id === relativeTime
|
||||||
})}
|
})}
|
||||||
key={id}
|
key={id}
|
||||||
|
|
|
@ -4,4 +4,8 @@
|
||||||
max-height: 200px;
|
max-height: 200px;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
font-size: $font-size;
|
font-size: $font-size;
|
||||||
|
|
||||||
|
&_mobile {
|
||||||
|
max-height: 100%
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ import TimeDurationSelector from "../TimeDurationSelector/TimeDurationSelector";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import { getAppModeEnable } from "../../../../utils/app-mode";
|
import { getAppModeEnable } from "../../../../utils/app-mode";
|
||||||
import { useTimeDispatch, useTimeState } from "../../../../state/time/TimeStateContext";
|
import { useTimeDispatch, useTimeState } from "../../../../state/time/TimeStateContext";
|
||||||
import { AlarmIcon, CalendarIcon, ClockIcon } from "../../../Main/Icons";
|
import { AlarmIcon, ArrowDownIcon, CalendarIcon, ClockIcon } from "../../../Main/Icons";
|
||||||
import Button from "../../../Main/Button/Button";
|
import Button from "../../../Main/Button/Button";
|
||||||
import Popper from "../../../Main/Popper/Popper";
|
import Popper from "../../../Main/Popper/Popper";
|
||||||
import Tooltip from "../../../Main/Tooltip/Tooltip";
|
import Tooltip from "../../../Main/Tooltip/Tooltip";
|
||||||
|
@ -15,8 +15,10 @@ import "./style.scss";
|
||||||
import useClickOutside from "../../../../hooks/useClickOutside";
|
import useClickOutside from "../../../../hooks/useClickOutside";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { useAppState } from "../../../../state/common/StateContext";
|
import { useAppState } from "../../../../state/common/StateContext";
|
||||||
|
import useDeviceDetect from "../../../../hooks/useDeviceDetect";
|
||||||
|
|
||||||
export const TimeSelector: FC = () => {
|
export const TimeSelector: FC = () => {
|
||||||
|
const { isMobile } = useDeviceDetect();
|
||||||
const { isDarkTheme } = useAppState();
|
const { isDarkTheme } = useAppState();
|
||||||
const wrapperRef = useRef<HTMLDivElement>(null);
|
const wrapperRef = useRef<HTMLDivElement>(null);
|
||||||
const documentSize = useResize(document.body);
|
const documentSize = useResize(document.body);
|
||||||
|
@ -112,6 +114,7 @@ export const TimeSelector: FC = () => {
|
||||||
}, [timezone]);
|
}, [timezone]);
|
||||||
|
|
||||||
useClickOutside(wrapperRef, (e) => {
|
useClickOutside(wrapperRef, (e) => {
|
||||||
|
if (isMobile) return;
|
||||||
const target = e.target as HTMLElement;
|
const target = e.target as HTMLElement;
|
||||||
const isFromButton = fromRef?.current && fromRef.current.contains(target);
|
const isFromButton = fromRef?.current && fromRef.current.contains(target);
|
||||||
const isUntilButton = untilRef?.current && untilRef.current.contains(target);
|
const isUntilButton = untilRef?.current && untilRef.current.contains(target);
|
||||||
|
@ -123,6 +126,19 @@ export const TimeSelector: FC = () => {
|
||||||
|
|
||||||
return <>
|
return <>
|
||||||
<div ref={buttonRef}>
|
<div ref={buttonRef}>
|
||||||
|
{isMobile ? (
|
||||||
|
<div
|
||||||
|
className="vm-mobile-option"
|
||||||
|
onClick={toggleOpenOptions}
|
||||||
|
>
|
||||||
|
<span className="vm-mobile-option__icon"><ClockIcon/></span>
|
||||||
|
<div className="vm-mobile-option-text">
|
||||||
|
<span className="vm-mobile-option-text__label">Time range</span>
|
||||||
|
<span className="vm-mobile-option-text__value">{dateTitle}</span>
|
||||||
|
</div>
|
||||||
|
<span className="vm-mobile-option__arrow"><ArrowDownIcon/></span>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
<Tooltip title={displayFullDate ? "Time range controls" : dateTitle}>
|
<Tooltip title={displayFullDate ? "Time range controls" : dateTitle}>
|
||||||
<Button
|
<Button
|
||||||
className={appModeEnable ? "" : "vm-header-button"}
|
className={appModeEnable ? "" : "vm-header-button"}
|
||||||
|
@ -134,6 +150,7 @@ export const TimeSelector: FC = () => {
|
||||||
{displayFullDate && <span>{dateTitle}</span>}
|
{displayFullDate && <span>{dateTitle}</span>}
|
||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<Popper
|
<Popper
|
||||||
open={openOptions}
|
open={openOptions}
|
||||||
|
@ -141,9 +158,13 @@ export const TimeSelector: FC = () => {
|
||||||
placement="bottom-right"
|
placement="bottom-right"
|
||||||
onClose={handleCloseOptions}
|
onClose={handleCloseOptions}
|
||||||
clickOutside={false}
|
clickOutside={false}
|
||||||
|
title={isMobile ? "Time range controls" : ""}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="vm-time-selector"
|
className={classNames({
|
||||||
|
"vm-time-selector": true,
|
||||||
|
"vm-time-selector_mobile": isMobile
|
||||||
|
})}
|
||||||
ref={wrapperRef}
|
ref={wrapperRef}
|
||||||
>
|
>
|
||||||
<div className="vm-time-selector-left">
|
<div className="vm-time-selector-left">
|
||||||
|
@ -161,6 +182,7 @@ export const TimeSelector: FC = () => {
|
||||||
<span>{formFormat}</span>
|
<span>{formFormat}</span>
|
||||||
<CalendarIcon/>
|
<CalendarIcon/>
|
||||||
<DatePicker
|
<DatePicker
|
||||||
|
label={"Date From"}
|
||||||
ref={fromPickerRef}
|
ref={fromPickerRef}
|
||||||
date={from || ""}
|
date={from || ""}
|
||||||
onChange={handleFromChange}
|
onChange={handleFromChange}
|
||||||
|
@ -176,6 +198,7 @@ export const TimeSelector: FC = () => {
|
||||||
<span>{untilFormat}</span>
|
<span>{untilFormat}</span>
|
||||||
<CalendarIcon/>
|
<CalendarIcon/>
|
||||||
<DatePicker
|
<DatePicker
|
||||||
|
label={"Date To"}
|
||||||
ref={untilPickerRef}
|
ref={untilPickerRef}
|
||||||
date={until || ""}
|
date={until || ""}
|
||||||
onChange={handleUntilChange}
|
onChange={handleUntilChange}
|
||||||
|
|
|
@ -5,9 +5,18 @@
|
||||||
grid-template-columns: repeat(2, 230px);
|
grid-template-columns: repeat(2, 230px);
|
||||||
padding: $padding-global 0;
|
padding: $padding-global 0;
|
||||||
|
|
||||||
@media (max-width: 500px) {
|
&_mobile {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
min-width: 250px;
|
min-width: 250px;
|
||||||
|
width: 100%;
|
||||||
|
max-height: calc(($vh * 100) - 70px);
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
&_mobile &-left {
|
||||||
|
border-right: none;
|
||||||
|
border-bottom: $border-divider;
|
||||||
|
padding-bottom: $padding-global;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-left {
|
&-left {
|
||||||
|
@ -17,12 +26,6 @@
|
||||||
border-right: $border-divider;
|
border-right: $border-divider;
|
||||||
padding: 0 $padding-global;
|
padding: 0 $padding-global;
|
||||||
|
|
||||||
@media (max-width: 500px) {
|
|
||||||
border-right: none;
|
|
||||||
border-bottom: $border-divider;
|
|
||||||
padding-bottom: $padding-global;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-inputs {
|
&-inputs {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
display: grid;
|
display: grid;
|
||||||
|
@ -62,6 +65,7 @@
|
||||||
grid-column: 1/3;
|
grid-column: 1/3;
|
||||||
font-size: $font-size-small;
|
font-size: $font-size-small;
|
||||||
color: $color-text-secondary;
|
color: $color-text-secondary;
|
||||||
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
|
|
|
@ -8,6 +8,8 @@ import Spinner from "../../Main/Spinner/Spinner";
|
||||||
import Alert from "../../Main/Alert/Alert";
|
import Alert from "../../Main/Alert/Alert";
|
||||||
import Button from "../../Main/Button/Button";
|
import Button from "../../Main/Button/Button";
|
||||||
import "./style.scss";
|
import "./style.scss";
|
||||||
|
import classNames from "classnames";
|
||||||
|
import useDeviceDetect from "../../../hooks/useDeviceDetect";
|
||||||
|
|
||||||
interface ExploreMetricItemGraphProps {
|
interface ExploreMetricItemGraphProps {
|
||||||
name: string,
|
name: string,
|
||||||
|
@ -26,6 +28,7 @@ const ExploreMetricItem: FC<ExploreMetricItemGraphProps> = ({
|
||||||
isBucket,
|
isBucket,
|
||||||
height
|
height
|
||||||
}) => {
|
}) => {
|
||||||
|
const { isMobile } = useDeviceDetect();
|
||||||
const { customStep, yaxis } = useGraphState();
|
const { customStep, yaxis } = useGraphState();
|
||||||
const { period } = useTimeState();
|
const { period } = useTimeState();
|
||||||
|
|
||||||
|
@ -92,7 +95,12 @@ with (q = ${queryBase}) (
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="vm-explore-metrics-graph">
|
<div
|
||||||
|
className={classNames({
|
||||||
|
"vm-explore-metrics-graph": true,
|
||||||
|
"vm-explore-metrics-graph_mobile": isMobile
|
||||||
|
})}
|
||||||
|
>
|
||||||
{isLoading && <Spinner />}
|
{isLoading && <Spinner />}
|
||||||
{error && <Alert variant="error">{error}</Alert>}
|
{error && <Alert variant="error">{error}</Alert>}
|
||||||
{warning && <Alert variant="warning">
|
{warning && <Alert variant="warning">
|
||||||
|
|
|
@ -3,6 +3,10 @@
|
||||||
.vm-explore-metrics-graph {
|
.vm-explore-metrics-graph {
|
||||||
padding: 0 $padding-global $padding-global;
|
padding: 0 $padding-global $padding-global;
|
||||||
|
|
||||||
|
&_mobile {
|
||||||
|
padding: 0 $padding-global $padding-global;
|
||||||
|
}
|
||||||
|
|
||||||
&__warning {
|
&__warning {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr auto;
|
grid-template-columns: 1fr auto;
|
||||||
|
|
|
@ -10,6 +10,7 @@ interface ExploreMetricItemProps {
|
||||||
job: string
|
job: string
|
||||||
instance: string
|
instance: string
|
||||||
index: number
|
index: number
|
||||||
|
length: number
|
||||||
size: GraphSize
|
size: GraphSize
|
||||||
onRemoveItem: (name: string) => void
|
onRemoveItem: (name: string) => void
|
||||||
onChangeOrder: (name: string, oldIndex: number, newIndex: number) => void
|
onChangeOrder: (name: string, oldIndex: number, newIndex: number) => void
|
||||||
|
@ -20,6 +21,7 @@ const ExploreMetricItem: FC<ExploreMetricItemProps> = ({
|
||||||
job,
|
job,
|
||||||
instance,
|
instance,
|
||||||
index,
|
index,
|
||||||
|
length,
|
||||||
size,
|
size,
|
||||||
onRemoveItem,
|
onRemoveItem,
|
||||||
onChangeOrder,
|
onChangeOrder,
|
||||||
|
@ -42,6 +44,7 @@ const ExploreMetricItem: FC<ExploreMetricItemProps> = ({
|
||||||
<ExploreMetricItemHeader
|
<ExploreMetricItemHeader
|
||||||
name={name}
|
name={name}
|
||||||
index={index}
|
index={index}
|
||||||
|
length={length}
|
||||||
isBucket={isBucket}
|
isBucket={isBucket}
|
||||||
rateEnabled={rateEnabled}
|
rateEnabled={rateEnabled}
|
||||||
size={size.id}
|
size={size.id}
|
||||||
|
|
|
@ -1,13 +1,16 @@
|
||||||
import React, { FC } from "preact/compat";
|
import React, { FC, useState } from "preact/compat";
|
||||||
import "./style.scss";
|
import "./style.scss";
|
||||||
import Switch from "../../Main/Switch/Switch";
|
import Switch from "../../Main/Switch/Switch";
|
||||||
import Tooltip from "../../Main/Tooltip/Tooltip";
|
import Tooltip from "../../Main/Tooltip/Tooltip";
|
||||||
import Button from "../../Main/Button/Button";
|
import Button from "../../Main/Button/Button";
|
||||||
import { ArrowDownIcon, CloseIcon } from "../../Main/Icons";
|
import { ArrowDownIcon, CloseIcon, MinusIcon, MoreIcon, PlusIcon } from "../../Main/Icons";
|
||||||
|
import useDeviceDetect from "../../../hooks/useDeviceDetect";
|
||||||
|
import Modal from "../../Main/Modal/Modal";
|
||||||
|
|
||||||
interface ExploreMetricItemControlsProps {
|
interface ExploreMetricItemControlsProps {
|
||||||
name: string
|
name: string
|
||||||
index: number
|
index: number
|
||||||
|
length: number
|
||||||
isBucket: boolean
|
isBucket: boolean
|
||||||
rateEnabled: boolean
|
rateEnabled: boolean
|
||||||
size: string
|
size: string
|
||||||
|
@ -19,12 +22,15 @@ interface ExploreMetricItemControlsProps {
|
||||||
const ExploreMetricItemHeader: FC<ExploreMetricItemControlsProps> = ({
|
const ExploreMetricItemHeader: FC<ExploreMetricItemControlsProps> = ({
|
||||||
name,
|
name,
|
||||||
index,
|
index,
|
||||||
|
length,
|
||||||
isBucket,
|
isBucket,
|
||||||
rateEnabled,
|
rateEnabled,
|
||||||
onChangeRate,
|
onChangeRate,
|
||||||
onRemoveItem,
|
onRemoveItem,
|
||||||
onChangeOrder,
|
onChangeOrder,
|
||||||
}) => {
|
}) => {
|
||||||
|
const { isMobile } = useDeviceDetect();
|
||||||
|
const [openOptions, setOpenOptions] = useState(false);
|
||||||
|
|
||||||
const handleClickRemove = () => {
|
const handleClickRemove = () => {
|
||||||
onRemoveItem(name);
|
onRemoveItem(name);
|
||||||
|
@ -38,6 +44,76 @@ const ExploreMetricItemHeader: FC<ExploreMetricItemControlsProps> = ({
|
||||||
onChangeOrder(name, index, index - 1);
|
onChangeOrder(name, index, index - 1);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleOpenOptions = () => {
|
||||||
|
setOpenOptions(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCloseOptions = () => {
|
||||||
|
setOpenOptions(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isMobile) {
|
||||||
|
return (
|
||||||
|
<div className="vm-explore-metrics-item-header vm-explore-metrics-item-header_mobile">
|
||||||
|
<div className="vm-explore-metrics-item-header__name">{name}</div>
|
||||||
|
<Button
|
||||||
|
variant="text"
|
||||||
|
size="small"
|
||||||
|
startIcon={<MoreIcon/>}
|
||||||
|
onClick={handleOpenOptions}
|
||||||
|
/>
|
||||||
|
{openOptions && (
|
||||||
|
<Modal
|
||||||
|
title={name}
|
||||||
|
onClose={handleCloseOptions}
|
||||||
|
>
|
||||||
|
<div className="vm-explore-metrics-item-header-modal">
|
||||||
|
<div className="vm-explore-metrics-item-header-modal-order">
|
||||||
|
<Button
|
||||||
|
startIcon={<MinusIcon/>}
|
||||||
|
variant="outlined"
|
||||||
|
onClick={handleOrderUp}
|
||||||
|
disabled={index === 0}
|
||||||
|
/>
|
||||||
|
<p>position:
|
||||||
|
<span className="vm-explore-metrics-item-header-modal-order__index">#{index + 1}</span>
|
||||||
|
</p>
|
||||||
|
<Button
|
||||||
|
endIcon={<PlusIcon/>}
|
||||||
|
variant="outlined"
|
||||||
|
onClick={handleOrderDown}
|
||||||
|
disabled={index === length - 1}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{!isBucket && (
|
||||||
|
<div className="vm-explore-metrics-item-header-modal__rate">
|
||||||
|
<Switch
|
||||||
|
label={<span>enable <code>rate()</code></span>}
|
||||||
|
value={rateEnabled}
|
||||||
|
onChange={onChangeRate}
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
<p>
|
||||||
|
calculates the average per-second speed of metrics change
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<Button
|
||||||
|
startIcon={<CloseIcon/>}
|
||||||
|
color="error"
|
||||||
|
variant="outlined"
|
||||||
|
onClick={handleClickRemove}
|
||||||
|
fullWidth
|
||||||
|
>
|
||||||
|
Remove graph
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="vm-explore-metrics-item-header">
|
<div className="vm-explore-metrics-item-header">
|
||||||
<div className="vm-explore-metrics-item-header-order">
|
<div className="vm-explore-metrics-item-header-order">
|
||||||
|
@ -65,6 +141,7 @@ const ExploreMetricItemHeader: FC<ExploreMetricItemControlsProps> = ({
|
||||||
</div>
|
</div>
|
||||||
<div className="vm-explore-metrics-item-header__name">{name}</div>
|
<div className="vm-explore-metrics-item-header__name">{name}</div>
|
||||||
{!isBucket && (
|
{!isBucket && (
|
||||||
|
<div className="vm-explore-metrics-item-header__rate">
|
||||||
<Tooltip title="calculates the average per-second speed of metric's change">
|
<Tooltip title="calculates the average per-second speed of metric's change">
|
||||||
<Switch
|
<Switch
|
||||||
label={<span>enable <code>rate()</code></span>}
|
label={<span>enable <code>rate()</code></span>}
|
||||||
|
@ -72,8 +149,9 @@ const ExploreMetricItemHeader: FC<ExploreMetricItemControlsProps> = ({
|
||||||
onChange={onChangeRate}
|
onChange={onChangeRate}
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="vm-explore-metrics-item-header__layout">
|
<div className="vm-explore-metrics-item-header__close">
|
||||||
<Tooltip title="close graph">
|
<Tooltip title="close graph">
|
||||||
<Button
|
<Button
|
||||||
startIcon={<CloseIcon/>}
|
startIcon={<CloseIcon/>}
|
||||||
|
|
|
@ -1,14 +1,19 @@
|
||||||
@use "src/styles/variables" as *;
|
@use "src/styles/variables" as *;
|
||||||
|
|
||||||
.vm-explore-metrics-item-header {
|
.vm-explore-metrics-item-header {
|
||||||
display: flex;
|
display: grid;
|
||||||
flex-wrap: wrap;
|
grid-template-columns: auto 1fr auto auto;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
padding: $padding-global;
|
padding: $padding-global;
|
||||||
border-bottom: $border-divider;
|
border-bottom: $border-divider;
|
||||||
gap: $padding-global;
|
gap: $padding-global;
|
||||||
|
|
||||||
|
&_mobile {
|
||||||
|
grid-template-columns: 1fr auto;
|
||||||
|
padding: $padding-small $padding-global;
|
||||||
|
}
|
||||||
|
|
||||||
&__index {
|
&__index {
|
||||||
color: $color-text-secondary;
|
color: $color-text-secondary;
|
||||||
font-size: $font-size-small;
|
font-size: $font-size-small;
|
||||||
|
@ -17,9 +22,14 @@
|
||||||
&__name {
|
&__name {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
max-width: 100%;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
line-height: 130%;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-order {
|
&-order {
|
||||||
|
grid-column: 1;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: auto 20px auto;
|
grid-template-columns: auto 20px auto;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
@ -31,7 +41,13 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__layout {
|
&__rate {
|
||||||
|
grid-column: 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__close {
|
||||||
|
grid-row: 1;
|
||||||
|
grid-column: 4;
|
||||||
display: grid;
|
display: grid;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
@ -42,4 +58,35 @@
|
||||||
background-color: $color-hover-black;
|
background-color: $color-hover-black;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&-modal {
|
||||||
|
display: grid;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: $padding-medium;
|
||||||
|
|
||||||
|
&-order {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: $padding-medium;
|
||||||
|
|
||||||
|
p {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__index {
|
||||||
|
margin-left: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__rate {
|
||||||
|
display: grid;
|
||||||
|
gap: $padding-small;
|
||||||
|
|
||||||
|
p {
|
||||||
|
color: $color-text-secondary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,8 @@ import React, { FC, useMemo } from "preact/compat";
|
||||||
import Select from "../../Main/Select/Select";
|
import Select from "../../Main/Select/Select";
|
||||||
import "./style.scss";
|
import "./style.scss";
|
||||||
import { GRAPH_SIZES } from "../../../constants/graph";
|
import { GRAPH_SIZES } from "../../../constants/graph";
|
||||||
|
import classNames from "classnames";
|
||||||
|
import useDeviceDetect from "../../../hooks/useDeviceDetect";
|
||||||
|
|
||||||
interface ExploreMetricsHeaderProps {
|
interface ExploreMetricsHeaderProps {
|
||||||
jobs: string[]
|
jobs: string[]
|
||||||
|
@ -34,9 +36,17 @@ const ExploreMetricsHeader: FC<ExploreMetricsHeaderProps> = ({
|
||||||
}) => {
|
}) => {
|
||||||
const noInstanceText = useMemo(() => job ? "" : "No instances. Please select job", [job]);
|
const noInstanceText = useMemo(() => job ? "" : "No instances. Please select job", [job]);
|
||||||
const noMetricsText = useMemo(() => job ? "" : "No metric names. Please select job", [job]);
|
const noMetricsText = useMemo(() => job ? "" : "No metric names. Please select job", [job]);
|
||||||
|
const { isMobile } = useDeviceDetect();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="vm-explore-metrics-header vm-block">
|
<div
|
||||||
|
className={classNames({
|
||||||
|
"vm-explore-metrics-header": true,
|
||||||
|
"vm-explore-metrics-header_mobile": isMobile,
|
||||||
|
"vm-block": true,
|
||||||
|
"vm-block_mobile": isMobile,
|
||||||
|
})}
|
||||||
|
>
|
||||||
<div className="vm-explore-metrics-header__job">
|
<div className="vm-explore-metrics-header__job">
|
||||||
<Select
|
<Select
|
||||||
value={job}
|
value={job}
|
||||||
|
@ -45,6 +55,7 @@ const ExploreMetricsHeader: FC<ExploreMetricsHeaderProps> = ({
|
||||||
placeholder="Please select job"
|
placeholder="Please select job"
|
||||||
onChange={onChangeJob}
|
onChange={onChangeJob}
|
||||||
autofocus={!job}
|
autofocus={!job}
|
||||||
|
searchable
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="vm-explore-metrics-header__instance">
|
<div className="vm-explore-metrics-header__instance">
|
||||||
|
@ -56,6 +67,7 @@ const ExploreMetricsHeader: FC<ExploreMetricsHeaderProps> = ({
|
||||||
onChange={onChangeInstance}
|
onChange={onChangeInstance}
|
||||||
noOptionsText={noInstanceText}
|
noOptionsText={noInstanceText}
|
||||||
clearable
|
clearable
|
||||||
|
searchable
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="vm-explore-metrics-header__size">
|
<div className="vm-explore-metrics-header__size">
|
||||||
|
@ -68,12 +80,14 @@ const ExploreMetricsHeader: FC<ExploreMetricsHeaderProps> = ({
|
||||||
</div>
|
</div>
|
||||||
<div className="vm-explore-metrics-header-metrics">
|
<div className="vm-explore-metrics-header-metrics">
|
||||||
<Select
|
<Select
|
||||||
|
label={"Metrics"}
|
||||||
value={selectedMetrics}
|
value={selectedMetrics}
|
||||||
list={names}
|
list={names}
|
||||||
placeholder="Search metric name"
|
placeholder="Search metric name"
|
||||||
onChange={onToggleMetric}
|
onChange={onToggleMetric}
|
||||||
noOptionsText={noMetricsText}
|
noOptionsText={noMetricsText}
|
||||||
clearable
|
clearable
|
||||||
|
searchable
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -6,17 +6,26 @@
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
gap: $padding-global calc($padding-small + 10px);
|
gap: $padding-global calc($padding-small + 10px);
|
||||||
|
max-width: calc(100vw - var(--scrollbar-width));
|
||||||
|
|
||||||
|
&_mobile {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
&__job {
|
&__job {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
|
min-width: 150px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__instance {
|
&__instance {
|
||||||
flex-grow: 2;
|
flex-grow: 2;
|
||||||
|
min-width: 150px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__size {
|
&__size {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
|
min-width: 150px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-metrics {
|
&-metrics {
|
||||||
|
@ -35,5 +44,4 @@
|
||||||
opacity: 0.7
|
opacity: 0.7
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,10 @@ import React, { FC } from "preact/compat";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import "./style.scss";
|
import "./style.scss";
|
||||||
import { IssueIcon, LogoIcon, WikiIcon } from "../../Main/Icons";
|
import { IssueIcon, LogoIcon, WikiIcon } from "../../Main/Icons";
|
||||||
|
import useDeviceDetect from "../../../hooks/useDeviceDetect";
|
||||||
|
|
||||||
const Footer: FC = () => {
|
const Footer: FC = () => {
|
||||||
|
const { isMobile } = useDeviceDetect();
|
||||||
const copyrightYears = `2019-${dayjs().format("YYYY")}`;
|
const copyrightYears = `2019-${dayjs().format("YYYY")}`;
|
||||||
|
|
||||||
return <footer className="vm-footer">
|
return <footer className="vm-footer">
|
||||||
|
@ -23,7 +25,7 @@ const Footer: FC = () => {
|
||||||
rel="help noreferrer"
|
rel="help noreferrer"
|
||||||
>
|
>
|
||||||
<WikiIcon/>
|
<WikiIcon/>
|
||||||
Documentation
|
{isMobile ? "Docs" : "Documentation"}
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
className="vm-link vm-footer__link"
|
className="vm-link vm-footer__link"
|
||||||
|
@ -32,7 +34,7 @@ const Footer: FC = () => {
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
>
|
>
|
||||||
<IssueIcon/>
|
<IssueIcon/>
|
||||||
Create an issue
|
{isMobile ? "New issue" : "Create an issue"}
|
||||||
</a>
|
</a>
|
||||||
<div className="vm-footer__copyright">
|
<div className="vm-footer__copyright">
|
||||||
© {copyrightYears} VictoriaMetrics
|
© {copyrightYears} VictoriaMetrics
|
||||||
|
|
|
@ -11,6 +11,11 @@
|
||||||
color: $color-text-secondary;
|
color: $color-text-secondary;
|
||||||
background: $color-background-body;
|
background: $color-background-body;
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
padding: $padding-global;
|
||||||
|
gap: $padding-global;
|
||||||
|
}
|
||||||
|
|
||||||
&__link,
|
&__link,
|
||||||
&__website {
|
&__website {
|
||||||
display: grid;
|
display: grid;
|
||||||
|
@ -25,7 +30,6 @@
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
width: 100%;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,6 +43,7 @@
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
font-size: $font-size-small;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,31 +1,26 @@
|
||||||
import React, { FC, useMemo } from "preact/compat";
|
import React, { FC, useMemo } from "preact/compat";
|
||||||
import { ExecutionControls } from "../../Configurators/TimeRangeSettings/ExecutionControls/ExecutionControls";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { TimeSelector } from "../../Configurators/TimeRangeSettings/TimeSelector/TimeSelector";
|
import router from "../../../router";
|
||||||
import GlobalSettings from "../../Configurators/GlobalSettings/GlobalSettings";
|
|
||||||
import { useLocation, useNavigate } from "react-router-dom";
|
|
||||||
import router, { RouterOptions, routerOptions } from "../../../router";
|
|
||||||
import ShortcutKeys from "../../Main/ShortcutKeys/ShortcutKeys";
|
|
||||||
import { getAppModeEnable, getAppModeParams } from "../../../utils/app-mode";
|
import { getAppModeEnable, getAppModeParams } from "../../../utils/app-mode";
|
||||||
import CardinalityDatePicker from "../../Configurators/CardinalityDatePicker/CardinalityDatePicker";
|
|
||||||
import { LogoFullIcon } from "../../Main/Icons";
|
import { LogoFullIcon } from "../../Main/Icons";
|
||||||
import { getCssVariable } from "../../../utils/theme";
|
import { getCssVariable } from "../../../utils/theme";
|
||||||
import "./style.scss";
|
import "./style.scss";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import StepConfigurator from "../../Configurators/StepConfigurator/StepConfigurator";
|
|
||||||
import { useAppState } from "../../../state/common/StateContext";
|
import { useAppState } from "../../../state/common/StateContext";
|
||||||
import HeaderNav from "./HeaderNav/HeaderNav";
|
import HeaderNav from "./HeaderNav/HeaderNav";
|
||||||
import TenantsConfiguration from "../../Configurators/GlobalSettings/TenantsConfiguration/TenantsConfiguration";
|
|
||||||
import { useFetchAccountIds } from "../../Configurators/GlobalSettings/TenantsConfiguration/hooks/useFetchAccountIds";
|
|
||||||
import useResize from "../../../hooks/useResize";
|
import useResize from "../../../hooks/useResize";
|
||||||
import SidebarHeader from "./SidebarNav/SidebarHeader";
|
import SidebarHeader from "./SidebarNav/SidebarHeader";
|
||||||
|
import HeaderControls from "./HeaderControls/HeaderControls";
|
||||||
|
import useDeviceDetect from "../../../hooks/useDeviceDetect";
|
||||||
|
|
||||||
const Header: FC = () => {
|
const Header: FC = () => {
|
||||||
|
const { isMobile } = useDeviceDetect();
|
||||||
|
|
||||||
const windowSize = useResize(document.body);
|
const windowSize = useResize(document.body);
|
||||||
const displaySidebar = useMemo(() => window.innerWidth < 1000, [windowSize]);
|
const displaySidebar = useMemo(() => window.innerWidth < 1000, [windowSize]);
|
||||||
|
|
||||||
const { isDarkTheme } = useAppState();
|
const { isDarkTheme } = useAppState();
|
||||||
const appModeEnable = getAppModeEnable();
|
const appModeEnable = getAppModeEnable();
|
||||||
const { accountIds } = useFetchAccountIds();
|
|
||||||
|
|
||||||
const primaryColor = useMemo(() => {
|
const primaryColor = useMemo(() => {
|
||||||
const variable = isDarkTheme ? "color-background-block" : "color-primary";
|
const variable = isDarkTheme ? "color-background-block" : "color-primary";
|
||||||
|
@ -42,11 +37,6 @@ const Header: FC = () => {
|
||||||
}, [primaryColor]);
|
}, [primaryColor]);
|
||||||
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { pathname } = useLocation();
|
|
||||||
|
|
||||||
const headerSetup = useMemo(() => {
|
|
||||||
return ((routerOptions[pathname] || {}) as RouterOptions).header || {};
|
|
||||||
}, [pathname]);
|
|
||||||
|
|
||||||
const onClickLogo = () => {
|
const onClickLogo = () => {
|
||||||
navigate({ pathname: router.home });
|
navigate({ pathname: router.home });
|
||||||
|
@ -57,7 +47,8 @@ const Header: FC = () => {
|
||||||
className={classNames({
|
className={classNames({
|
||||||
"vm-header": true,
|
"vm-header": true,
|
||||||
"vm-header_app": appModeEnable,
|
"vm-header_app": appModeEnable,
|
||||||
"vm-header_dark": isDarkTheme
|
"vm-header_dark": isDarkTheme,
|
||||||
|
"vm-header_mobile": isMobile
|
||||||
})}
|
})}
|
||||||
style={{ background, color }}
|
style={{ background, color }}
|
||||||
>
|
>
|
||||||
|
@ -65,7 +56,6 @@ const Header: FC = () => {
|
||||||
<SidebarHeader
|
<SidebarHeader
|
||||||
background={background}
|
background={background}
|
||||||
color={color}
|
color={color}
|
||||||
onClickLogo={onClickLogo}
|
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
|
@ -84,15 +74,19 @@ const Header: FC = () => {
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<div className="vm-header__settings">
|
{isMobile && (
|
||||||
{headerSetup?.tenant && <TenantsConfiguration accountIds={accountIds}/>}
|
<div
|
||||||
{headerSetup?.stepControl && <StepConfigurator/>}
|
className="vm-header-logo vm-header-logo_mobile"
|
||||||
{headerSetup?.timeSelector && <TimeSelector/>}
|
onClick={onClickLogo}
|
||||||
{headerSetup?.cardinalityDatePicker && <CardinalityDatePicker/>}
|
style={{ color }}
|
||||||
{headerSetup?.executionControls && <ExecutionControls/>}
|
>
|
||||||
{!displaySidebar && <GlobalSettings/>}
|
<LogoFullIcon/>
|
||||||
{!displaySidebar && <ShortcutKeys/>}
|
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
<HeaderControls
|
||||||
|
displaySidebar={displaySidebar}
|
||||||
|
isMobile={isMobile}
|
||||||
|
/>
|
||||||
</header>;
|
</header>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,107 @@
|
||||||
|
import React, { FC, useMemo, useState } from "preact/compat";
|
||||||
|
import { RouterOptions, routerOptions, RouterOptionsHeader } from "../../../../router";
|
||||||
|
import TenantsConfiguration from "../../../Configurators/GlobalSettings/TenantsConfiguration/TenantsConfiguration";
|
||||||
|
import StepConfigurator from "../../../Configurators/StepConfigurator/StepConfigurator";
|
||||||
|
import { TimeSelector } from "../../../Configurators/TimeRangeSettings/TimeSelector/TimeSelector";
|
||||||
|
import CardinalityDatePicker from "../../../Configurators/CardinalityDatePicker/CardinalityDatePicker";
|
||||||
|
import { ExecutionControls } from "../../../Configurators/TimeRangeSettings/ExecutionControls/ExecutionControls";
|
||||||
|
import GlobalSettings from "../../../Configurators/GlobalSettings/GlobalSettings";
|
||||||
|
import ShortcutKeys from "../../../Main/ShortcutKeys/ShortcutKeys";
|
||||||
|
import { useLocation } from "react-router-dom";
|
||||||
|
import { useFetchAccountIds } from "../../../Configurators/GlobalSettings/TenantsConfiguration/hooks/useFetchAccountIds";
|
||||||
|
import Button from "../../../Main/Button/Button";
|
||||||
|
import { MoreIcon } from "../../../Main/Icons";
|
||||||
|
import "./style.scss";
|
||||||
|
import classNames from "classnames";
|
||||||
|
import { getAppModeEnable } from "../../../../utils/app-mode";
|
||||||
|
import Modal from "../../../Main/Modal/Modal";
|
||||||
|
|
||||||
|
interface HeaderControlsProp {
|
||||||
|
displaySidebar: boolean
|
||||||
|
isMobile?: boolean
|
||||||
|
headerSetup?: RouterOptionsHeader
|
||||||
|
accountIds?: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const Controls: FC<HeaderControlsProp> = ({
|
||||||
|
displaySidebar,
|
||||||
|
isMobile,
|
||||||
|
headerSetup,
|
||||||
|
accountIds
|
||||||
|
}) => {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={classNames({
|
||||||
|
"vm-header-controls": true,
|
||||||
|
"vm-header-controls_mobile": isMobile,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{headerSetup?.tenant && <TenantsConfiguration accountIds={accountIds || []}/>}
|
||||||
|
{headerSetup?.stepControl && <StepConfigurator/>}
|
||||||
|
{headerSetup?.timeSelector && <TimeSelector/>}
|
||||||
|
{headerSetup?.cardinalityDatePicker && <CardinalityDatePicker/>}
|
||||||
|
{headerSetup?.executionControls && <ExecutionControls/>}
|
||||||
|
<GlobalSettings/>
|
||||||
|
{!displaySidebar && <ShortcutKeys/>}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const HeaderControls: FC<HeaderControlsProp> = (props) => {
|
||||||
|
const appModeEnable = getAppModeEnable();
|
||||||
|
const [openList, setOpenList] = useState(false);
|
||||||
|
const { pathname } = useLocation();
|
||||||
|
const { accountIds } = useFetchAccountIds();
|
||||||
|
|
||||||
|
const headerSetup = useMemo(() => {
|
||||||
|
return ((routerOptions[pathname] || {}) as RouterOptions).header || {};
|
||||||
|
}, [pathname]);
|
||||||
|
|
||||||
|
const handleToggleList = () => {
|
||||||
|
setOpenList(prev => !prev);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCloseList = () => {
|
||||||
|
setOpenList(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (props.isMobile) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div>
|
||||||
|
<Button
|
||||||
|
className={classNames({
|
||||||
|
"vm-header-button": !appModeEnable
|
||||||
|
})}
|
||||||
|
startIcon={<MoreIcon/>}
|
||||||
|
onClick={handleToggleList}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Modal
|
||||||
|
title={"Controls"}
|
||||||
|
onClose={handleCloseList}
|
||||||
|
isOpen={openList}
|
||||||
|
className={classNames({
|
||||||
|
"vm-header-controls-modal": true,
|
||||||
|
"vm-header-controls-modal_open": openList,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<Controls
|
||||||
|
{...props}
|
||||||
|
accountIds={accountIds}
|
||||||
|
headerSetup={headerSetup}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <Controls
|
||||||
|
{...props}
|
||||||
|
accountIds={accountIds}
|
||||||
|
headerSetup={headerSetup}
|
||||||
|
/>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default HeaderControls;
|
|
@ -0,0 +1,27 @@
|
||||||
|
@use "src/styles/variables" as *;
|
||||||
|
|
||||||
|
.vm-header-controls {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: $padding-small;
|
||||||
|
flex-grow: 1;
|
||||||
|
|
||||||
|
&_mobile {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
.vm-header-button {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-modal {
|
||||||
|
transform: scale(0);
|
||||||
|
|
||||||
|
&_open {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,8 +1,6 @@
|
||||||
import React, { FC, useEffect, useRef, useState } from "preact/compat";
|
import React, { FC, useEffect, useRef, useState } from "preact/compat";
|
||||||
import GlobalSettings from "../../../Configurators/GlobalSettings/GlobalSettings";
|
|
||||||
import { useLocation } from "react-router-dom";
|
import { useLocation } from "react-router-dom";
|
||||||
import ShortcutKeys from "../../../Main/ShortcutKeys/ShortcutKeys";
|
import ShortcutKeys from "../../../Main/ShortcutKeys/ShortcutKeys";
|
||||||
import { LogoFullIcon } from "../../../Main/Icons";
|
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import HeaderNav from "../HeaderNav/HeaderNav";
|
import HeaderNav from "../HeaderNav/HeaderNav";
|
||||||
import useClickOutside from "../../../../hooks/useClickOutside";
|
import useClickOutside from "../../../../hooks/useClickOutside";
|
||||||
|
@ -13,13 +11,11 @@ import "./style.scss";
|
||||||
interface SidebarHeaderProps {
|
interface SidebarHeaderProps {
|
||||||
background: string
|
background: string
|
||||||
color: string
|
color: string
|
||||||
onClickLogo: () => void
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const SidebarHeader: FC<SidebarHeaderProps> = ({
|
const SidebarHeader: FC<SidebarHeaderProps> = ({
|
||||||
background,
|
background,
|
||||||
color,
|
color,
|
||||||
onClickLogo,
|
|
||||||
}) => {
|
}) => {
|
||||||
const { pathname } = useLocation();
|
const { pathname } = useLocation();
|
||||||
const { isMobile } = useDeviceDetect();
|
const { isMobile } = useDeviceDetect();
|
||||||
|
@ -48,11 +44,9 @@ const SidebarHeader: FC<SidebarHeaderProps> = ({
|
||||||
"vm-header-sidebar-button": true,
|
"vm-header-sidebar-button": true,
|
||||||
"vm-header-sidebar-button_open": openMenu
|
"vm-header-sidebar-button_open": openMenu
|
||||||
})}
|
})}
|
||||||
>
|
|
||||||
<MenuBurger
|
|
||||||
open={openMenu}
|
|
||||||
onClick={handleToggleMenu}
|
onClick={handleToggleMenu}
|
||||||
/>
|
>
|
||||||
|
<MenuBurger open={openMenu}/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className={classNames({
|
className={classNames({
|
||||||
|
@ -60,13 +54,6 @@ const SidebarHeader: FC<SidebarHeaderProps> = ({
|
||||||
"vm-header-sidebar-menu_open": openMenu
|
"vm-header-sidebar-menu_open": openMenu
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<div
|
|
||||||
className="vm-header-sidebar-menu__logo"
|
|
||||||
onClick={onClickLogo}
|
|
||||||
style={{ color }}
|
|
||||||
>
|
|
||||||
<LogoFullIcon/>
|
|
||||||
</div>
|
|
||||||
<div>
|
<div>
|
||||||
<HeaderNav
|
<HeaderNav
|
||||||
color={color}
|
color={color}
|
||||||
|
@ -75,7 +62,6 @@ const SidebarHeader: FC<SidebarHeaderProps> = ({
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="vm-header-sidebar-menu-settings">
|
<div className="vm-header-sidebar-menu-settings">
|
||||||
<GlobalSettings showTitle={true}/>
|
|
||||||
{!isMobile && <ShortcutKeys showTitle={true}/>}
|
{!isMobile && <ShortcutKeys showTitle={true}/>}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
@use "src/styles/variables" as *;
|
@use "src/styles/variables" as *;
|
||||||
|
|
||||||
|
$sidebar-transition: cubic-bezier(0.280, 0.840, 0.420, 1);
|
||||||
|
|
||||||
.vm-header-sidebar {
|
.vm-header-sidebar {
|
||||||
width: 24px;
|
width: 24px;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
|
@ -7,14 +9,19 @@
|
||||||
background-color: inherit;
|
background-color: inherit;
|
||||||
|
|
||||||
&-button {
|
&-button {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: $padding-global;
|
left: 0;
|
||||||
top: $padding-global;
|
top: 0;
|
||||||
transition: left 300ms cubic-bezier(0.280, 0.840, 0.420, 1);
|
height: 51px;
|
||||||
|
width: 51px;
|
||||||
|
transition: left 350ms $sidebar-transition;
|
||||||
|
|
||||||
&_open {
|
&_open {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
left: calc(182px - $padding-global);
|
left: 149px;
|
||||||
z-index: 102;
|
z-index: 102;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,14 +33,14 @@
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: $padding-global;
|
gap: $padding-global;
|
||||||
padding: $padding-global;
|
padding: $padding-global;
|
||||||
grid-template-rows: auto 1fr auto;
|
grid-template-rows: 1fr auto;
|
||||||
width: 200px;
|
width: 200px;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background-color: inherit;
|
background-color: inherit;
|
||||||
z-index: 101;
|
z-index: 101;
|
||||||
transform-origin: left;
|
transform-origin: left;
|
||||||
transform: translateX(-100%);
|
transform: translateX(-100%);
|
||||||
transition: transform 300ms cubic-bezier(0.280, 0.840, 0.420, 1);
|
transition: transform 300ms $sidebar-transition;
|
||||||
box-shadow: $box-shadow-popper;
|
box-shadow: $box-shadow-popper;
|
||||||
|
|
||||||
&_open {
|
&_open {
|
||||||
|
|
|
@ -21,6 +21,12 @@
|
||||||
padding: $padding-small;
|
padding: $padding-small;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&_mobile {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 33px 1fr 33px;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
&_dark {
|
&_dark {
|
||||||
.vm-header-button,
|
.vm-header-button,
|
||||||
button:before,
|
button:before,
|
||||||
|
@ -50,18 +56,16 @@
|
||||||
max-width: 65px;
|
max-width: 65px;
|
||||||
min-width: 65px;
|
min-width: 65px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&_mobile {
|
||||||
|
max-width: 65px;
|
||||||
|
min-width: 65px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&-nav {
|
&-nav {
|
||||||
font-size: $font-size-small;
|
font-size: $font-size-small;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__settings {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: flex-end;
|
|
||||||
gap: $padding-small;
|
|
||||||
flex-grow: 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,9 +7,11 @@ import classNames from "classnames";
|
||||||
import Footer from "./Footer/Footer";
|
import Footer from "./Footer/Footer";
|
||||||
import { routerOptions } from "../../router";
|
import { routerOptions } from "../../router";
|
||||||
import { useFetchDashboards } from "../../pages/PredefinedPanels/hooks/useFetchDashboards";
|
import { useFetchDashboards } from "../../pages/PredefinedPanels/hooks/useFetchDashboards";
|
||||||
|
import useDeviceDetect from "../../hooks/useDeviceDetect";
|
||||||
|
|
||||||
const Layout: FC = () => {
|
const Layout: FC = () => {
|
||||||
const appModeEnable = getAppModeEnable();
|
const appModeEnable = getAppModeEnable();
|
||||||
|
const { isMobile } = useDeviceDetect();
|
||||||
useFetchDashboards();
|
useFetchDashboards();
|
||||||
|
|
||||||
const { pathname } = useLocation();
|
const { pathname } = useLocation();
|
||||||
|
@ -24,6 +26,7 @@ const Layout: FC = () => {
|
||||||
<div
|
<div
|
||||||
className={classNames({
|
className={classNames({
|
||||||
"vm-container-body": true,
|
"vm-container-body": true,
|
||||||
|
"vm-container-body_mobile": isMobile,
|
||||||
"vm-container-body_app": appModeEnable
|
"vm-container-body_app": appModeEnable
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
|
|
|
@ -11,8 +11,12 @@
|
||||||
padding: $padding-medium;
|
padding: $padding-medium;
|
||||||
background-color: $color-background-body;
|
background-color: $color-background-body;
|
||||||
|
|
||||||
|
&_mobile {
|
||||||
|
padding: $padding-small 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
padding: 0;
|
padding: $padding-small 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
&_app {
|
&_app {
|
||||||
|
|
|
@ -4,6 +4,7 @@ import classNames from "classnames";
|
||||||
import { ErrorIcon, InfoIcon, SuccessIcon, WarningIcon } from "../Icons";
|
import { ErrorIcon, InfoIcon, SuccessIcon, WarningIcon } from "../Icons";
|
||||||
import "./style.scss";
|
import "./style.scss";
|
||||||
import { useAppState } from "../../../state/common/StateContext";
|
import { useAppState } from "../../../state/common/StateContext";
|
||||||
|
import useDeviceDetect from "../../../hooks/useDeviceDetect";
|
||||||
|
|
||||||
interface AlertProps {
|
interface AlertProps {
|
||||||
variant?: "success" | "error" | "info" | "warning"
|
variant?: "success" | "error" | "info" | "warning"
|
||||||
|
@ -21,13 +22,15 @@ const Alert: FC<AlertProps> = ({
|
||||||
variant,
|
variant,
|
||||||
children }) => {
|
children }) => {
|
||||||
const { isDarkTheme } = useAppState();
|
const { isDarkTheme } = useAppState();
|
||||||
|
const { isMobile } = useDeviceDetect();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={classNames({
|
className={classNames({
|
||||||
"vm-alert": true,
|
"vm-alert": true,
|
||||||
[`vm-alert_${variant}`]: variant,
|
[`vm-alert_${variant}`]: variant,
|
||||||
"vm-alert_dark": isDarkTheme
|
"vm-alert_dark": isDarkTheme,
|
||||||
|
"vm-alert_mobile": isMobile
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<div className="vm-alert__icon">{icons[variant || "info"]}</div>
|
<div className="vm-alert__icon">{icons[variant || "info"]}</div>
|
||||||
|
|
|
@ -15,6 +15,11 @@
|
||||||
color: $color-text;
|
color: $color-text;
|
||||||
line-height: 20px;
|
line-height: 20px;
|
||||||
|
|
||||||
|
&_mobile {
|
||||||
|
align-items: flex-start;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
&:after {
|
&:after {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
content: '';
|
content: '';
|
||||||
|
@ -27,6 +32,10 @@
|
||||||
opacity: 0.1;
|
opacity: 0.1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&_mobile:after {
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
&__icon,
|
&__icon,
|
||||||
&__content {
|
&__content {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import React, { FC, Ref, useEffect, useMemo, useRef, useState } from "preact/compat";
|
import React, { FC, Ref, useEffect, useMemo, useRef, useState } from "preact/compat";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import useClickOutside from "../../../hooks/useClickOutside";
|
|
||||||
import Popper from "../Popper/Popper";
|
import Popper from "../Popper/Popper";
|
||||||
import "./style.scss";
|
import "./style.scss";
|
||||||
import { DoneIcon } from "../Icons";
|
import { DoneIcon } from "../Icons";
|
||||||
|
import useDeviceDetect from "../../../hooks/useDeviceDetect";
|
||||||
|
|
||||||
interface AutocompleteProps {
|
interface AutocompleteProps {
|
||||||
value: string
|
value: string
|
||||||
|
@ -15,7 +15,9 @@ interface AutocompleteProps {
|
||||||
fullWidth?: boolean
|
fullWidth?: boolean
|
||||||
noOptionsText?: string
|
noOptionsText?: string
|
||||||
selected?: string[]
|
selected?: string[]
|
||||||
onSelect: (val: string) => void,
|
label?: string
|
||||||
|
disabledFullScreen?: boolean
|
||||||
|
onSelect: (val: string) => void
|
||||||
onOpenAutocomplete?: (val: boolean) => void
|
onOpenAutocomplete?: (val: boolean) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,9 +31,12 @@ const Autocomplete: FC<AutocompleteProps> = ({
|
||||||
fullWidth,
|
fullWidth,
|
||||||
selected,
|
selected,
|
||||||
noOptionsText,
|
noOptionsText,
|
||||||
|
label,
|
||||||
|
disabledFullScreen,
|
||||||
onSelect,
|
onSelect,
|
||||||
onOpenAutocomplete
|
onOpenAutocomplete
|
||||||
}) => {
|
}) => {
|
||||||
|
const { isMobile } = useDeviceDetect();
|
||||||
const wrapperEl = useRef<HTMLDivElement>(null);
|
const wrapperEl = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const [openAutocomplete, setOpenAutocomplete] = useState(false);
|
const [openAutocomplete, setOpenAutocomplete] = useState(false);
|
||||||
|
@ -118,8 +123,6 @@ const Autocomplete: FC<AutocompleteProps> = ({
|
||||||
onOpenAutocomplete && onOpenAutocomplete(openAutocomplete);
|
onOpenAutocomplete && onOpenAutocomplete(openAutocomplete);
|
||||||
}, [openAutocomplete]);
|
}, [openAutocomplete]);
|
||||||
|
|
||||||
useClickOutside(wrapperEl, handleCloseAutocomplete, anchor);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Popper
|
<Popper
|
||||||
open={openAutocomplete}
|
open={openAutocomplete}
|
||||||
|
@ -127,9 +130,14 @@ const Autocomplete: FC<AutocompleteProps> = ({
|
||||||
placement="bottom-left"
|
placement="bottom-left"
|
||||||
onClose={handleCloseAutocomplete}
|
onClose={handleCloseAutocomplete}
|
||||||
fullWidth={fullWidth}
|
fullWidth={fullWidth}
|
||||||
|
title={isMobile ? label : undefined}
|
||||||
|
disabledFullScreen={disabledFullScreen}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="vm-autocomplete"
|
className={classNames({
|
||||||
|
"vm-autocomplete": true,
|
||||||
|
"vm-autocomplete_mobile": isMobile && !disabledFullScreen,
|
||||||
|
})}
|
||||||
ref={wrapperEl}
|
ref={wrapperEl}
|
||||||
>
|
>
|
||||||
{displayNoOptionsText && <div className="vm-autocomplete__no-options">{noOptionsText}</div>}
|
{displayNoOptionsText && <div className="vm-autocomplete__no-options">{noOptionsText}</div>}
|
||||||
|
@ -137,6 +145,7 @@ const Autocomplete: FC<AutocompleteProps> = ({
|
||||||
<div
|
<div
|
||||||
className={classNames({
|
className={classNames({
|
||||||
"vm-list-item": true,
|
"vm-list-item": true,
|
||||||
|
"vm-list-item_mobile": isMobile,
|
||||||
"vm-list-item_active": i === focusOption,
|
"vm-list-item_active": i === focusOption,
|
||||||
"vm-list-item_multiselect": selected,
|
"vm-list-item_multiselect": selected,
|
||||||
"vm-list-item_multiselect_selected": selected?.includes(option)
|
"vm-list-item_multiselect_selected": selected?.includes(option)
|
||||||
|
|
|
@ -3,6 +3,11 @@
|
||||||
.vm-autocomplete {
|
.vm-autocomplete {
|
||||||
max-height: 300px;
|
max-height: 300px;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
overscroll-behavior: none;
|
||||||
|
|
||||||
|
&_mobile {
|
||||||
|
max-height: calc(($vh * 100) - 70px);
|
||||||
|
}
|
||||||
|
|
||||||
&__no-options {
|
&__no-options {
|
||||||
padding: $padding-global;
|
padding: $padding-global;
|
||||||
|
|
|
@ -93,6 +93,7 @@ $button-radius: 6px;
|
||||||
/* variant CONTAINED */
|
/* variant CONTAINED */
|
||||||
&_contained_primary {
|
&_contained_primary {
|
||||||
color: $color-primary-text;
|
color: $color-primary-text;
|
||||||
|
background-color: $color-primary;
|
||||||
|
|
||||||
&:before {
|
&:before {
|
||||||
background-color: $color-primary;
|
background-color: $color-primary;
|
||||||
|
|
|
@ -8,6 +8,8 @@ import { DATE_TIME_FORMAT } from "../../../../constants/date";
|
||||||
import "./style.scss";
|
import "./style.scss";
|
||||||
import { CalendarIcon, ClockIcon } from "../../Icons";
|
import { CalendarIcon, ClockIcon } from "../../Icons";
|
||||||
import Tabs from "../../Tabs/Tabs";
|
import Tabs from "../../Tabs/Tabs";
|
||||||
|
import useDeviceDetect from "../../../../hooks/useDeviceDetect";
|
||||||
|
import classNames from "classnames";
|
||||||
|
|
||||||
interface DatePickerProps {
|
interface DatePickerProps {
|
||||||
date: Date | Dayjs
|
date: Date | Dayjs
|
||||||
|
@ -33,6 +35,7 @@ const Calendar: FC<DatePickerProps> = ({
|
||||||
const [viewDate, setViewDate] = useState(dayjs.tz(date));
|
const [viewDate, setViewDate] = useState(dayjs.tz(date));
|
||||||
const [selectDate, setSelectDate] = useState(dayjs.tz(date));
|
const [selectDate, setSelectDate] = useState(dayjs.tz(date));
|
||||||
const [tab, setTab] = useState(tabs[0].value);
|
const [tab, setTab] = useState(tabs[0].value);
|
||||||
|
const { isMobile } = useDeviceDetect();
|
||||||
|
|
||||||
const toggleDisplayYears = () => {
|
const toggleDisplayYears = () => {
|
||||||
setDisplayYears(prev => !prev);
|
setDisplayYears(prev => !prev);
|
||||||
|
@ -67,7 +70,12 @@ const Calendar: FC<DatePickerProps> = ({
|
||||||
}, [selectDate]);
|
}, [selectDate]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="vm-calendar">
|
<div
|
||||||
|
className={classNames({
|
||||||
|
"vm-calendar": true,
|
||||||
|
"vm-calendar_mobile": isMobile,
|
||||||
|
})}
|
||||||
|
>
|
||||||
{tab === "date" && (
|
{tab === "date" && (
|
||||||
<CalendarHeader
|
<CalendarHeader
|
||||||
viewDate={viewDate}
|
viewDate={viewDate}
|
||||||
|
|
|
@ -9,6 +9,10 @@
|
||||||
background-color: $color-background-block;
|
background-color: $color-background-block;
|
||||||
border-radius: $border-radius-medium;
|
border-radius: $border-radius-medium;
|
||||||
|
|
||||||
|
&_mobile {
|
||||||
|
padding: 0 $padding-global;
|
||||||
|
}
|
||||||
|
|
||||||
&__tabs {
|
&__tabs {
|
||||||
margin: 0 0-$padding-global 0-$padding-global;
|
margin: 0 0-$padding-global 0-$padding-global;
|
||||||
border-top: $border-divider;
|
border-top: $border-divider;
|
||||||
|
@ -61,6 +65,8 @@
|
||||||
|
|
||||||
&__prev,
|
&__prev,
|
||||||
&__next {
|
&__next {
|
||||||
|
margin: -8px;
|
||||||
|
padding: 8px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: opacity 200ms ease-in-out;
|
transition: opacity 200ms ease-in-out;
|
||||||
|
|
||||||
|
@ -87,6 +93,11 @@
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: 2px;
|
gap: 2px;
|
||||||
|
|
||||||
|
@media (max-width: 500px) {
|
||||||
|
grid-template-columns: repeat(7, calc((100vw - ($padding-global * 2) - (6 * 2px))/7));
|
||||||
|
grid-template-rows: repeat(6, calc((100vw - ($padding-global * 2) - (5 * 2px))/7));
|
||||||
|
}
|
||||||
|
|
||||||
&-cell {
|
&-cell {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
|
@ -3,12 +3,14 @@ import Calendar from "../../Main/DatePicker/Calendar/Calendar";
|
||||||
import dayjs, { Dayjs } from "dayjs";
|
import dayjs, { Dayjs } from "dayjs";
|
||||||
import Popper from "../../Main/Popper/Popper";
|
import Popper from "../../Main/Popper/Popper";
|
||||||
import { DATE_TIME_FORMAT } from "../../../constants/date";
|
import { DATE_TIME_FORMAT } from "../../../constants/date";
|
||||||
|
import useDeviceDetect from "../../../hooks/useDeviceDetect";
|
||||||
|
|
||||||
interface DatePickerProps {
|
interface DatePickerProps {
|
||||||
date: string | Date | Dayjs,
|
date: string | Date | Dayjs,
|
||||||
targetRef: Ref<HTMLElement>
|
targetRef: Ref<HTMLElement>
|
||||||
format?: string
|
format?: string
|
||||||
timepicker?: boolean
|
timepicker?: boolean
|
||||||
|
label?: string
|
||||||
onChange: (val: string) => void
|
onChange: (val: string) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,9 +20,11 @@ const DatePicker = forwardRef<HTMLDivElement, DatePickerProps>(({
|
||||||
format = DATE_TIME_FORMAT,
|
format = DATE_TIME_FORMAT,
|
||||||
timepicker,
|
timepicker,
|
||||||
onChange,
|
onChange,
|
||||||
|
label
|
||||||
}, ref) => {
|
}, ref) => {
|
||||||
const [openCalendar, setOpenCalendar] = useState(false);
|
const [openCalendar, setOpenCalendar] = useState(false);
|
||||||
const dateDayjs = useMemo(() => date ? dayjs.tz(date) : dayjs().tz(), [date]);
|
const dateDayjs = useMemo(() => date ? dayjs.tz(date) : dayjs().tz(), [date]);
|
||||||
|
const { isMobile } = useDeviceDetect();
|
||||||
|
|
||||||
const toggleOpenCalendar = () => {
|
const toggleOpenCalendar = () => {
|
||||||
setOpenCalendar(prev => !prev);
|
setOpenCalendar(prev => !prev);
|
||||||
|
@ -61,6 +65,7 @@ const DatePicker = forwardRef<HTMLDivElement, DatePickerProps>(({
|
||||||
buttonRef={targetRef}
|
buttonRef={targetRef}
|
||||||
placement="bottom-right"
|
placement="bottom-right"
|
||||||
onClose={handleCloseCalendar}
|
onClose={handleCloseCalendar}
|
||||||
|
title={isMobile ? label : undefined}
|
||||||
>
|
>
|
||||||
<div ref={ref}>
|
<div ref={ref}>
|
||||||
<Calendar
|
<Calendar
|
||||||
|
|
|
@ -125,17 +125,6 @@ export const ArrowDropDownIcon = () => (
|
||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
|
|
||||||
export const PlusCircleFillIcon = () => (
|
|
||||||
<svg
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
fill="currentColor"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm5 11h-4v4h-2v-4H7v-2h4V7h2v4h4v2z"
|
|
||||||
></path>
|
|
||||||
</svg>
|
|
||||||
);
|
|
||||||
|
|
||||||
export const ClockIcon = () => (
|
export const ClockIcon = () => (
|
||||||
<svg
|
<svg
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
|
@ -181,15 +170,6 @@ export const KeyboardIcon = () => (
|
||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
|
|
||||||
export const RemoveCircleIcon = () => (
|
|
||||||
<svg
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
fill="currentColor"
|
|
||||||
>
|
|
||||||
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm5 11H7v-2h10v2z"></path>
|
|
||||||
</svg>
|
|
||||||
);
|
|
||||||
|
|
||||||
export const PlayIcon = () => (
|
export const PlayIcon = () => (
|
||||||
<svg
|
<svg
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
|
@ -257,6 +237,15 @@ export const PlusIcon = () => (
|
||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const MinusIcon = () => (
|
||||||
|
<svg
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="currentColor"
|
||||||
|
>
|
||||||
|
<path d="M19 13H5v-2h14v2z"></path>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
|
||||||
export const DoneIcon = () => (
|
export const DoneIcon = () => (
|
||||||
<svg
|
<svg
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
|
@ -310,30 +299,6 @@ export const DragIcon = () => (
|
||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
|
|
||||||
export const SearchIcon = () => (
|
|
||||||
<svg
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
fill="currentColor"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"
|
|
||||||
></path>
|
|
||||||
</svg>
|
|
||||||
);
|
|
||||||
|
|
||||||
export const ResizeIcon = () => (
|
|
||||||
<svg
|
|
||||||
className="MuiSvgIcon-root MuiSvgIcon-fontSizeMedium MuiBox-root css-1om0hkc"
|
|
||||||
focusable="false"
|
|
||||||
aria-hidden="true"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
data-testid="OpenInFullIcon"
|
|
||||||
fill="currentColor"
|
|
||||||
>
|
|
||||||
<path d="M21 11V3h-8l3.29 3.29-10 10L3 13v8h8l-3.29-3.29 10-10z"></path>
|
|
||||||
</svg>
|
|
||||||
);
|
|
||||||
|
|
||||||
export const TimelineIcon = () => (
|
export const TimelineIcon = () => (
|
||||||
<svg
|
<svg
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
|
@ -401,13 +366,24 @@ export const StorageIcon = () => (
|
||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
|
|
||||||
export const MenuIcon = () => (
|
export const MoreIcon = () => (
|
||||||
<svg
|
<svg
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M4 18h16c.55 0 1-.45 1-1s-.45-1-1-1H4c-.55 0-1 .45-1 1s.45 1 1 1zm0-5h16c.55 0 1-.45 1-1s-.45-1-1-1H4c-.55 0-1 .45-1 1s.45 1 1 1zM3 7c0 .55.45 1 1 1h16c.55 0 1-.45 1-1s-.45-1-1-1H4c-.55 0-1 .45-1 1z"
|
d="M12 8c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const TuneIcon = () => (
|
||||||
|
<svg
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="currentColor"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M3 17v2h6v-2H3zM3 5v2h10V5H3zm10 16v-2h8v-2h-8v-2h-2v6h2zM7 9v2H3v2h4v2h2V9H7zm14 4v-2H11v2h10zm-6-4h2V7h4V5h-4V3h-2v6z"
|
||||||
></path>
|
></path>
|
||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
|
|
|
@ -2,13 +2,12 @@ import React from "preact/compat";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import "./style.scss";
|
import "./style.scss";
|
||||||
|
|
||||||
const MenuBurger = ({ open, onClick }: {open: boolean, onClick: () => void}) => (
|
const MenuBurger = ({ open }: {open: boolean}) => (
|
||||||
<button
|
<button
|
||||||
className={classNames({
|
className={classNames({
|
||||||
"vm-menu-burger": true,
|
"vm-menu-burger": true,
|
||||||
"vm-menu-burger_opened": open
|
"vm-menu-burger_opened": open
|
||||||
})}
|
})}
|
||||||
onClick={onClick}
|
|
||||||
>
|
>
|
||||||
<span></span>
|
<span></span>
|
||||||
</button>
|
</button>
|
||||||
|
|
|
@ -6,15 +6,26 @@ import { ReactNode, MouseEvent } from "react";
|
||||||
import "./style.scss";
|
import "./style.scss";
|
||||||
import useDeviceDetect from "../../../hooks/useDeviceDetect";
|
import useDeviceDetect from "../../../hooks/useDeviceDetect";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
|
import { useLocation, useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
interface ModalProps {
|
interface ModalProps {
|
||||||
title?: string
|
title?: string
|
||||||
children: ReactNode
|
children: ReactNode
|
||||||
onClose: () => void
|
onClose: () => void
|
||||||
|
className?: string
|
||||||
|
isOpen?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const Modal: FC<ModalProps> = ({ title, children, onClose }) => {
|
const Modal: FC<ModalProps> = ({
|
||||||
|
title,
|
||||||
|
children,
|
||||||
|
onClose,
|
||||||
|
className,
|
||||||
|
isOpen= true
|
||||||
|
}) => {
|
||||||
const { isMobile } = useDeviceDetect();
|
const { isMobile } = useDeviceDetect();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const location = useLocation();
|
||||||
|
|
||||||
const handleKeyUp = (e: KeyboardEvent) => {
|
const handleKeyUp = (e: KeyboardEvent) => {
|
||||||
if (e.key === "Escape") onClose();
|
if (e.key === "Escape") onClose();
|
||||||
|
@ -24,7 +35,23 @@ const Modal: FC<ModalProps> = ({ title, children, onClose }) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handlePopstate = () => {
|
||||||
|
if (isOpen) {
|
||||||
|
navigate(location, { replace: true });
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
window.addEventListener("popstate", handlePopstate);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener("popstate", handlePopstate);
|
||||||
|
};
|
||||||
|
}, [isOpen, location]);
|
||||||
|
|
||||||
|
const handleDisplayModal = () => {
|
||||||
|
if (!isOpen) return;
|
||||||
document.body.style.overflow = "hidden";
|
document.body.style.overflow = "hidden";
|
||||||
window.addEventListener("keyup", handleKeyUp);
|
window.addEventListener("keyup", handleKeyUp);
|
||||||
|
|
||||||
|
@ -32,18 +59,24 @@ const Modal: FC<ModalProps> = ({ title, children, onClose }) => {
|
||||||
document.body.style.overflow = "auto";
|
document.body.style.overflow = "auto";
|
||||||
window.removeEventListener("keyup", handleKeyUp);
|
window.removeEventListener("keyup", handleKeyUp);
|
||||||
};
|
};
|
||||||
}, []);
|
};
|
||||||
|
|
||||||
|
useEffect(handleDisplayModal, [isOpen]);
|
||||||
|
|
||||||
return ReactDOM.createPortal((
|
return ReactDOM.createPortal((
|
||||||
<div
|
<div
|
||||||
className={classNames({
|
className={classNames({
|
||||||
"vm-modal": true,
|
"vm-modal": true,
|
||||||
"vm-modal_mobile": isMobile
|
"vm-modal_mobile": isMobile,
|
||||||
|
[`${className}`]: className
|
||||||
})}
|
})}
|
||||||
onMouseDown={onClose}
|
onMouseDown={onClose}
|
||||||
>
|
>
|
||||||
<div className="vm-modal-content">
|
<div className="vm-modal-content">
|
||||||
<div className="vm-modal-content-header">
|
<div
|
||||||
|
className="vm-modal-content-header"
|
||||||
|
onMouseDown={handleMouseDown}
|
||||||
|
>
|
||||||
{title && (
|
{title && (
|
||||||
<div className="vm-modal-content-header__title">
|
<div className="vm-modal-content-header__title">
|
||||||
{title}
|
{title}
|
||||||
|
|
|
@ -14,16 +14,31 @@ $padding-modal: 22px;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
background: rgba($color-black, 0.55);
|
background: rgba($color-black, 0.55);
|
||||||
|
|
||||||
&_mobile &-content {
|
&_mobile {
|
||||||
|
align-items: flex-start;
|
||||||
min-height: calc($vh * 100);
|
min-height: calc($vh * 100);
|
||||||
max-height: calc($vh * 100);
|
max-height: calc($vh * 100);
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
&_mobile &-content {
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
|
overflow: visible;
|
||||||
|
min-height: 100%;
|
||||||
|
max-height: max-content;
|
||||||
|
grid-template-rows: 70px max-content;
|
||||||
|
|
||||||
|
&-header {
|
||||||
|
padding: $padding-small $padding-small $padding-small $padding-global;
|
||||||
|
margin-bottom: $padding-global;
|
||||||
|
}
|
||||||
|
|
||||||
&-body {
|
&-body {
|
||||||
display: grid;
|
display: grid;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
min-height: 100%;
|
min-height: 100%;
|
||||||
|
padding: 0 $padding-global $padding-modal;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,7 +46,6 @@ $padding-modal: 22px;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-rows: auto 1fr;
|
grid-template-rows: auto 1fr;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
padding: $padding-modal;
|
|
||||||
background: $color-background-block;
|
background: $color-background-block;
|
||||||
box-shadow: 0 0 24px rgba($color-black, 0.07);
|
box-shadow: 0 0 24px rgba($color-black, 0.07);
|
||||||
border-radius: $border-radius-small;
|
border-radius: $border-radius-small;
|
||||||
|
@ -39,14 +53,25 @@ $padding-modal: 22px;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
|
||||||
&-header {
|
&-header {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr auto;
|
grid-template-columns: 1fr auto;
|
||||||
|
gap: $padding-small;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-bottom: $padding-modal ;
|
justify-content: space-between;
|
||||||
|
background-color: $color-background-block;
|
||||||
|
padding: $padding-global $padding-modal;
|
||||||
|
border-radius: $border-radius-small $border-radius-small 0 0;
|
||||||
|
color: $color-text;
|
||||||
|
border-bottom: $border-divider;
|
||||||
|
margin-bottom: $padding-modal;
|
||||||
|
min-height: 51px;
|
||||||
|
z-index: 3;
|
||||||
|
|
||||||
&__title {
|
&__title {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-size: $font-size-medium;
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__close {
|
&__close {
|
||||||
|
@ -61,7 +86,9 @@ $padding-modal: 22px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&-body {}
|
&-body {
|
||||||
|
padding: 0 $padding-modal $padding-modal;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,12 @@
|
||||||
import React, { FC, ReactNode, useEffect, useMemo, useRef, useState } from "react";
|
import React, { FC, MouseEvent as ReactMouseEvent, ReactNode, useEffect, useMemo, useRef, useState } from "react";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import ReactDOM from "react-dom";
|
import ReactDOM from "react-dom";
|
||||||
import "./style.scss";
|
import "./style.scss";
|
||||||
import useClickOutside from "../../../hooks/useClickOutside";
|
import useClickOutside from "../../../hooks/useClickOutside";
|
||||||
|
import useDeviceDetect from "../../../hooks/useDeviceDetect";
|
||||||
|
import Button from "../Button/Button";
|
||||||
|
import { CloseIcon } from "../Icons";
|
||||||
|
import { useLocation, useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
interface PopperProps {
|
interface PopperProps {
|
||||||
children: ReactNode
|
children: ReactNode
|
||||||
|
@ -14,6 +18,8 @@ interface PopperProps {
|
||||||
offset?: {top: number, left: number}
|
offset?: {top: number, left: number}
|
||||||
clickOutside?: boolean,
|
clickOutside?: boolean,
|
||||||
fullWidth?: boolean
|
fullWidth?: boolean
|
||||||
|
title?: string
|
||||||
|
disabledFullScreen?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const Popper: FC<PopperProps> = ({
|
const Popper: FC<PopperProps> = ({
|
||||||
|
@ -24,10 +30,14 @@ const Popper: FC<PopperProps> = ({
|
||||||
onClose,
|
onClose,
|
||||||
offset = { top: 6, left: 0 },
|
offset = { top: 6, left: 0 },
|
||||||
clickOutside = true,
|
clickOutside = true,
|
||||||
fullWidth
|
fullWidth,
|
||||||
|
title,
|
||||||
|
disabledFullScreen
|
||||||
}) => {
|
}) => {
|
||||||
|
const { isMobile } = useDeviceDetect();
|
||||||
const [isOpen, setIsOpen] = useState(true);
|
const navigate = useNavigate();
|
||||||
|
const location = useLocation();
|
||||||
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const [popperSize, setPopperSize] = useState({ width: 0, height: 0 });
|
const [popperSize, setPopperSize] = useState({ width: 0, height: 0 });
|
||||||
|
|
||||||
const popperRef = useRef<HTMLDivElement>(null);
|
const popperRef = useRef<HTMLDivElement>(null);
|
||||||
|
@ -50,6 +60,13 @@ const Popper: FC<PopperProps> = ({
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isOpen && onClose) onClose();
|
if (!isOpen && onClose) onClose();
|
||||||
|
if (isOpen && isMobile && !disabledFullScreen) {
|
||||||
|
document.body.style.overflow = "hidden";
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
document.body.style.overflow = "auto";
|
||||||
|
};
|
||||||
}, [isOpen]);
|
}, [isOpen]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -63,7 +80,7 @@ const Popper: FC<PopperProps> = ({
|
||||||
const popperStyle = useMemo(() => {
|
const popperStyle = useMemo(() => {
|
||||||
const buttonEl = buttonRef.current;
|
const buttonEl = buttonRef.current;
|
||||||
|
|
||||||
if (!buttonEl|| !isOpen) return {};
|
if (!buttonEl || !isOpen) return {};
|
||||||
|
|
||||||
const buttonPos = buttonEl.getBoundingClientRect();
|
const buttonPos = buttonEl.getBoundingClientRect();
|
||||||
|
|
||||||
|
@ -104,28 +121,63 @@ const Popper: FC<PopperProps> = ({
|
||||||
return position;
|
return position;
|
||||||
},[buttonRef, placement, isOpen, children, fullWidth]);
|
},[buttonRef, placement, isOpen, children, fullWidth]);
|
||||||
|
|
||||||
|
const handleClickClose = (e: ReactMouseEvent<HTMLButtonElement, MouseEvent>) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onClose();
|
||||||
|
};
|
||||||
|
|
||||||
if (clickOutside) useClickOutside(popperRef, () => setIsOpen(false), buttonRef);
|
if (clickOutside) useClickOutside(popperRef, () => setIsOpen(false), buttonRef);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!popperRef.current || !isOpen) return;
|
if (!popperRef.current || !isOpen || (isMobile && !disabledFullScreen)) return;
|
||||||
const { right, width } = popperRef.current.getBoundingClientRect();
|
const { right, width } = popperRef.current.getBoundingClientRect();
|
||||||
if (right > window.innerWidth) popperRef.current.style.left = `${window.innerWidth - 20 -width}px`;
|
if (right > window.innerWidth) {
|
||||||
|
const left = window.innerWidth - 20 - width;
|
||||||
|
popperRef.current.style.left = left < window.innerWidth ? "0" : `${left}px`;
|
||||||
|
}
|
||||||
}, [isOpen, popperRef]);
|
}, [isOpen, popperRef]);
|
||||||
|
|
||||||
|
const handlePopstate = () => {
|
||||||
|
if (isOpen && isMobile && !disabledFullScreen) {
|
||||||
|
navigate(location, { replace: true });
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
window.addEventListener("popstate", handlePopstate);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener("popstate", handlePopstate);
|
||||||
|
};
|
||||||
|
}, [isOpen, isMobile, disabledFullScreen, location]);
|
||||||
|
|
||||||
const popperClasses = classNames({
|
const popperClasses = classNames({
|
||||||
"vm-popper": true,
|
"vm-popper": true,
|
||||||
"vm-popper_open": isOpen,
|
"vm-popper_mobile": isMobile && !disabledFullScreen,
|
||||||
|
"vm-popper_open": (isMobile || Object.keys(popperStyle).length) && isOpen,
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{isOpen && ReactDOM.createPortal((
|
{(isOpen || !popperSize.width) && ReactDOM.createPortal((
|
||||||
<div
|
<div
|
||||||
className={popperClasses}
|
className={popperClasses}
|
||||||
ref={popperRef}
|
ref={popperRef}
|
||||||
style={popperStyle}
|
style={(isMobile && !disabledFullScreen) ? {} : popperStyle}
|
||||||
>
|
>
|
||||||
|
{(title || (isMobile && !disabledFullScreen)) && (
|
||||||
|
<div className="vm-popper-header">
|
||||||
|
<p className="vm-popper-header__title">{title}</p>
|
||||||
|
<Button
|
||||||
|
variant="text"
|
||||||
|
size="small"
|
||||||
|
onClick={handleClickClose}
|
||||||
|
>
|
||||||
|
<CloseIcon/>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{children}
|
{children}
|
||||||
</div>), document.body)}
|
</div>), document.body)}
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -17,6 +17,38 @@
|
||||||
animation: vm-slider 150ms cubic-bezier(0.280, 0.840, 0.420, 1.1);
|
animation: vm-slider 150ms cubic-bezier(0.280, 0.840, 0.420, 1.1);
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&_mobile {
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 100%;
|
||||||
|
border-radius: 0;
|
||||||
|
overflow: auto;
|
||||||
|
animation: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-header {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr auto;
|
||||||
|
gap: $padding-small;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
background-color: $color-background-block;
|
||||||
|
padding: $padding-small $padding-small $padding-small $padding-global;
|
||||||
|
border-radius: $border-radius-small $border-radius-small 0 0;
|
||||||
|
color: $color-text;
|
||||||
|
border-bottom: $border-divider;
|
||||||
|
margin-bottom: $padding-global;
|
||||||
|
min-height: 51px;
|
||||||
|
|
||||||
|
&__title {
|
||||||
|
font-weight: bold;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes vm-slider {
|
@keyframes vm-slider {
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { FormEvent, MouseEvent } from "react";
|
||||||
import Autocomplete from "../Autocomplete/Autocomplete";
|
import Autocomplete from "../Autocomplete/Autocomplete";
|
||||||
import { useAppState } from "../../../state/common/StateContext";
|
import { useAppState } from "../../../state/common/StateContext";
|
||||||
import "./style.scss";
|
import "./style.scss";
|
||||||
|
import useDeviceDetect from "../../../hooks/useDeviceDetect";
|
||||||
|
|
||||||
interface SelectProps {
|
interface SelectProps {
|
||||||
value: string | string[]
|
value: string | string[]
|
||||||
|
@ -13,6 +14,7 @@ interface SelectProps {
|
||||||
placeholder?: string
|
placeholder?: string
|
||||||
noOptionsText?: string
|
noOptionsText?: string
|
||||||
clearable?: boolean
|
clearable?: boolean
|
||||||
|
searchable?: boolean
|
||||||
autofocus?: boolean
|
autofocus?: boolean
|
||||||
onChange: (value: string) => void
|
onChange: (value: string) => void
|
||||||
}
|
}
|
||||||
|
@ -24,10 +26,12 @@ const Select: FC<SelectProps> = ({
|
||||||
placeholder,
|
placeholder,
|
||||||
noOptionsText,
|
noOptionsText,
|
||||||
clearable = false,
|
clearable = false,
|
||||||
|
searchable = false,
|
||||||
autofocus,
|
autofocus,
|
||||||
onChange
|
onChange
|
||||||
}) => {
|
}) => {
|
||||||
const { isDarkTheme } = useAppState();
|
const { isDarkTheme } = useAppState();
|
||||||
|
const { isMobile } = useDeviceDetect();
|
||||||
|
|
||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState("");
|
||||||
const autocompleteAnchorEl = useRef<HTMLDivElement>(null);
|
const autocompleteAnchorEl = useRef<HTMLDivElement>(null);
|
||||||
|
@ -95,7 +99,7 @@ const Select: FC<SelectProps> = ({
|
||||||
}, [openList, inputRef]);
|
}, [openList, inputRef]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!autofocus || !inputRef.current) return;
|
if (!autofocus || !inputRef.current || isMobile) return;
|
||||||
inputRef.current.focus();
|
inputRef.current.focus();
|
||||||
}, [autofocus, inputRef]);
|
}, [autofocus, inputRef]);
|
||||||
|
|
||||||
|
@ -120,17 +124,23 @@ const Select: FC<SelectProps> = ({
|
||||||
ref={autocompleteAnchorEl}
|
ref={autocompleteAnchorEl}
|
||||||
>
|
>
|
||||||
<div className="vm-select-input-content">
|
<div className="vm-select-input-content">
|
||||||
{selectedValues && selectedValues.map(item => (
|
{!isMobile && selectedValues && selectedValues.map(item => (
|
||||||
<div
|
<div
|
||||||
className="vm-select-input-content__selected"
|
className="vm-select-input-content__selected"
|
||||||
key={item}
|
key={item}
|
||||||
>
|
>
|
||||||
{item}
|
<span>{item}</span>
|
||||||
<div onClick={createHandleClick(item)}>
|
<div onClick={createHandleClick(item)}>
|
||||||
<CloseIcon/>
|
<CloseIcon/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
{isMobile && !!selectedValues?.length && (
|
||||||
|
<span className="vm-select-input-content__counter">
|
||||||
|
selected {selectedValues.length}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{!isMobile || (isMobile && (!selectedValues || !selectedValues?.length)) && (
|
||||||
<input
|
<input
|
||||||
value={textFieldValue}
|
value={textFieldValue}
|
||||||
type="text"
|
type="text"
|
||||||
|
@ -138,7 +148,9 @@ const Select: FC<SelectProps> = ({
|
||||||
onInput={handleChange}
|
onInput={handleChange}
|
||||||
onFocus={handleFocus}
|
onFocus={handleFocus}
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
|
readOnly={isMobile || !searchable}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
{label && <span className="vm-text-field__label">{label}</span>}
|
{label && <span className="vm-text-field__label">{label}</span>}
|
||||||
{clearable && value && (
|
{clearable && value && (
|
||||||
|
@ -159,6 +171,7 @@ const Select: FC<SelectProps> = ({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Autocomplete
|
<Autocomplete
|
||||||
|
label={label}
|
||||||
value={autocompleteValue}
|
value={autocompleteValue}
|
||||||
options={list}
|
options={list}
|
||||||
anchor={autocompleteAnchorEl}
|
anchor={autocompleteAnchorEl}
|
||||||
|
|
|
@ -19,6 +19,16 @@
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: $padding-small;
|
gap: $padding-small;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
max-width: calc(100% - ($padding-global + 61px));
|
||||||
|
|
||||||
|
&_mobile {
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__counter {
|
||||||
|
font-size: $font-size;
|
||||||
|
line-height: $font-size;
|
||||||
|
}
|
||||||
|
|
||||||
&__selected {
|
&__selected {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
|
@ -29,6 +39,13 @@
|
||||||
border-radius: $border-radius-small;
|
border-radius: $border-radius-small;
|
||||||
font-size: $font-size;
|
font-size: $font-size;
|
||||||
line-height: $font-size;
|
line-height: $font-size;
|
||||||
|
max-width: 100%;
|
||||||
|
|
||||||
|
span {
|
||||||
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
width: 20px;
|
width: 20px;
|
||||||
|
@ -95,4 +112,20 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&-options {
|
||||||
|
display: grid;
|
||||||
|
gap: $padding-small;
|
||||||
|
max-width: 300px;
|
||||||
|
max-height: 208px;
|
||||||
|
overflow: auto;
|
||||||
|
padding: $padding-global;
|
||||||
|
font-size: $font-size;
|
||||||
|
|
||||||
|
&_mobile {
|
||||||
|
padding: 0 $padding-global $padding-small;
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: calc(($vh * 100) - 70px);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,11 +8,17 @@ interface SwitchProps {
|
||||||
color?: "primary" | "secondary" | "error"
|
color?: "primary" | "secondary" | "error"
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
label?: string | ReactNode
|
label?: string | ReactNode
|
||||||
|
fullWidth?: boolean
|
||||||
onChange: (value: boolean) => void
|
onChange: (value: boolean) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const Switch: FC<SwitchProps> = ({
|
const Switch: FC<SwitchProps> = ({
|
||||||
value = false, disabled = false, label, color = "secondary", onChange
|
value = false,
|
||||||
|
disabled = false,
|
||||||
|
label,
|
||||||
|
color = "secondary",
|
||||||
|
fullWidth,
|
||||||
|
onChange
|
||||||
}) => {
|
}) => {
|
||||||
const toggleSwitch = () => {
|
const toggleSwitch = () => {
|
||||||
if (disabled) return;
|
if (disabled) return;
|
||||||
|
@ -21,6 +27,7 @@ const Switch: FC<SwitchProps> = ({
|
||||||
|
|
||||||
const switchClasses = classNames({
|
const switchClasses = classNames({
|
||||||
"vm-switch": true,
|
"vm-switch": true,
|
||||||
|
"vm-switch_full-width": fullWidth,
|
||||||
"vm-switch_disabled": disabled,
|
"vm-switch_disabled": disabled,
|
||||||
"vm-switch_active": value,
|
"vm-switch_active": value,
|
||||||
[`vm-switch_${color}_active`]: value,
|
[`vm-switch_${color}_active`]: value,
|
||||||
|
|
|
@ -11,6 +11,16 @@ $switch-border-radius: $switch-handle-size + ($switch-padding * 2);
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
|
&_full-width {
|
||||||
|
justify-content: space-between;
|
||||||
|
flex-direction: row-reverse;
|
||||||
|
}
|
||||||
|
|
||||||
|
&_full-width &__label {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
&_disabled {
|
&_disabled {
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
|
|
|
@ -2,6 +2,7 @@ import React, { FC, KeyboardEvent, useEffect, useRef, HTMLInputTypeAttribute, Re
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { useMemo } from "preact/compat";
|
import { useMemo } from "preact/compat";
|
||||||
import { useAppState } from "../../../state/common/StateContext";
|
import { useAppState } from "../../../state/common/StateContext";
|
||||||
|
import useDeviceDetect from "../../../hooks/useDeviceDetect";
|
||||||
import "./style.scss";
|
import "./style.scss";
|
||||||
|
|
||||||
interface TextFieldProps {
|
interface TextFieldProps {
|
||||||
|
@ -15,6 +16,7 @@ interface TextFieldProps {
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
autofocus?: boolean
|
autofocus?: boolean
|
||||||
helperText?: string
|
helperText?: string
|
||||||
|
inputmode?: "search" | "text" | "email" | "tel" | "url" | "none" | "numeric" | "decimal"
|
||||||
onChange?: (value: string) => void
|
onChange?: (value: string) => void
|
||||||
onEnter?: () => void
|
onEnter?: () => void
|
||||||
onKeyDown?: (e: KeyboardEvent) => void
|
onKeyDown?: (e: KeyboardEvent) => void
|
||||||
|
@ -33,6 +35,7 @@ const TextField: FC<TextFieldProps> = ({
|
||||||
disabled = false,
|
disabled = false,
|
||||||
autofocus = false,
|
autofocus = false,
|
||||||
helperText,
|
helperText,
|
||||||
|
inputmode = "text",
|
||||||
onChange,
|
onChange,
|
||||||
onEnter,
|
onEnter,
|
||||||
onKeyDown,
|
onKeyDown,
|
||||||
|
@ -40,6 +43,7 @@ const TextField: FC<TextFieldProps> = ({
|
||||||
onBlur
|
onBlur
|
||||||
}) => {
|
}) => {
|
||||||
const { isDarkTheme } = useAppState();
|
const { isDarkTheme } = useAppState();
|
||||||
|
const { isMobile } = useDeviceDetect();
|
||||||
|
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
||||||
|
@ -67,7 +71,7 @@ const TextField: FC<TextFieldProps> = ({
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!autofocus) return;
|
if (!autofocus || isMobile) return;
|
||||||
fieldRef?.current?.focus && fieldRef.current.focus();
|
fieldRef?.current?.focus && fieldRef.current.focus();
|
||||||
}, [fieldRef, autofocus]);
|
}, [fieldRef, autofocus]);
|
||||||
|
|
||||||
|
@ -97,7 +101,9 @@ const TextField: FC<TextFieldProps> = ({
|
||||||
ref={textareaRef}
|
ref={textareaRef}
|
||||||
value={value}
|
value={value}
|
||||||
rows={1}
|
rows={1}
|
||||||
|
inputMode={inputmode}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
|
autoCapitalize={"none"}
|
||||||
onInput={handleChange}
|
onInput={handleChange}
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
onFocus={handleFocus}
|
onFocus={handleFocus}
|
||||||
|
@ -112,6 +118,8 @@ const TextField: FC<TextFieldProps> = ({
|
||||||
value={value}
|
value={value}
|
||||||
type={type}
|
type={type}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
|
inputMode={inputmode}
|
||||||
|
autoCapitalize={"none"}
|
||||||
onInput={handleChange}
|
onInput={handleChange}
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
onFocus={handleFocus}
|
onFocus={handleFocus}
|
||||||
|
|
|
@ -43,6 +43,11 @@
|
||||||
-webkit-line-clamp: 2; /* number of lines to show */
|
-webkit-line-clamp: 2; /* number of lines to show */
|
||||||
line-clamp: 2;
|
line-clamp: 2;
|
||||||
-webkit-box-orient: vertical;
|
-webkit-box-orient: vertical;
|
||||||
|
|
||||||
|
@media (max-width: 500px) {
|
||||||
|
-webkit-line-clamp: 1; /* number of lines to show */
|
||||||
|
line-clamp: 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__label {
|
&__label {
|
||||||
|
|
|
@ -7,6 +7,7 @@ import { darkPalette, lightPalette } from "../../../constants/palette";
|
||||||
import { Theme } from "../../../types";
|
import { Theme } from "../../../types";
|
||||||
import { useAppDispatch, useAppState } from "../../../state/common/StateContext";
|
import { useAppDispatch, useAppState } from "../../../state/common/StateContext";
|
||||||
import useSystemTheme from "../../../hooks/useSystemTheme";
|
import useSystemTheme from "../../../hooks/useSystemTheme";
|
||||||
|
import useResize from "../../../hooks/useResize";
|
||||||
|
|
||||||
interface ThemeProviderProps {
|
interface ThemeProviderProps {
|
||||||
onLoaded: (val: boolean) => void
|
onLoaded: (val: boolean) => void
|
||||||
|
@ -28,6 +29,7 @@ export const ThemeProvider: FC<ThemeProviderProps> = ({ onLoaded }) => {
|
||||||
const { theme } = useAppState();
|
const { theme } = useAppState();
|
||||||
const isDarkTheme = useSystemTheme();
|
const isDarkTheme = useSystemTheme();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
const windowSize = useResize(document.body);
|
||||||
|
|
||||||
const [palette, setPalette] = useState({
|
const [palette, setPalette] = useState({
|
||||||
[Theme.dark]: darkPalette,
|
[Theme.dark]: darkPalette,
|
||||||
|
@ -93,6 +95,7 @@ export const ThemeProvider: FC<ThemeProviderProps> = ({ onLoaded }) => {
|
||||||
setTheme();
|
setTheme();
|
||||||
}, [palette]);
|
}, [palette]);
|
||||||
|
|
||||||
|
useEffect(setScrollbarSize, [windowSize]);
|
||||||
useEffect(updatePalette, [theme, isDarkTheme]);
|
useEffect(updatePalette, [theme, isDarkTheme]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { ArrowDownIcon } from "../../Main/Icons";
|
||||||
import "./style.scss";
|
import "./style.scss";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { useAppState } from "../../../state/common/StateContext";
|
import { useAppState } from "../../../state/common/StateContext";
|
||||||
|
import useDeviceDetect from "../../../hooks/useDeviceDetect";
|
||||||
|
|
||||||
interface RecursiveProps {
|
interface RecursiveProps {
|
||||||
trace: Trace;
|
trace: Trace;
|
||||||
|
@ -17,6 +18,7 @@ interface OpenLevels {
|
||||||
|
|
||||||
const NestedNav: FC<RecursiveProps> = ({ trace, totalMsec }) => {
|
const NestedNav: FC<RecursiveProps> = ({ trace, totalMsec }) => {
|
||||||
const { isDarkTheme } = useAppState();
|
const { isDarkTheme } = useAppState();
|
||||||
|
const { isMobile } = useDeviceDetect();
|
||||||
const [openLevels, setOpenLevels] = useState({} as OpenLevels);
|
const [openLevels, setOpenLevels] = useState({} as OpenLevels);
|
||||||
|
|
||||||
const handleListClick = (level: number) => () => {
|
const handleListClick = (level: number) => () => {
|
||||||
|
@ -32,6 +34,7 @@ const NestedNav: FC<RecursiveProps> = ({ trace, totalMsec }) => {
|
||||||
className={classNames({
|
className={classNames({
|
||||||
"vm-nested-nav": true,
|
"vm-nested-nav": true,
|
||||||
"vm-nested-nav_dark": isDarkTheme,
|
"vm-nested-nav_dark": isDarkTheme,
|
||||||
|
"vm-nested-nav_mobile": isMobile,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
|
|
|
@ -5,6 +5,10 @@
|
||||||
border-radius: $border-radius-small;
|
border-radius: $border-radius-small;
|
||||||
background-color: rgba($color-tropical-blue, 0.4);
|
background-color: rgba($color-tropical-blue, 0.4);
|
||||||
|
|
||||||
|
&_mobile {
|
||||||
|
margin-left: $padding-small;
|
||||||
|
}
|
||||||
|
|
||||||
&_dark {
|
&_dark {
|
||||||
background-color: rgba($color-black, 0.1);
|
background-color: rgba($color-black, 0.1);
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,8 @@ import Alert from "../Main/Alert/Alert";
|
||||||
import Tooltip from "../Main/Tooltip/Tooltip";
|
import Tooltip from "../Main/Tooltip/Tooltip";
|
||||||
import Modal from "../Main/Modal/Modal";
|
import Modal from "../Main/Modal/Modal";
|
||||||
import JsonForm from "../../pages/TracePage/JsonForm/JsonForm";
|
import JsonForm from "../../pages/TracePage/JsonForm/JsonForm";
|
||||||
|
import classNames from "classnames";
|
||||||
|
import useDeviceDetect from "../../hooks/useDeviceDetect";
|
||||||
|
|
||||||
interface TraceViewProps {
|
interface TraceViewProps {
|
||||||
traces: Trace[];
|
traces: Trace[];
|
||||||
|
@ -16,6 +18,7 @@ interface TraceViewProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
const TracingsView: FC<TraceViewProps> = ({ traces, jsonEditor = false, onDeleteClick }) => {
|
const TracingsView: FC<TraceViewProps> = ({ traces, jsonEditor = false, onDeleteClick }) => {
|
||||||
|
const { isMobile } = useDeviceDetect();
|
||||||
const [openTrace, setOpenTrace] = useState<Trace | null>(null);
|
const [openTrace, setOpenTrace] = useState<Trace | null>(null);
|
||||||
|
|
||||||
const handleCloseJson = () => {
|
const handleCloseJson = () => {
|
||||||
|
@ -77,7 +80,12 @@ const TracingsView: FC<TraceViewProps> = ({ traces, jsonEditor = false, onDelete
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
<nav className="vm-tracings-view-trace__nav">
|
<nav
|
||||||
|
className={classNames({
|
||||||
|
"vm-tracings-view-trace__nav": true,
|
||||||
|
"vm-tracings-view-trace__nav_mobile": isMobile
|
||||||
|
})}
|
||||||
|
>
|
||||||
<NestedNav
|
<NestedNav
|
||||||
trace={trace}
|
trace={trace}
|
||||||
totalMsec={trace.duration}
|
totalMsec={trace.duration}
|
||||||
|
|
|
@ -25,6 +25,10 @@
|
||||||
|
|
||||||
&__nav {
|
&__nav {
|
||||||
padding: $padding-medium $padding-medium $padding-medium 0;
|
padding: $padding-medium $padding-medium $padding-medium 0;
|
||||||
|
|
||||||
|
&_mobile {
|
||||||
|
padding: $padding-small $padding-small $padding-small 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ import classNames from "classnames";
|
||||||
import { useTimeState } from "../../../state/time/TimeStateContext";
|
import { useTimeState } from "../../../state/time/TimeStateContext";
|
||||||
import "./style.scss";
|
import "./style.scss";
|
||||||
import { promValueToNumber } from "../../../utils/metric";
|
import { promValueToNumber } from "../../../utils/metric";
|
||||||
|
import useDeviceDetect from "../../../hooks/useDeviceDetect";
|
||||||
|
|
||||||
export interface GraphViewProps {
|
export interface GraphViewProps {
|
||||||
data?: MetricResult[];
|
data?: MetricResult[];
|
||||||
|
@ -43,6 +44,7 @@ const GraphView: FC<GraphViewProps> = ({
|
||||||
fullWidth = true,
|
fullWidth = true,
|
||||||
height
|
height
|
||||||
}) => {
|
}) => {
|
||||||
|
const { isMobile } = useDeviceDetect();
|
||||||
const { timezone } = useTimeState();
|
const { timezone } = useTimeState();
|
||||||
const currentStep = useMemo(() => customStep || period.step || "1s", [period.step, customStep]);
|
const currentStep = useMemo(() => customStep || period.step || "1s", [period.step, customStep]);
|
||||||
const getSeriesItem = useCallback(getSeriesItemContext(), [data]);
|
const getSeriesItem = useCallback(getSeriesItemContext(), [data]);
|
||||||
|
@ -132,7 +134,8 @@ const GraphView: FC<GraphViewProps> = ({
|
||||||
<div
|
<div
|
||||||
className={classNames({
|
className={classNames({
|
||||||
"vm-graph-view": true,
|
"vm-graph-view": true,
|
||||||
"vm-graph-view_full-width": fullWidth
|
"vm-graph-view_full-width": fullWidth,
|
||||||
|
"vm-graph-view_full-width_mobile": fullWidth && isMobile
|
||||||
})}
|
})}
|
||||||
ref={containerRef}
|
ref={containerRef}
|
||||||
>
|
>
|
||||||
|
|
|
@ -9,5 +9,9 @@
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
width: calc(100vw - ($padding-medium * 2) - var(--scrollbar-width));
|
width: calc(100vw - ($padding-medium * 2) - var(--scrollbar-width));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&_mobile {
|
||||||
|
width: calc(100vw - ($padding-global * 2) - var(--scrollbar-width));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
&__copy {
|
&__copy {
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: $padding-medium;
|
top: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
|
|
|
@ -12,6 +12,7 @@ import { getNameForMetric } from "../../../utils/metric";
|
||||||
import { useCustomPanelState } from "../../../state/customPanel/CustomPanelStateContext";
|
import { useCustomPanelState } from "../../../state/customPanel/CustomPanelStateContext";
|
||||||
import "./style.scss";
|
import "./style.scss";
|
||||||
import useResize from "../../../hooks/useResize";
|
import useResize from "../../../hooks/useResize";
|
||||||
|
import useDeviceDetect from "../../../hooks/useDeviceDetect";
|
||||||
|
|
||||||
export interface GraphViewProps {
|
export interface GraphViewProps {
|
||||||
data: InstantMetricResult[];
|
data: InstantMetricResult[];
|
||||||
|
@ -20,6 +21,7 @@ export interface GraphViewProps {
|
||||||
|
|
||||||
const TableView: FC<GraphViewProps> = ({ data, displayColumns }) => {
|
const TableView: FC<GraphViewProps> = ({ data, displayColumns }) => {
|
||||||
const { showInfoMessage } = useSnack();
|
const { showInfoMessage } = useSnack();
|
||||||
|
const { isMobile } = useDeviceDetect();
|
||||||
|
|
||||||
const { tableCompact } = useCustomPanelState();
|
const { tableCompact } = useCustomPanelState();
|
||||||
const windowSize = useResize(document.body);
|
const windowSize = useResize(document.body);
|
||||||
|
@ -108,7 +110,12 @@ const TableView: FC<GraphViewProps> = ({ data, displayColumns }) => {
|
||||||
if (!rows.length) return <Alert variant="warning">No data to show</Alert>;
|
if (!rows.length) return <Alert variant="warning">No data to show</Alert>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="vm-table-view">
|
<div
|
||||||
|
className={classNames({
|
||||||
|
"vm-table-view": true,
|
||||||
|
"vm-table-view_mobile": isMobile,
|
||||||
|
})}
|
||||||
|
>
|
||||||
<table
|
<table
|
||||||
className="vm-table"
|
className="vm-table"
|
||||||
ref={tableRef}
|
ref={tableRef}
|
||||||
|
|
|
@ -5,6 +5,10 @@
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
|
||||||
|
&_mobile {
|
||||||
|
margin-top: -$padding-global;
|
||||||
|
}
|
||||||
|
|
||||||
table {
|
table {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
import React, { createContext, FC, useContext, useEffect, useState } from "preact/compat";
|
import React, { createContext, FC, useContext, useEffect, useState } from "preact/compat";
|
||||||
import Alert from "../components/Main/Alert/Alert";
|
import Alert from "../components/Main/Alert/Alert";
|
||||||
|
import useDeviceDetect from "../hooks/useDeviceDetect";
|
||||||
|
import classNames from "classnames";
|
||||||
|
import { CloseIcon } from "../components/Main/Icons";
|
||||||
|
|
||||||
export interface SnackModel {
|
export interface SnackModel {
|
||||||
message?: string;
|
message?: string;
|
||||||
|
@ -26,6 +29,8 @@ export const SnackbarContext = createContext<SnackbarContextType>({
|
||||||
export const useSnack = (): SnackbarContextType => useContext(SnackbarContext);
|
export const useSnack = (): SnackbarContextType => useContext(SnackbarContext);
|
||||||
|
|
||||||
export const SnackbarProvider: FC = ({ children }) => {
|
export const SnackbarProvider: FC = ({ children }) => {
|
||||||
|
const { isMobile } = useDeviceDetect();
|
||||||
|
|
||||||
const [snack, setSnack] = useState<SnackModel>({});
|
const [snack, setSnack] = useState<SnackModel>({});
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
|
@ -44,17 +49,28 @@ export const SnackbarProvider: FC = ({ children }) => {
|
||||||
return () => clearTimeout(timeout);
|
return () => clearTimeout(timeout);
|
||||||
}, [infoMessage]);
|
}, [infoMessage]);
|
||||||
|
|
||||||
const handleClose = (e: unknown, reason: string): void => {
|
const handleClose = () => {
|
||||||
if (reason !== "clickaway") {
|
|
||||||
setInfoMessage(undefined);
|
setInfoMessage(undefined);
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return <SnackbarContext.Provider value={{ showInfoMessage: setInfoMessage }}>
|
return <SnackbarContext.Provider value={{ showInfoMessage: setInfoMessage }}>
|
||||||
{open && <div className="vm-snackbar">
|
{open && <div
|
||||||
|
className={classNames({
|
||||||
|
"vm-snackbar": true,
|
||||||
|
"vm-snackbar_mobile": isMobile,
|
||||||
|
})}
|
||||||
|
>
|
||||||
<Alert variant={snack.variant}>
|
<Alert variant={snack.variant}>
|
||||||
{snack.message}
|
<div className="vm-snackbar-content">
|
||||||
|
<span>{snack.message}</span>
|
||||||
|
<div
|
||||||
|
className="vm-snackbar-content__close"
|
||||||
|
onClick={handleClose}
|
||||||
|
>
|
||||||
|
<CloseIcon/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</Alert>
|
</Alert>
|
||||||
</div>}
|
</div>}
|
||||||
{children}
|
{children}
|
||||||
|
|
|
@ -4,12 +4,17 @@ import useResize from "./useResize";
|
||||||
|
|
||||||
export default function useDeviceDetect() {
|
export default function useDeviceDetect() {
|
||||||
const windowSize = useResize(document.body);
|
const windowSize = useResize(document.body);
|
||||||
const [isMobile, setMobile] = useState(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
const getIsMobile = () => {
|
||||||
const mobileAgent = isMobileAgent();
|
const mobileAgent = isMobileAgent();
|
||||||
const smallWidth = window.innerWidth < 500;
|
const smallWidth = window.innerWidth < 500;
|
||||||
setMobile(mobileAgent || smallWidth);
|
return mobileAgent || smallWidth;
|
||||||
|
};
|
||||||
|
|
||||||
|
const [isMobile, setMobile] = useState(getIsMobile());
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setMobile(getIsMobile());
|
||||||
}, [windowSize]);
|
}, [windowSize]);
|
||||||
|
|
||||||
return { isMobile };
|
return { isMobile };
|
||||||
|
|
|
@ -9,6 +9,8 @@ import Button from "../../../components/Main/Button/Button";
|
||||||
import TextField from "../../../components/Main/TextField/TextField";
|
import TextField from "../../../components/Main/TextField/TextField";
|
||||||
import "./style.scss";
|
import "./style.scss";
|
||||||
import Tooltip from "../../../components/Main/Tooltip/Tooltip";
|
import Tooltip from "../../../components/Main/Tooltip/Tooltip";
|
||||||
|
import useDeviceDetect from "../../../hooks/useDeviceDetect";
|
||||||
|
import classNames from "classnames";
|
||||||
|
|
||||||
export interface CardinalityConfiguratorProps {
|
export interface CardinalityConfiguratorProps {
|
||||||
onSetHistory: (step: number) => void;
|
onSetHistory: (step: number) => void;
|
||||||
|
@ -43,6 +45,7 @@ const CardinalityConfigurator: FC<CardinalityConfiguratorProps> = ({
|
||||||
}) => {
|
}) => {
|
||||||
const { autocomplete } = useQueryState();
|
const { autocomplete } = useQueryState();
|
||||||
const queryDispatch = useQueryDispatch();
|
const queryDispatch = useQueryDispatch();
|
||||||
|
const { isMobile } = useDeviceDetect();
|
||||||
|
|
||||||
const { queryOptions } = useFetchQueryOptions();
|
const { queryOptions } = useFetchQueryOptions();
|
||||||
|
|
||||||
|
@ -60,7 +63,13 @@ const CardinalityConfigurator: FC<CardinalityConfiguratorProps> = ({
|
||||||
onSetHistory(1);
|
onSetHistory(1);
|
||||||
};
|
};
|
||||||
|
|
||||||
return <div className="vm-cardinality-configurator vm-block">
|
return <div
|
||||||
|
className={classNames({
|
||||||
|
"vm-cardinality-configurator": true,
|
||||||
|
"vm-block": true,
|
||||||
|
"vm-block_mobile": isMobile,
|
||||||
|
})}
|
||||||
|
>
|
||||||
<div className="vm-cardinality-configurator-controls">
|
<div className="vm-cardinality-configurator-controls">
|
||||||
<div className="vm-cardinality-configurator-controls__query">
|
<div className="vm-cardinality-configurator-controls__query">
|
||||||
<QueryEditor
|
<QueryEditor
|
||||||
|
@ -112,7 +121,12 @@ const CardinalityConfigurator: FC<CardinalityConfiguratorProps> = ({
|
||||||
onChange={onChangeAutocomplete}
|
onChange={onChangeAutocomplete}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="vm-cardinality-configurator-bottom">
|
<div
|
||||||
|
className={classNames({
|
||||||
|
"vm-cardinality-configurator-bottom": true,
|
||||||
|
"vm-cardinality-configurator-bottom_mobile": isMobile,
|
||||||
|
})}
|
||||||
|
>
|
||||||
<div className="vm-cardinality-configurator-bottom__info">
|
<div className="vm-cardinality-configurator-bottom__info">
|
||||||
Analyzed <b>{totalSeries}</b> series with <b>{totalLabelValuePairs}</b> "label=value" pairs
|
Analyzed <b>{totalSeries}</b> series with <b>{totalLabelValuePairs}</b> "label=value" pairs
|
||||||
at <b>{date}</b>{match && <span> for series selector <b>{match}</b></span>}.
|
at <b>{date}</b>{match && <span> for series selector <b>{match}</b></span>}.
|
||||||
|
@ -141,6 +155,7 @@ const CardinalityConfigurator: FC<CardinalityConfiguratorProps> = ({
|
||||||
<Button
|
<Button
|
||||||
startIcon={<PlayIcon/>}
|
startIcon={<PlayIcon/>}
|
||||||
onClick={onRunQuery}
|
onClick={onRunQuery}
|
||||||
|
fullWidth
|
||||||
>
|
>
|
||||||
Execute Query
|
Execute Query
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
@ -38,6 +38,10 @@
|
||||||
gap: $padding-global;
|
gap: $padding-global;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&_mobile &__docs {
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
&__info {
|
&__info {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
font-size: $font-size;
|
font-size: $font-size;
|
||||||
|
@ -50,5 +54,14 @@
|
||||||
button {
|
button {
|
||||||
margin: 0 0 0 auto;
|
margin: 0 0 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&_mobile {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
|
||||||
|
button {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,8 @@ import Tabs from "../../../components/Main/Tabs/Tabs";
|
||||||
import { useMemo } from "preact/compat";
|
import { useMemo } from "preact/compat";
|
||||||
import { ChartIcon, TableIcon } from "../../../components/Main/Icons";
|
import { ChartIcon, TableIcon } from "../../../components/Main/Icons";
|
||||||
import "./style.scss";
|
import "./style.scss";
|
||||||
|
import classNames from "classnames";
|
||||||
|
import useDeviceDetect from "../../../hooks/useDeviceDetect";
|
||||||
|
|
||||||
interface MetricsProperties {
|
interface MetricsProperties {
|
||||||
rows: Data[];
|
rows: Data[];
|
||||||
|
@ -35,6 +37,8 @@ const MetricsContent: FC<MetricsProperties> = ({
|
||||||
sectionTitle,
|
sectionTitle,
|
||||||
tableHeaderCells,
|
tableHeaderCells,
|
||||||
}) => {
|
}) => {
|
||||||
|
const { isMobile } = useDeviceDetect();
|
||||||
|
|
||||||
const tableCells = (row: Data) => (
|
const tableCells = (row: Data) => (
|
||||||
<TableCells
|
<TableCells
|
||||||
row={row}
|
row={row}
|
||||||
|
@ -54,9 +58,21 @@ const MetricsContent: FC<MetricsProperties> = ({
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="vm-metrics-content vm-block">
|
<div
|
||||||
|
className={classNames({
|
||||||
|
"vm-metrics-content": true,
|
||||||
|
"vm-metrics-content_mobile": isMobile,
|
||||||
|
"vm-block": true,
|
||||||
|
"vm-block_mobile": isMobile,
|
||||||
|
})}
|
||||||
|
>
|
||||||
<div className="vm-metrics-content-header vm-section-header">
|
<div className="vm-metrics-content-header vm-section-header">
|
||||||
<h5 className="vm-section-header__title">{sectionTitle}</h5>
|
<h5
|
||||||
|
className={classNames({
|
||||||
|
"vm-section-header__title": true,
|
||||||
|
"vm-section-header__title_mobile": isMobile,
|
||||||
|
})}
|
||||||
|
>{sectionTitle}</h5>
|
||||||
<div className="vm-section-header__tabs">
|
<div className="vm-section-header__tabs">
|
||||||
<Tabs
|
<Tabs
|
||||||
activeItem={String(activeTab)}
|
activeItem={String(activeTab)}
|
||||||
|
@ -67,7 +83,10 @@ const MetricsContent: FC<MetricsProperties> = ({
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
ref={chartContainer}
|
ref={chartContainer}
|
||||||
className="vm-metrics-content__table"
|
className={classNames({
|
||||||
|
"vm-metrics-content__table": true,
|
||||||
|
"vm-metrics-content__table_mobile": isMobile
|
||||||
|
})}
|
||||||
>
|
>
|
||||||
{activeTab === 0 && (
|
{activeTab === 0 && (
|
||||||
<EnhancedTable
|
<EnhancedTable
|
||||||
|
|
|
@ -5,6 +5,10 @@
|
||||||
margin: -$padding-medium 0-$padding-medium 0;
|
margin: -$padding-medium 0-$padding-medium 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&_mobile &-header {
|
||||||
|
margin: -$padding-global 0-$padding-global 0;
|
||||||
|
}
|
||||||
|
|
||||||
&__table {
|
&__table {
|
||||||
padding-top: $padding-medium;
|
padding-top: $padding-medium;
|
||||||
width: calc(100vw - ($padding-medium * 4) - var(--scrollbar-width));
|
width: calc(100vw - ($padding-medium * 4) - var(--scrollbar-width));
|
||||||
|
@ -14,8 +18,16 @@
|
||||||
width: calc(100vw - ($padding-medium * 2) - var(--scrollbar-width));
|
width: calc(100vw - ($padding-medium * 2) - var(--scrollbar-width));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&_mobile {
|
||||||
|
width: calc(100vw - ($padding-global * 2) - var(--scrollbar-width));
|
||||||
|
}
|
||||||
|
|
||||||
.vm-table-cell_header {
|
.vm-table-cell_header {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&_mobile &__table {
|
||||||
|
width: calc(100vw - ($padding-global * 2) - var(--scrollbar-width));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ import React, { FC, useEffect, useState, useRef, useMemo } from "preact/compat";
|
||||||
import { useSortedCategories } from "../../../../hooks/useSortedCategories";
|
import { useSortedCategories } from "../../../../hooks/useSortedCategories";
|
||||||
import { InstantMetricResult } from "../../../../api/types";
|
import { InstantMetricResult } from "../../../../api/types";
|
||||||
import Button from "../../../../components/Main/Button/Button";
|
import Button from "../../../../components/Main/Button/Button";
|
||||||
import { CloseIcon, RestartIcon, SettingsIcon } from "../../../../components/Main/Icons";
|
import { RestartIcon, SettingsIcon } from "../../../../components/Main/Icons";
|
||||||
import Popper from "../../../../components/Main/Popper/Popper";
|
import Popper from "../../../../components/Main/Popper/Popper";
|
||||||
import "./style.scss";
|
import "./style.scss";
|
||||||
import Checkbox from "../../../../components/Main/Checkbox/Checkbox";
|
import Checkbox from "../../../../components/Main/Checkbox/Checkbox";
|
||||||
|
@ -10,6 +10,8 @@ import Tooltip from "../../../../components/Main/Tooltip/Tooltip";
|
||||||
import { useCustomPanelDispatch, useCustomPanelState } from "../../../../state/customPanel/CustomPanelStateContext";
|
import { useCustomPanelDispatch, useCustomPanelState } from "../../../../state/customPanel/CustomPanelStateContext";
|
||||||
import Switch from "../../../../components/Main/Switch/Switch";
|
import Switch from "../../../../components/Main/Switch/Switch";
|
||||||
import { arrayEquals } from "../../../../utils/array";
|
import { arrayEquals } from "../../../../utils/array";
|
||||||
|
import classNames from "classnames";
|
||||||
|
import useDeviceDetect from "../../../../hooks/useDeviceDetect";
|
||||||
|
|
||||||
const title = "Table settings";
|
const title = "Table settings";
|
||||||
|
|
||||||
|
@ -20,6 +22,7 @@ interface TableSettingsProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
const TableSettings: FC<TableSettingsProps> = ({ data, defaultColumns = [], onChange }) => {
|
const TableSettings: FC<TableSettingsProps> = ({ data, defaultColumns = [], onChange }) => {
|
||||||
|
const { isMobile } = useDeviceDetect();
|
||||||
|
|
||||||
const { tableCompact } = useCustomPanelState();
|
const { tableCompact } = useCustomPanelState();
|
||||||
const customPanelDispatch = useCustomPanelDispatch();
|
const customPanelDispatch = useCustomPanelDispatch();
|
||||||
|
@ -79,20 +82,15 @@ const TableSettings: FC<TableSettingsProps> = ({ data, defaultColumns = [], onCh
|
||||||
onClose={handleClose}
|
onClose={handleClose}
|
||||||
placement="bottom-right"
|
placement="bottom-right"
|
||||||
buttonRef={buttonRef}
|
buttonRef={buttonRef}
|
||||||
|
title={title}
|
||||||
>
|
>
|
||||||
<div className="vm-table-settings-popper">
|
<div
|
||||||
<div className="vm-popper-header">
|
className={classNames({
|
||||||
<h3 className="vm-popper-header__title">
|
"vm-table-settings-popper": true,
|
||||||
{title}
|
"vm-table-settings-popper_mobile": isMobile
|
||||||
</h3>
|
})}
|
||||||
<Button
|
>
|
||||||
onClick={handleClose}
|
<div className="vm-table-settings-popper-list vm-table-settings-popper-list_first">
|
||||||
startIcon={<CloseIcon/>}
|
|
||||||
size="small"
|
|
||||||
variant="text"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="vm-table-settings-popper-list">
|
|
||||||
<Switch
|
<Switch
|
||||||
label={"Compact view"}
|
label={"Compact view"}
|
||||||
value={tableCompact}
|
value={tableCompact}
|
||||||
|
|
|
@ -4,6 +4,14 @@
|
||||||
display: grid;
|
display: grid;
|
||||||
min-width: 250px;
|
min-width: 250px;
|
||||||
|
|
||||||
|
&_mobile &-list {
|
||||||
|
gap: $padding-global;
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&-list {
|
&-list {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: $padding-small;
|
gap: $padding-small;
|
||||||
|
@ -12,6 +20,10 @@
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
border-bottom: $border-divider;
|
border-bottom: $border-divider;
|
||||||
|
|
||||||
|
&_first {
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
&-header {
|
&-header {
|
||||||
display: grid;
|
display: grid;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
|
@ -10,11 +10,14 @@ import { DefaultActiveTab, Tabs, TSDBStatus, Containers } from "./types";
|
||||||
import { useSetQueryParams } from "./hooks/useSetQueryParams";
|
import { useSetQueryParams } from "./hooks/useSetQueryParams";
|
||||||
import Alert from "../../components/Main/Alert/Alert";
|
import Alert from "../../components/Main/Alert/Alert";
|
||||||
import "./style.scss";
|
import "./style.scss";
|
||||||
|
import classNames from "classnames";
|
||||||
|
import useDeviceDetect from "../../hooks/useDeviceDetect";
|
||||||
|
|
||||||
const spinnerMessage = `Please wait while cardinality stats is calculated.
|
const spinnerMessage = `Please wait while cardinality stats is calculated.
|
||||||
This may take some time if the db contains big number of time series.`;
|
This may take some time if the db contains big number of time series.`;
|
||||||
|
|
||||||
const Index: FC = () => {
|
const Index: FC = () => {
|
||||||
|
const { isMobile } = useDeviceDetect();
|
||||||
const { topN, match, date, focusLabel } = useCardinalityState();
|
const { topN, match, date, focusLabel } = useCardinalityState();
|
||||||
const cardinalityDispatch = useCardinalityDispatch();
|
const cardinalityDispatch = useCardinalityDispatch();
|
||||||
useSetQueryParams();
|
useSetQueryParams();
|
||||||
|
@ -70,7 +73,12 @@ const Index: FC = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="vm-cardinality-panel">
|
<div
|
||||||
|
className={classNames({
|
||||||
|
"vm-cardinality-panel": true,
|
||||||
|
"vm-cardinality-panel_mobile": isMobile
|
||||||
|
})}
|
||||||
|
>
|
||||||
{isLoading && <Spinner message={spinnerMessage}/>}
|
{isLoading && <Spinner message={spinnerMessage}/>}
|
||||||
<CardinalityConfigurator
|
<CardinalityConfigurator
|
||||||
error={configError}
|
error={configError}
|
||||||
|
|
|
@ -4,4 +4,8 @@
|
||||||
display: grid;
|
display: grid;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
gap: $padding-medium;
|
gap: $padding-medium;
|
||||||
|
|
||||||
|
&_mobile {
|
||||||
|
gap: $padding-small;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ import Tooltip from "../../../components/Main/Tooltip/Tooltip";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { MouseEvent as ReactMouseEvent } from "react";
|
import { MouseEvent as ReactMouseEvent } from "react";
|
||||||
import { arrayEquals } from "../../../utils/array";
|
import { arrayEquals } from "../../../utils/array";
|
||||||
|
import useDeviceDetect from "../../../hooks/useDeviceDetect";
|
||||||
|
|
||||||
export interface QueryConfiguratorProps {
|
export interface QueryConfiguratorProps {
|
||||||
error?: ErrorTypes | string;
|
error?: ErrorTypes | string;
|
||||||
|
@ -21,6 +22,7 @@ export interface QueryConfiguratorProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
const QueryConfigurator: FC<QueryConfiguratorProps> = ({ error, queryOptions, onHideQuery }) => {
|
const QueryConfigurator: FC<QueryConfiguratorProps> = ({ error, queryOptions, onHideQuery }) => {
|
||||||
|
const { isMobile } = useDeviceDetect();
|
||||||
|
|
||||||
const { query, queryHistory, autocomplete } = useQueryState();
|
const { query, queryHistory, autocomplete } = useQueryState();
|
||||||
const queryDispatch = useQueryDispatch();
|
const queryDispatch = useQueryDispatch();
|
||||||
|
@ -111,13 +113,20 @@ const QueryConfigurator: FC<QueryConfiguratorProps> = ({ error, queryOptions, on
|
||||||
onHideQuery(hideQuery);
|
onHideQuery(hideQuery);
|
||||||
}, [hideQuery]);
|
}, [hideQuery]);
|
||||||
|
|
||||||
return <div className="vm-query-configurator vm-block">
|
return <div
|
||||||
|
className={classNames({
|
||||||
|
"vm-query-configurator": true,
|
||||||
|
"vm-block": true,
|
||||||
|
"vm-block_mobile": isMobile
|
||||||
|
})}
|
||||||
|
>
|
||||||
<div className="vm-query-configurator-list">
|
<div className="vm-query-configurator-list">
|
||||||
{stateQuery.map((q, i) => (
|
{stateQuery.map((q, i) => (
|
||||||
<div
|
<div
|
||||||
className={classNames({
|
className={classNames({
|
||||||
"vm-query-configurator-list-row": true,
|
"vm-query-configurator-list-row": true,
|
||||||
"vm-query-configurator-list-row_disabled": hideQuery.includes(i)
|
"vm-query-configurator-list-row_disabled": hideQuery.includes(i),
|
||||||
|
"vm-query-configurator-list-row_mobile": isMobile
|
||||||
})}
|
})}
|
||||||
key={i}
|
key={i}
|
||||||
>
|
>
|
||||||
|
@ -175,7 +184,7 @@ const QueryConfigurator: FC<QueryConfiguratorProps> = ({ error, queryOptions, on
|
||||||
onClick={onRunQuery}
|
onClick={onRunQuery}
|
||||||
startIcon={<PlayIcon/>}
|
startIcon={<PlayIcon/>}
|
||||||
>
|
>
|
||||||
Execute Query
|
{isMobile ? "Execute" : "Execute Query"}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
.vm-query-configurator {
|
.vm-query-configurator {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: $padding-small;
|
gap: $padding-global;
|
||||||
|
|
||||||
&-list {
|
&-list {
|
||||||
display: grid;
|
display: grid;
|
||||||
|
@ -13,6 +13,10 @@
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: $padding-small;
|
gap: $padding-small;
|
||||||
|
|
||||||
|
&_mobile {
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
&_disabled {
|
&_disabled {
|
||||||
filter: grayscale(100%);
|
filter: grayscale(100%);
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
|
@ -33,22 +37,12 @@
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
gap: $padding-medium;
|
gap: $padding-medium;
|
||||||
|
|
||||||
@media (max-width: 500px) {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1fr;
|
|
||||||
align-items: flex-end;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__buttons {
|
&__buttons {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(2, auto);
|
grid-template-columns: repeat(2, auto);
|
||||||
gap: $padding-small;
|
gap: $padding-small;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
|
|
||||||
@media (max-width: 500px) {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,12 +20,15 @@ import "./style.scss";
|
||||||
import Alert from "../../components/Main/Alert/Alert";
|
import Alert from "../../components/Main/Alert/Alert";
|
||||||
import TableView from "../../components/Views/TableView/TableView";
|
import TableView from "../../components/Views/TableView/TableView";
|
||||||
import Button from "../../components/Main/Button/Button";
|
import Button from "../../components/Main/Button/Button";
|
||||||
|
import classNames from "classnames";
|
||||||
|
import useDeviceDetect from "../../hooks/useDeviceDetect";
|
||||||
|
|
||||||
const CustomPanel: FC = () => {
|
const CustomPanel: FC = () => {
|
||||||
const { displayType, isTracingEnabled } = useCustomPanelState();
|
const { displayType, isTracingEnabled } = useCustomPanelState();
|
||||||
const { query } = useQueryState();
|
const { query } = useQueryState();
|
||||||
const { period } = useTimeState();
|
const { period } = useTimeState();
|
||||||
const timeDispatch = useTimeDispatch();
|
const timeDispatch = useTimeDispatch();
|
||||||
|
const { isMobile } = useDeviceDetect();
|
||||||
useSetQueryParams();
|
useSetQueryParams();
|
||||||
|
|
||||||
const [displayColumns, setDisplayColumns] = useState<string[]>();
|
const [displayColumns, setDisplayColumns] = useState<string[]>();
|
||||||
|
@ -84,7 +87,12 @@ const CustomPanel: FC = () => {
|
||||||
}, [query]);
|
}, [query]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="vm-custom-panel">
|
<div
|
||||||
|
className={classNames({
|
||||||
|
"vm-custom-panel": true,
|
||||||
|
"vm-custom-panel_mobile": isMobile,
|
||||||
|
})}
|
||||||
|
>
|
||||||
<QueryConfigurator
|
<QueryConfigurator
|
||||||
error={error}
|
error={error}
|
||||||
queryOptions={queryOptions}
|
queryOptions={queryOptions}
|
||||||
|
@ -101,7 +109,12 @@ const CustomPanel: FC = () => {
|
||||||
{isLoading && <Spinner />}
|
{isLoading && <Spinner />}
|
||||||
{error && <Alert variant="error">{error}</Alert>}
|
{error && <Alert variant="error">{error}</Alert>}
|
||||||
{warning && <Alert variant="warning">
|
{warning && <Alert variant="warning">
|
||||||
<div className="vm-custom-panel__warning">
|
<div
|
||||||
|
className={classNames({
|
||||||
|
"vm-custom-panel__warning": true,
|
||||||
|
"vm-custom-panel__warning_mobile": isMobile
|
||||||
|
})}
|
||||||
|
>
|
||||||
<p>{warning}</p>
|
<p>{warning}</p>
|
||||||
<Button
|
<Button
|
||||||
color="warning"
|
color="warning"
|
||||||
|
@ -112,7 +125,14 @@ const CustomPanel: FC = () => {
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</Alert>}
|
</Alert>}
|
||||||
<div className="vm-custom-panel-body vm-block">
|
<div
|
||||||
|
className={classNames({
|
||||||
|
"vm-custom-panel-body": true,
|
||||||
|
"vm-custom-panel-body_mobile": isMobile,
|
||||||
|
"vm-block": true,
|
||||||
|
"vm-block_mobile": isMobile,
|
||||||
|
})}
|
||||||
|
>
|
||||||
<div className="vm-custom-panel-body-header">
|
<div className="vm-custom-panel-body-header">
|
||||||
<DisplayTypeSwitch/>
|
<DisplayTypeSwitch/>
|
||||||
{displayType === "chart" && (
|
{displayType === "chart" && (
|
||||||
|
@ -139,6 +159,7 @@ const CustomPanel: FC = () => {
|
||||||
yaxis={yaxis}
|
yaxis={yaxis}
|
||||||
setYaxisLimits={setYaxisLimits}
|
setYaxisLimits={setYaxisLimits}
|
||||||
setPeriod={setPeriod}
|
setPeriod={setPeriod}
|
||||||
|
height={isMobile ? window.innerHeight * 0.5 : 500}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{liveData && (displayType === "code") && (
|
{liveData && (displayType === "code") && (
|
||||||
|
|
|
@ -7,11 +7,20 @@
|
||||||
gap: $padding-medium;
|
gap: $padding-medium;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
||||||
|
&_mobile {
|
||||||
|
gap: $padding-small;
|
||||||
|
}
|
||||||
|
|
||||||
&__warning {
|
&__warning {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr auto;
|
grid-template-columns: 1fr auto;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
gap: $padding-small;
|
||||||
|
|
||||||
|
&_mobile {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__trace {
|
&__trace {
|
||||||
|
@ -31,5 +40,10 @@
|
||||||
border-bottom: $border-divider;
|
border-bottom: $border-divider;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&_mobile &-header {
|
||||||
|
margin: -$padding-global 0-$padding-global $padding-global;
|
||||||
|
padding: 0 $padding-global;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -96,6 +96,7 @@ const ExploreMetrics: FC = () => {
|
||||||
job={job}
|
job={job}
|
||||||
instance={instance}
|
instance={instance}
|
||||||
index={i}
|
index={i}
|
||||||
|
length={metrics.length}
|
||||||
size={size}
|
size={size}
|
||||||
onRemoveItem={handleToggleMetric}
|
onRemoveItem={handleToggleMetric}
|
||||||
onChangeOrder={handleChangeOrder}
|
onChangeOrder={handleChangeOrder}
|
||||||
|
|
|
@ -5,9 +5,17 @@
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
gap: $padding-medium;
|
gap: $padding-medium;
|
||||||
|
|
||||||
|
@media (max-width: 500px) {
|
||||||
|
gap: $padding-small;
|
||||||
|
}
|
||||||
|
|
||||||
&-body {
|
&-body {
|
||||||
display: grid;
|
display: grid;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
gap: $padding-medium;
|
gap: $padding-medium;
|
||||||
|
|
||||||
|
@media (max-width: 500px) {
|
||||||
|
gap: $padding-small;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ import "./style.scss";
|
||||||
import Alert from "../../../components/Main/Alert/Alert";
|
import Alert from "../../../components/Main/Alert/Alert";
|
||||||
import Tooltip from "../../../components/Main/Tooltip/Tooltip";
|
import Tooltip from "../../../components/Main/Tooltip/Tooltip";
|
||||||
import { useGraphState } from "../../../state/graph/GraphStateContext";
|
import { useGraphState } from "../../../state/graph/GraphStateContext";
|
||||||
|
import useDeviceDetect from "../../../hooks/useDeviceDetect";
|
||||||
|
|
||||||
export interface PredefinedPanelsProps extends PanelSettings {
|
export interface PredefinedPanelsProps extends PanelSettings {
|
||||||
filename: string;
|
filename: string;
|
||||||
|
@ -26,7 +27,7 @@ const PredefinedPanel: FC<PredefinedPanelsProps> = ({
|
||||||
filename,
|
filename,
|
||||||
alias
|
alias
|
||||||
}) => {
|
}) => {
|
||||||
|
const { isMobile } = useDeviceDetect();
|
||||||
const { period } = useTimeState();
|
const { period } = useTimeState();
|
||||||
const { customStep } = useGraphState();
|
const { customStep } = useGraphState();
|
||||||
const dispatch = useTimeDispatch();
|
const dispatch = useTimeDispatch();
|
||||||
|
@ -138,6 +139,7 @@ const PredefinedPanel: FC<PredefinedPanelsProps> = ({
|
||||||
setYaxisLimits={setYaxisLimits}
|
setYaxisLimits={setYaxisLimits}
|
||||||
setPeriod={setPeriod}
|
setPeriod={setPeriod}
|
||||||
fullWidth={false}
|
fullWidth={false}
|
||||||
|
height={isMobile ? window.innerHeight * 0.5 : 500}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -38,5 +38,9 @@
|
||||||
&-body {
|
&-body {
|
||||||
padding: $padding-small $padding-small*2;
|
padding: $padding-small $padding-small*2;
|
||||||
min-height: 500px;
|
min-height: 500px;
|
||||||
|
|
||||||
|
@media (max-width: 500px) {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,9 +6,11 @@ import classNames from "classnames";
|
||||||
import "./style.scss";
|
import "./style.scss";
|
||||||
import { useDashboardsState } from "../../state/dashboards/DashboardsStateContext";
|
import { useDashboardsState } from "../../state/dashboards/DashboardsStateContext";
|
||||||
import Spinner from "../../components/Main/Spinner/Spinner";
|
import Spinner from "../../components/Main/Spinner/Spinner";
|
||||||
|
import useDeviceDetect from "../../hooks/useDeviceDetect";
|
||||||
|
|
||||||
const DashboardsLayout: FC = () => {
|
const DashboardsLayout: FC = () => {
|
||||||
useSetQueryParams();
|
useSetQueryParams();
|
||||||
|
const { isMobile } = useDeviceDetect();
|
||||||
const { dashboardsSettings, dashboardsLoading, dashboardsError } = useDashboardsState();
|
const { dashboardsSettings, dashboardsLoading, dashboardsError } = useDashboardsState();
|
||||||
const [dashboard, setDashboard] = useState(0);
|
const [dashboard, setDashboard] = useState(0);
|
||||||
|
|
||||||
|
@ -35,7 +37,13 @@ const DashboardsLayout: FC = () => {
|
||||||
{dashboardsError && <Alert variant="error">{dashboardsError}</Alert>}
|
{dashboardsError && <Alert variant="error">{dashboardsError}</Alert>}
|
||||||
{!dashboardsSettings.length && <Alert variant="info">Dashboards not found</Alert>}
|
{!dashboardsSettings.length && <Alert variant="info">Dashboards not found</Alert>}
|
||||||
{dashboards.length > 1 && (
|
{dashboards.length > 1 && (
|
||||||
<div className="vm-predefined-panels-tabs vm-block">
|
<div
|
||||||
|
className={classNames({
|
||||||
|
"vm-predefined-panels-tabs": true,
|
||||||
|
"vm-block": true,
|
||||||
|
"vm-block_mobile": isMobile,
|
||||||
|
})}
|
||||||
|
>
|
||||||
{dashboards.map(tab => (
|
{dashboards.map(tab => (
|
||||||
<div
|
<div
|
||||||
key={tab.value}
|
key={tab.value}
|
||||||
|
|
|
@ -9,6 +9,10 @@
|
||||||
padding: $padding-medium 0;
|
padding: $padding-medium 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 500px) {
|
||||||
|
padding: $padding-small 0;
|
||||||
|
}
|
||||||
|
|
||||||
&-tabs.vm-block {
|
&-tabs.vm-block {
|
||||||
padding: $padding-global;
|
padding: $padding-global;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,8 @@ import { CodeIcon, TableIcon } from "../../../components/Main/Icons";
|
||||||
import Tabs from "../../../components/Main/Tabs/Tabs";
|
import Tabs from "../../../components/Main/Tabs/Tabs";
|
||||||
import TopQueryTable from "../TopQueryTable/TopQueryTable";
|
import TopQueryTable from "../TopQueryTable/TopQueryTable";
|
||||||
import "./style.scss";
|
import "./style.scss";
|
||||||
|
import classNames from "classnames";
|
||||||
|
import useDeviceDetect from "../../../hooks/useDeviceDetect";
|
||||||
|
|
||||||
export interface TopQueryPanelProps {
|
export interface TopQueryPanelProps {
|
||||||
rows: TopQuery[],
|
rows: TopQuery[],
|
||||||
|
@ -19,7 +21,7 @@ const tabs = ["table", "JSON"].map((t, i) => ({
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const TopQueryPanel: FC<TopQueryPanelProps> = ({ rows, title, columns, defaultOrderBy }) => {
|
const TopQueryPanel: FC<TopQueryPanelProps> = ({ rows, title, columns, defaultOrderBy }) => {
|
||||||
|
const { isMobile } = useDeviceDetect();
|
||||||
const [activeTab, setActiveTab] = useState(0);
|
const [activeTab, setActiveTab] = useState(0);
|
||||||
|
|
||||||
const handleChangeTab = (val: string) => {
|
const handleChangeTab = (val: string) => {
|
||||||
|
@ -27,10 +29,26 @@ const TopQueryPanel: FC<TopQueryPanelProps> = ({ rows, title, columns, defaultOr
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="vm-top-queries-panel vm-block">
|
<div
|
||||||
|
className={classNames({
|
||||||
<div className="vm-top-queries-panel-header vm-section-header">
|
"vm-top-queries-panel": true,
|
||||||
<h5 className="vm-section-header__title">{title}</h5>
|
"vm-block": true,
|
||||||
|
"vm-block_mobile": isMobile,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={classNames({
|
||||||
|
"vm-top-queries-panel-header": true,
|
||||||
|
"vm-section-header": true,
|
||||||
|
"vm-top-queries-panel-header_mobile": isMobile,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<h5
|
||||||
|
className={classNames({
|
||||||
|
"vm-section-header__title": true,
|
||||||
|
"vm-section-header__title_mobile": isMobile,
|
||||||
|
})}
|
||||||
|
>{title}</h5>
|
||||||
<div className="vm-section-header__tabs">
|
<div className="vm-section-header__tabs">
|
||||||
<Tabs
|
<Tabs
|
||||||
activeItem={String(activeTab)}
|
activeItem={String(activeTab)}
|
||||||
|
@ -40,7 +58,12 @@ const TopQueryPanel: FC<TopQueryPanelProps> = ({ rows, title, columns, defaultOr
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="vm-top-queries-panel__table">
|
<div
|
||||||
|
className={classNames({
|
||||||
|
"vm-top-queries-panel__table": true,
|
||||||
|
"vm-top-queries-panel__table_mobile": isMobile,
|
||||||
|
})}
|
||||||
|
>
|
||||||
{activeTab === 0 && (
|
{activeTab === 0 && (
|
||||||
<TopQueryTable
|
<TopQueryTable
|
||||||
rows={rows}
|
rows={rows}
|
||||||
|
|
|
@ -3,6 +3,10 @@
|
||||||
.vm-top-queries-panel {
|
.vm-top-queries-panel {
|
||||||
&-header {
|
&-header {
|
||||||
margin: -$padding-medium 0-$padding-medium 0;
|
margin: -$padding-medium 0-$padding-medium 0;
|
||||||
|
|
||||||
|
&_mobile {
|
||||||
|
margin: -$padding-global 0-$padding-global 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__table {
|
&__table {
|
||||||
|
@ -14,6 +18,10 @@
|
||||||
width: calc(100vw - ($padding-medium * 2) - var(--scrollbar-width));
|
width: calc(100vw - ($padding-medium * 2) - var(--scrollbar-width));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&_mobile {
|
||||||
|
width: calc(100vw - ($padding-global * 2) - var(--scrollbar-width));
|
||||||
|
}
|
||||||
|
|
||||||
.vm-table-cell_header {
|
.vm-table-cell_header {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,10 +14,14 @@ import TextField from "../../components/Main/TextField/TextField";
|
||||||
import Alert from "../../components/Main/Alert/Alert";
|
import Alert from "../../components/Main/Alert/Alert";
|
||||||
import Tooltip from "../../components/Main/Tooltip/Tooltip";
|
import Tooltip from "../../components/Main/Tooltip/Tooltip";
|
||||||
import "./style.scss";
|
import "./style.scss";
|
||||||
|
import useDeviceDetect from "../../hooks/useDeviceDetect";
|
||||||
|
import classNames from "classnames";
|
||||||
|
|
||||||
const exampleDuration = "30ms, 15s, 3d4h, 1y2w";
|
const exampleDuration = "30ms, 15s, 3d4h, 1y2w";
|
||||||
|
|
||||||
const Index: FC = () => {
|
const Index: FC = () => {
|
||||||
|
const { isMobile } = useDeviceDetect();
|
||||||
|
|
||||||
const { data, error, loading } = useFetchTopQueries();
|
const { data, error, loading } = useFetchTopQueries();
|
||||||
const { topN, maxLifetime } = useTopQueriesState();
|
const { topN, maxLifetime } = useTopQueriesState();
|
||||||
const topQueriesDispatch = useTopQueriesDispatch();
|
const topQueriesDispatch = useTopQueriesDispatch();
|
||||||
|
@ -67,10 +71,21 @@ const Index: FC = () => {
|
||||||
}, [data]);
|
}, [data]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="vm-top-queries">
|
<div
|
||||||
|
className={classNames({
|
||||||
|
"vm-top-queries": true,
|
||||||
|
"vm-top-queries_mobile": isMobile,
|
||||||
|
})}
|
||||||
|
>
|
||||||
{loading && <Spinner containerStyles={{ height: "500px" }}/>}
|
{loading && <Spinner containerStyles={{ height: "500px" }}/>}
|
||||||
|
|
||||||
<div className="vm-top-queries-controls vm-block">
|
<div
|
||||||
|
className={classNames({
|
||||||
|
"vm-top-queries-controls": true,
|
||||||
|
"vm-block": true,
|
||||||
|
"vm-block_mobile": isMobile,
|
||||||
|
})}
|
||||||
|
>
|
||||||
<div className="vm-top-queries-controls-fields">
|
<div className="vm-top-queries-controls-fields">
|
||||||
<div className="vm-top-queries-controls-fields__item">
|
<div className="vm-top-queries-controls-fields__item">
|
||||||
<TextField
|
<TextField
|
||||||
|
@ -93,7 +108,12 @@ const Index: FC = () => {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="vm-top-queries-controls-bottom">
|
<div
|
||||||
|
className={classNames({
|
||||||
|
"vm-top-queries-controls-bottom": true,
|
||||||
|
"vm-top-queries-controls-bottom_mobile": isMobile,
|
||||||
|
})}
|
||||||
|
>
|
||||||
<div className="vm-top-queries-controls-bottom__info">
|
<div className="vm-top-queries-controls-bottom__info">
|
||||||
VictoriaMetrics tracks the last
|
VictoriaMetrics tracks the last
|
||||||
<Tooltip title="search.queryStats.lastQueriesCount">
|
<Tooltip title="search.queryStats.lastQueriesCount">
|
||||||
|
|
|
@ -5,6 +5,10 @@
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
gap: $padding-medium;
|
gap: $padding-medium;
|
||||||
|
|
||||||
|
&_mobile {
|
||||||
|
gap: $padding-small;
|
||||||
|
}
|
||||||
|
|
||||||
&-controls {
|
&-controls {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: $padding-small;
|
gap: $padding-small;
|
||||||
|
@ -28,6 +32,11 @@
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
gap: $padding-medium;
|
gap: $padding-medium;
|
||||||
|
|
||||||
|
&_mobile {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: $padding-small;
|
||||||
|
}
|
||||||
|
|
||||||
&__info {
|
&__info {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -80,6 +80,7 @@ const JsonForm: FC<JsonFormProps> = ({
|
||||||
className={classNames({
|
className={classNames({
|
||||||
"vm-json-form": true,
|
"vm-json-form": true,
|
||||||
"vm-json-form_one-field": !displayTitle,
|
"vm-json-form_one-field": !displayTitle,
|
||||||
|
"vm-json-form_one-field_mobile": !displayTitle && isMobile,
|
||||||
"vm-json-form_mobile": isMobile
|
"vm-json-form_mobile": isMobile
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
|
|
|
@ -12,14 +12,15 @@
|
||||||
&_mobile {
|
&_mobile {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-height: 100%;
|
min-height: 100%;
|
||||||
grid-template-rows: auto 1fr auto;
|
grid-template-rows: auto calc(($vh * 100) - 200px - ($padding-global*3)) auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
&_one-field {
|
&_one-field {
|
||||||
grid-template-rows: calc(($vh * 70) - 78px - ($padding-medium*3)) auto;
|
grid-template-rows: calc(($vh * 70) - 78px - ($padding-medium*3)) auto;
|
||||||
}
|
|
||||||
|
|
||||||
.vm-text-field_textarea {
|
&_mobile {
|
||||||
|
grid-template-rows: calc(($vh * 100) - 160px - ($padding-global*2)) auto;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
textarea {
|
textarea {
|
||||||
|
|
|
@ -6,6 +6,11 @@
|
||||||
border-radius: $border-radius-medium;
|
border-radius: $border-radius-medium;
|
||||||
box-shadow: $box-shadow;
|
box-shadow: $box-shadow;
|
||||||
|
|
||||||
|
&_mobile {
|
||||||
|
padding: $padding-global;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
&_empty-padding {
|
&_empty-padding {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,10 @@
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
transition: background-color 200ms ease;
|
transition: background-color 200ms ease;
|
||||||
|
|
||||||
|
&_mobile {
|
||||||
|
padding: $padding-global $padding-global;
|
||||||
|
}
|
||||||
|
|
||||||
&:hover,
|
&:hover,
|
||||||
&_active {
|
&_active {
|
||||||
background-color: $color-hover-black;
|
background-color: $color-hover-black;
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue