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