mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2024-11-21 14:44:00 +00:00
vmui: improvements to the UI styles (#3704)
* feat: add dark theme * update packages * feat: add multilevel menu (#3678) * fix: correct styles * fix: update link to cardinality-explorer * fix: remove unused scss variables * docs/CHANGELOG.md: document the changes Co-authored-by: Aliaksandr Valialkin <valyala@victoriametrics.com>
This commit is contained in:
parent
1a8875b417
commit
20ad848c5d
59 changed files with 1493 additions and 658 deletions
1246
app/vmui/packages/vmui/package-lock.json
generated
1246
app/vmui/packages/vmui/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -17,15 +17,11 @@ const App: FC = () => {
|
||||||
|
|
||||||
const [loadingTheme, setLoadingTheme] = useState(true);
|
const [loadingTheme, setLoadingTheme] = useState(true);
|
||||||
|
|
||||||
if (loadingTheme) return (
|
|
||||||
<>
|
|
||||||
<Spinner/>
|
|
||||||
<ThemeProvider setLoadingTheme={setLoadingTheme}/>;
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
|
|
||||||
return <>
|
return <>
|
||||||
<HashRouter>
|
{loadingTheme && <Spinner/>}
|
||||||
|
<ThemeProvider setLoadingTheme={setLoadingTheme}/>
|
||||||
|
|
||||||
|
<HashRouter key={`${loadingTheme}`}>
|
||||||
<AppContextProvider>
|
<AppContextProvider>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route
|
<Route
|
||||||
|
|
|
@ -3,11 +3,13 @@ import uPlot, { Options as uPlotOptions } from "uplot";
|
||||||
import useResize from "../../../hooks/useResize";
|
import useResize from "../../../hooks/useResize";
|
||||||
import { BarChartProps } from "./types";
|
import { BarChartProps } from "./types";
|
||||||
import "./style.scss";
|
import "./style.scss";
|
||||||
|
import { useAppState } from "../../../state/common/StateContext";
|
||||||
|
|
||||||
const BarChart: FC<BarChartProps> = ({
|
const BarChart: FC<BarChartProps> = ({
|
||||||
data,
|
data,
|
||||||
container,
|
container,
|
||||||
configs }) => {
|
configs }) => {
|
||||||
|
const { darkTheme } = useAppState();
|
||||||
|
|
||||||
const uPlotRef = useRef<HTMLDivElement>(null);
|
const uPlotRef = useRef<HTMLDivElement>(null);
|
||||||
const [uPlotInst, setUPlotInst] = useState<uPlot>();
|
const [uPlotInst, setUPlotInst] = useState<uPlot>();
|
||||||
|
@ -28,7 +30,7 @@ const BarChart: FC<BarChartProps> = ({
|
||||||
const u = new uPlot(options, data, uPlotRef.current);
|
const u = new uPlot(options, data, uPlotRef.current);
|
||||||
setUPlotInst(u);
|
setUPlotInst(u);
|
||||||
return u.destroy;
|
return u.destroy;
|
||||||
}, [uPlotRef.current, layoutSize]);
|
}, [uPlotRef.current, layoutSize, darkTheme]);
|
||||||
|
|
||||||
useEffect(() => updateChart(), [data]);
|
useEffect(() => updateChart(), [data]);
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,6 @@ $chart-tooltip-y: -1 * ($padding-small + $chart-tooltip-half-icon);
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
|
||||||
&_sticky {
|
&_sticky {
|
||||||
background-color: $color-dove-gray;
|
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
z-index: 99;
|
z-index: 99;
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ import "./style.scss";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import ChartTooltip, { ChartTooltipProps } from "../ChartTooltip/ChartTooltip";
|
import ChartTooltip, { ChartTooltipProps } from "../ChartTooltip/ChartTooltip";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
|
import { useAppState } from "../../../state/common/StateContext";
|
||||||
|
|
||||||
export interface LineChartProps {
|
export interface LineChartProps {
|
||||||
metrics: MetricResult[];
|
metrics: MetricResult[];
|
||||||
|
@ -47,6 +48,8 @@ const LineChart: FC<LineChartProps> = ({
|
||||||
container,
|
container,
|
||||||
height
|
height
|
||||||
}) => {
|
}) => {
|
||||||
|
const { darkTheme } = useAppState();
|
||||||
|
|
||||||
const uPlotRef = useRef<HTMLDivElement>(null);
|
const uPlotRef = useRef<HTMLDivElement>(null);
|
||||||
const [isPanning, setPanning] = useState(false);
|
const [isPanning, setPanning] = useState(false);
|
||||||
const [xRange, setXRange] = useState({ min: period.start, max: period.end });
|
const [xRange, setXRange] = useState({ min: period.start, max: period.end });
|
||||||
|
@ -222,7 +225,7 @@ const LineChart: FC<LineChartProps> = ({
|
||||||
setUPlotInst(u);
|
setUPlotInst(u);
|
||||||
setXRange({ min: period.start, max: period.end });
|
setXRange({ min: period.start, max: period.end });
|
||||||
return u.destroy;
|
return u.destroy;
|
||||||
}, [uPlotRef.current, series, layoutSize, height]);
|
}, [uPlotRef.current, series, layoutSize, height, darkTheme]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
window.addEventListener("keydown", handleKeyDown);
|
window.addEventListener("keydown", handleKeyDown);
|
||||||
|
|
|
@ -13,6 +13,7 @@ import { getAppModeEnable } from "../../../utils/app-mode";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import Timezones from "./Timezones/Timezones";
|
import Timezones from "./Timezones/Timezones";
|
||||||
import { useTimeDispatch, useTimeState } from "../../../state/time/TimeStateContext";
|
import { useTimeDispatch, useTimeState } from "../../../state/time/TimeStateContext";
|
||||||
|
import ThemeControl from "../ThemeControl/ThemeControl";
|
||||||
|
|
||||||
const title = "Settings";
|
const title = "Settings";
|
||||||
|
|
||||||
|
@ -82,6 +83,9 @@ const GlobalSettings: FC = () => {
|
||||||
onChange={setTimezone}
|
onChange={setTimezone}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="vm-server-configurator__input">
|
||||||
|
<ThemeControl/>
|
||||||
|
</div>
|
||||||
<div className="vm-server-configurator__footer">
|
<div className="vm-server-configurator__footer">
|
||||||
<Button
|
<Button
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
background-color: rgba($color-black, 0.06);
|
background-color: $color-hover-black;
|
||||||
padding: calc($padding-small/2);
|
padding: calc($padding-small/2);
|
||||||
border-radius: $border-radius-small;
|
border-radius: $border-radius-small;
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,6 +57,7 @@ const GraphSettings: FC<GraphSettingsProps> = ({ yaxis, setYaxisLimits, toggleEn
|
||||||
</h3>
|
</h3>
|
||||||
<Button
|
<Button
|
||||||
size="small"
|
size="small"
|
||||||
|
variant="text"
|
||||||
startIcon={<CloseIcon/>}
|
startIcon={<CloseIcon/>}
|
||||||
onClick={handleClose}
|
onClick={handleClose}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -158,7 +158,7 @@ const StepConfigurator: FC = () => {
|
||||||
className="vm-link vm-link_colored"
|
className="vm-link vm-link_colored"
|
||||||
href="https://docs.victoriametrics.com/keyConcepts.html#range-query"
|
href="https://docs.victoriametrics.com/keyConcepts.html#range-query"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer"
|
rel="help noreferrer"
|
||||||
>
|
>
|
||||||
Read more about Range query
|
Read more about Range query
|
||||||
</a>
|
</a>
|
||||||
|
|
|
@ -29,7 +29,7 @@
|
||||||
padding: 0.2em 0.4em;
|
padding: 0.2em 0.4em;
|
||||||
margin: 0 0.2em;
|
margin: 0 0.2em;
|
||||||
font-size: 85%;
|
font-size: 85%;
|
||||||
background-color: rgba($color-black, 0.05);
|
background-color: $color-hover-black;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
import React from "react";
|
||||||
|
import "./style.scss";
|
||||||
|
import classNames from "classnames";
|
||||||
|
import { useAppDispatch, useAppState } from "../../../state/common/StateContext";
|
||||||
|
|
||||||
|
const options = [
|
||||||
|
{ title: "Light", value: false },
|
||||||
|
{ title: "Dark", value: true }
|
||||||
|
];
|
||||||
|
|
||||||
|
const ThemeControl = () => {
|
||||||
|
const { darkTheme } = useAppState();
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
const createHandlerClickItem = (value: boolean) => () => {
|
||||||
|
dispatch({ type: "SET_DARK_THEME", payload: value });
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="vm-theme-control">
|
||||||
|
<div className="vm-server-configurator__title">
|
||||||
|
Theme preferences
|
||||||
|
</div>
|
||||||
|
<div className="vm-theme-control-options">
|
||||||
|
<div
|
||||||
|
className="vm-theme-control-options__highlight"
|
||||||
|
style={{ left: darkTheme ? "50%" : 0 }}
|
||||||
|
/>
|
||||||
|
{options.map(item => (
|
||||||
|
<div
|
||||||
|
className={classNames({
|
||||||
|
"vm-theme-control-options__item": true,
|
||||||
|
"vm-theme-control-options__item_active": item.value === darkTheme
|
||||||
|
})}
|
||||||
|
onClick={createHandlerClickItem(item.value)}
|
||||||
|
key={item.title}
|
||||||
|
>{item.title}</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ThemeControl;
|
|
@ -0,0 +1,44 @@
|
||||||
|
@use "src/styles/variables" as *;
|
||||||
|
|
||||||
|
.vm-theme-control {
|
||||||
|
&-options {
|
||||||
|
position: relative;
|
||||||
|
display: inline-flex;
|
||||||
|
border: $border-divider;
|
||||||
|
border-radius: $border-radius-medium;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
&__item {
|
||||||
|
position: relative;
|
||||||
|
padding: $padding-small $padding-global;
|
||||||
|
border-right: $border-divider;
|
||||||
|
color: $color-text;
|
||||||
|
cursor: pointer;
|
||||||
|
z-index: 2;
|
||||||
|
transition: color 200ms ease-out;
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&_active {
|
||||||
|
color: $color-primary-text;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
box-shadow: $box-shadow-popper;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__highlight {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
background-color: $color-primary;
|
||||||
|
height: 100%;
|
||||||
|
width: 50%;
|
||||||
|
transition: left 150ms ease-in;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,11 +13,14 @@ import useResize from "../../../../hooks/useResize";
|
||||||
import DatePicker from "../../../Main/DatePicker/DatePicker";
|
import DatePicker from "../../../Main/DatePicker/DatePicker";
|
||||||
import "./style.scss";
|
import "./style.scss";
|
||||||
import useClickOutside from "../../../../hooks/useClickOutside";
|
import useClickOutside from "../../../../hooks/useClickOutside";
|
||||||
|
import classNames from "classnames";
|
||||||
|
import { useAppState } from "../../../../state/common/StateContext";
|
||||||
|
|
||||||
export const TimeSelector: FC = () => {
|
export const TimeSelector: FC = () => {
|
||||||
|
const { darkTheme } = useAppState();
|
||||||
const wrapperRef = useRef<HTMLDivElement>(null);
|
const wrapperRef = useRef<HTMLDivElement>(null);
|
||||||
const documentSize = useResize(document.body);
|
const documentSize = useResize(document.body);
|
||||||
const displayFullDate = useMemo(() => documentSize.width > 1120, [documentSize]);
|
const displayFullDate = useMemo(() => documentSize.width > 1280, [documentSize]);
|
||||||
|
|
||||||
const [until, setUntil] = useState<string>();
|
const [until, setUntil] = useState<string>();
|
||||||
const [from, setFrom] = useState<string>();
|
const [from, setFrom] = useState<string>();
|
||||||
|
@ -120,7 +123,7 @@ export const TimeSelector: FC = () => {
|
||||||
|
|
||||||
return <>
|
return <>
|
||||||
<div ref={buttonRef}>
|
<div ref={buttonRef}>
|
||||||
<Tooltip title="Time range controls">
|
<Tooltip title={displayFullDate ? "Time range controls" : dateTitle}>
|
||||||
<Button
|
<Button
|
||||||
className={appModeEnable ? "" : "vm-header-button"}
|
className={appModeEnable ? "" : "vm-header-button"}
|
||||||
variant="contained"
|
variant="contained"
|
||||||
|
@ -144,7 +147,12 @@ export const TimeSelector: FC = () => {
|
||||||
ref={wrapperRef}
|
ref={wrapperRef}
|
||||||
>
|
>
|
||||||
<div className="vm-time-selector-left">
|
<div className="vm-time-selector-left">
|
||||||
<div className="vm-time-selector-left-inputs">
|
<div
|
||||||
|
className={classNames({
|
||||||
|
"vm-time-selector-left-inputs": true,
|
||||||
|
"vm-time-selector-left-inputs_dark": darkTheme
|
||||||
|
})}
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
className="vm-time-selector-left-inputs__date"
|
className="vm-time-selector-left-inputs__date"
|
||||||
ref={fromRef}
|
ref={fromRef}
|
||||||
|
|
|
@ -18,6 +18,10 @@
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
justify-content: stretch;
|
justify-content: stretch;
|
||||||
|
|
||||||
|
&_dark &__date {
|
||||||
|
border-color: $color-text-disabled;
|
||||||
|
}
|
||||||
|
|
||||||
&__date {
|
&__date {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr 14px;
|
grid-template-columns: 1fr 14px;
|
||||||
|
@ -70,7 +74,7 @@
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
background-color: rgba($color-black, 0.06);
|
background-color: $color-hover-black;
|
||||||
padding: calc($padding-small/2);
|
padding: calc($padding-small/2);
|
||||||
border-radius: $border-radius-small;
|
border-radius: $border-radius-small;
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,7 +39,7 @@
|
||||||
code {
|
code {
|
||||||
padding: 0.2em 0.4em;
|
padding: 0.2em 0.4em;
|
||||||
font-size: 85%;
|
font-size: 85%;
|
||||||
background-color: rgba($color-black, 0.05);
|
background-color: $color-hover-black;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import React, { FC } from "preact/compat";
|
import React, { FC } from "preact/compat";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import "./style.scss";
|
import "./style.scss";
|
||||||
import { LogoIcon } from "../../Main/Icons";
|
import { IssueIcon, LogoIcon, WikiIcon } from "../../Main/Icons";
|
||||||
|
|
||||||
const Footer: FC = () => {
|
const Footer: FC = () => {
|
||||||
const copyrightYears = `2019-${dayjs().format("YYYY")}`;
|
const copyrightYears = `2019-${dayjs().format("YYYY")}`;
|
||||||
|
@ -11,18 +11,28 @@ const Footer: FC = () => {
|
||||||
className="vm-link vm-footer__website"
|
className="vm-link vm-footer__website"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
href="https://victoriametrics.com/"
|
href="https://victoriametrics.com/"
|
||||||
rel="noreferrer"
|
rel="me noreferrer"
|
||||||
>
|
>
|
||||||
<LogoIcon/>
|
<LogoIcon/>
|
||||||
victoriametrics.com
|
victoriametrics.com
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
className="vm-link"
|
className="vm-link vm-footer__link"
|
||||||
|
target="_blank"
|
||||||
|
href="https://docs.victoriametrics.com/#vmui"
|
||||||
|
rel="help noreferrer"
|
||||||
|
>
|
||||||
|
<WikiIcon/>
|
||||||
|
Documentation
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
className="vm-link vm-footer__link"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
href="https://github.com/VictoriaMetrics/VictoriaMetrics/issues/new/choose"
|
href="https://github.com/VictoriaMetrics/VictoriaMetrics/issues/new/choose"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
>
|
>
|
||||||
create an issue
|
<IssueIcon/>
|
||||||
|
Create an issue
|
||||||
</a>
|
</a>
|
||||||
<div className="vm-footer__copyright">
|
<div className="vm-footer__copyright">
|
||||||
© {copyrightYears} VictoriaMetrics
|
© {copyrightYears} VictoriaMetrics
|
||||||
|
|
|
@ -5,10 +5,12 @@
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: $padding-medium;
|
padding: $padding-medium;
|
||||||
gap: $padding-large;
|
gap: $padding-medium;
|
||||||
border-top: $border-divider;
|
border-top: $border-divider;
|
||||||
color: $color-text-secondary;
|
color: $color-text-secondary;
|
||||||
|
background: $color-background-body;
|
||||||
|
|
||||||
|
&__link,
|
||||||
&__website {
|
&__website {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 12px auto;
|
grid-template-columns: 12px auto;
|
||||||
|
@ -17,6 +19,14 @@
|
||||||
gap: 6px;
|
gap: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__website {
|
||||||
|
margin-right: $padding-global;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__link {
|
||||||
|
grid-template-columns: 14px auto;
|
||||||
|
}
|
||||||
|
|
||||||
&__copyright {
|
&__copyright {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
|
|
|
@ -1,86 +1,58 @@
|
||||||
import React, { FC, useMemo, useState } from "preact/compat";
|
import React, { FC, useMemo } from "preact/compat";
|
||||||
import { ExecutionControls } from "../../Configurators/TimeRangeSettings/ExecutionControls/ExecutionControls";
|
import { ExecutionControls } from "../../Configurators/TimeRangeSettings/ExecutionControls/ExecutionControls";
|
||||||
import { setQueryStringWithoutPageReload } from "../../../utils/query-string";
|
import { setQueryStringWithoutPageReload } from "../../../utils/query-string";
|
||||||
import { TimeSelector } from "../../Configurators/TimeRangeSettings/TimeSelector/TimeSelector";
|
import { TimeSelector } from "../../Configurators/TimeRangeSettings/TimeSelector/TimeSelector";
|
||||||
import GlobalSettings from "../../Configurators/GlobalSettings/GlobalSettings";
|
import GlobalSettings from "../../Configurators/GlobalSettings/GlobalSettings";
|
||||||
import { useLocation, useNavigate } from "react-router-dom";
|
import { useLocation, useNavigate } from "react-router-dom";
|
||||||
import router, { RouterOptions, routerOptions } from "../../../router";
|
import router, { RouterOptions, routerOptions } from "../../../router";
|
||||||
import { useEffect } from "react";
|
|
||||||
import ShortcutKeys from "../../Main/ShortcutKeys/ShortcutKeys";
|
import ShortcutKeys from "../../Main/ShortcutKeys/ShortcutKeys";
|
||||||
import { getAppModeEnable, getAppModeParams } from "../../../utils/app-mode";
|
import { getAppModeEnable, getAppModeParams } from "../../../utils/app-mode";
|
||||||
import CardinalityDatePicker from "../../Configurators/CardinalityDatePicker/CardinalityDatePicker";
|
import CardinalityDatePicker from "../../Configurators/CardinalityDatePicker/CardinalityDatePicker";
|
||||||
import { LogoFullIcon } from "../../Main/Icons";
|
import { LogoFullIcon } from "../../Main/Icons";
|
||||||
import { getCssVariable } from "../../../utils/theme";
|
import { getCssVariable } from "../../../utils/theme";
|
||||||
import Tabs from "../../Main/Tabs/Tabs";
|
|
||||||
import "./style.scss";
|
import "./style.scss";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { useDashboardsState } from "../../../state/dashboards/DashboardsStateContext";
|
|
||||||
import StepConfigurator from "../../Configurators/StepConfigurator/StepConfigurator";
|
import StepConfigurator from "../../Configurators/StepConfigurator/StepConfigurator";
|
||||||
|
import { useAppState } from "../../../state/common/StateContext";
|
||||||
|
import HeaderNav from "./HeaderNav/HeaderNav";
|
||||||
|
|
||||||
const Header: FC = () => {
|
const Header: FC = () => {
|
||||||
const primaryColor = getCssVariable("color-primary");
|
const { darkTheme } = useAppState();
|
||||||
const appModeEnable = getAppModeEnable();
|
const appModeEnable = getAppModeEnable();
|
||||||
const { dashboardsSettings } = useDashboardsState();
|
|
||||||
|
|
||||||
|
const primaryColor = useMemo(() => {
|
||||||
|
const variable = darkTheme ? "color-background-block" : "color-primary";
|
||||||
|
return getCssVariable(variable);
|
||||||
|
}, [darkTheme]);
|
||||||
|
|
||||||
|
const { background, color } = useMemo(() => {
|
||||||
const { headerStyles: {
|
const { headerStyles: {
|
||||||
background = appModeEnable ? "#FFF" : primaryColor,
|
background = appModeEnable ? "#FFF" : primaryColor,
|
||||||
color = appModeEnable ? primaryColor : "#FFF",
|
color = appModeEnable ? primaryColor : "#FFF",
|
||||||
} = {} } = getAppModeParams();
|
} = {} } = getAppModeParams();
|
||||||
|
|
||||||
|
return { background, color };
|
||||||
|
}, [primaryColor]);
|
||||||
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { search, pathname } = useLocation();
|
const { search, pathname } = useLocation();
|
||||||
const routes = useMemo(() => ([
|
|
||||||
{
|
|
||||||
label: routerOptions[router.home].title,
|
|
||||||
value: router.home,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: routerOptions[router.metrics].title,
|
|
||||||
value: router.metrics,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: routerOptions[router.cardinality].title,
|
|
||||||
value: router.cardinality,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: routerOptions[router.topQueries].title,
|
|
||||||
value: router.topQueries,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: routerOptions[router.trace].title,
|
|
||||||
value: router.trace,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: routerOptions[router.dashboards].title,
|
|
||||||
value: router.dashboards,
|
|
||||||
hide: appModeEnable || !dashboardsSettings.length
|
|
||||||
}
|
|
||||||
]), [appModeEnable, dashboardsSettings]);
|
|
||||||
|
|
||||||
const [activeMenu, setActiveMenu] = useState(pathname);
|
|
||||||
|
|
||||||
const headerSetup = useMemo(() => {
|
const headerSetup = useMemo(() => {
|
||||||
return ((routerOptions[pathname] || {}) as RouterOptions).header || {};
|
return ((routerOptions[pathname] || {}) as RouterOptions).header || {};
|
||||||
}, [pathname]);
|
}, [pathname]);
|
||||||
|
|
||||||
const onClickLogo = () => {
|
const onClickLogo = () => {
|
||||||
navigateHandler(router.home);
|
navigate({ pathname: router.home, search: search });
|
||||||
setQueryStringWithoutPageReload({});
|
setQueryStringWithoutPageReload({});
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
};
|
};
|
||||||
|
|
||||||
const navigateHandler = (pathname: string) => {
|
|
||||||
navigate({ pathname, search: search });
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setActiveMenu(pathname);
|
|
||||||
}, [pathname]);
|
|
||||||
|
|
||||||
return <header
|
return <header
|
||||||
className={classNames({
|
className={classNames({
|
||||||
"vm-header": true,
|
"vm-header": true,
|
||||||
"vm-header_app": appModeEnable
|
"vm-header_app": appModeEnable,
|
||||||
|
"vm-header_dark": darkTheme
|
||||||
})}
|
})}
|
||||||
style={{ background, color }}
|
style={{ background, color }}
|
||||||
>
|
>
|
||||||
|
@ -93,14 +65,10 @@ const Header: FC = () => {
|
||||||
<LogoFullIcon/>
|
<LogoFullIcon/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="vm-header-nav">
|
<HeaderNav
|
||||||
<Tabs
|
|
||||||
isNavLink
|
|
||||||
activeItem={activeMenu}
|
|
||||||
items={routes.filter(r => !r.hide)}
|
|
||||||
color={color}
|
color={color}
|
||||||
|
background={background}
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
<div className="vm-header__settings">
|
<div className="vm-header__settings">
|
||||||
{headerSetup?.stepControl && <StepConfigurator/>}
|
{headerSetup?.stepControl && <StepConfigurator/>}
|
||||||
{headerSetup?.timeSelector && <TimeSelector/>}
|
{headerSetup?.timeSelector && <TimeSelector/>}
|
||||||
|
|
|
@ -0,0 +1,89 @@
|
||||||
|
import React, { FC, useMemo, useState } from "preact/compat";
|
||||||
|
import router, { routerOptions } from "../../../../router";
|
||||||
|
import { getAppModeEnable } from "../../../../utils/app-mode";
|
||||||
|
import { useLocation } from "react-router-dom";
|
||||||
|
import { useDashboardsState } from "../../../../state/dashboards/DashboardsStateContext";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
import "./style.scss";
|
||||||
|
import NavItem from "./NavItem";
|
||||||
|
import NavSubItem from "./NavSubItem";
|
||||||
|
|
||||||
|
interface HeaderNavProps {
|
||||||
|
color: string
|
||||||
|
background: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const HeaderNav: FC<HeaderNavProps> = ({ color, background }) => {
|
||||||
|
const appModeEnable = getAppModeEnable();
|
||||||
|
const { dashboardsSettings } = useDashboardsState();
|
||||||
|
const { pathname } = useLocation();
|
||||||
|
|
||||||
|
const [activeMenu, setActiveMenu] = useState(pathname);
|
||||||
|
|
||||||
|
const menu = useMemo(() => ([
|
||||||
|
{
|
||||||
|
label: routerOptions[router.home].title,
|
||||||
|
value: router.home,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Explore",
|
||||||
|
submenu: [
|
||||||
|
{
|
||||||
|
label: routerOptions[router.metrics].title,
|
||||||
|
value: router.metrics,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: routerOptions[router.cardinality].title,
|
||||||
|
value: router.cardinality,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: routerOptions[router.topQueries].title,
|
||||||
|
value: router.topQueries,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: routerOptions[router.trace].title,
|
||||||
|
value: router.trace,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: routerOptions[router.dashboards].title,
|
||||||
|
value: router.dashboards,
|
||||||
|
hide: appModeEnable || !dashboardsSettings.length,
|
||||||
|
}
|
||||||
|
].filter(r => !r.hide)), [appModeEnable, dashboardsSettings]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setActiveMenu(pathname);
|
||||||
|
}, [pathname]);
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<nav className="vm-header-nav">
|
||||||
|
{menu.map(m => (
|
||||||
|
m.submenu
|
||||||
|
? (
|
||||||
|
<NavSubItem
|
||||||
|
key={m.label}
|
||||||
|
activeMenu={activeMenu}
|
||||||
|
label={m.label || ""}
|
||||||
|
submenu={m.submenu}
|
||||||
|
color={color}
|
||||||
|
background={background}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
: (
|
||||||
|
<NavItem
|
||||||
|
key={m.value}
|
||||||
|
activeMenu={activeMenu}
|
||||||
|
value={m.value}
|
||||||
|
label={m.label || ""}
|
||||||
|
color={color}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
))}
|
||||||
|
</nav>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default HeaderNav;
|
|
@ -0,0 +1,30 @@
|
||||||
|
import React, { FC } from "preact/compat";
|
||||||
|
import { NavLink } from "react-router-dom";
|
||||||
|
import classNames from "classnames";
|
||||||
|
|
||||||
|
interface NavItemProps {
|
||||||
|
activeMenu: string,
|
||||||
|
label: string,
|
||||||
|
value: string,
|
||||||
|
color?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const NavItem: FC<NavItemProps> = ({
|
||||||
|
activeMenu,
|
||||||
|
label,
|
||||||
|
value,
|
||||||
|
color
|
||||||
|
}) => (
|
||||||
|
<NavLink
|
||||||
|
className={classNames({
|
||||||
|
"vm-header-nav-item": true,
|
||||||
|
"vm-header-nav-item_active": activeMenu === value // || m.submenu?.find(m => m.value === activeMenu)
|
||||||
|
})}
|
||||||
|
style={{ color }}
|
||||||
|
to={value}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</NavLink>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default NavItem;
|
|
@ -0,0 +1,96 @@
|
||||||
|
import React, { FC, useRef, useState } from "preact/compat";
|
||||||
|
import { useLocation } from "react-router-dom";
|
||||||
|
import classNames from "classnames";
|
||||||
|
import { ArrowDropDownIcon } from "../../../Main/Icons";
|
||||||
|
import Popper from "../../../Main/Popper/Popper";
|
||||||
|
import NavItem from "./NavItem";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
|
||||||
|
interface NavItemProps {
|
||||||
|
activeMenu: string,
|
||||||
|
label: string,
|
||||||
|
submenu: {label: string | undefined, value: string}[],
|
||||||
|
color?: string
|
||||||
|
background?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const NavSubItem: FC<NavItemProps> = ({
|
||||||
|
activeMenu,
|
||||||
|
label,
|
||||||
|
color,
|
||||||
|
background,
|
||||||
|
submenu
|
||||||
|
}) => {
|
||||||
|
const { pathname } = useLocation();
|
||||||
|
|
||||||
|
const [openSubmenu, setOpenSubmenu] = useState(false);
|
||||||
|
const [menuTimeout, setMenuTimeout] = useState<NodeJS.Timeout | null>(null);
|
||||||
|
const buttonRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
const handleOpenSubmenu = () => {
|
||||||
|
setOpenSubmenu(true);
|
||||||
|
if (menuTimeout) clearTimeout(menuTimeout);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCloseSubmenu = () => {
|
||||||
|
setOpenSubmenu(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMouseLeave = () => {
|
||||||
|
if (menuTimeout) clearTimeout(menuTimeout);
|
||||||
|
const timeout = setTimeout(handleCloseSubmenu, 300);
|
||||||
|
setMenuTimeout(timeout);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMouseEnterPopup = () => {
|
||||||
|
if (menuTimeout) clearTimeout(menuTimeout);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
handleCloseSubmenu();
|
||||||
|
}, [pathname]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={classNames({
|
||||||
|
"vm-header-nav-item": true,
|
||||||
|
"vm-header-nav-item_sub": true,
|
||||||
|
"vm-header-nav-item_open": openSubmenu,
|
||||||
|
"vm-header-nav-item_active": submenu.find(m => m.value === activeMenu)
|
||||||
|
})}
|
||||||
|
style={{ color }}
|
||||||
|
onMouseEnter={handleOpenSubmenu}
|
||||||
|
onMouseLeave={handleMouseLeave}
|
||||||
|
ref={buttonRef}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
<ArrowDropDownIcon/>
|
||||||
|
|
||||||
|
<Popper
|
||||||
|
open={openSubmenu}
|
||||||
|
placement="bottom-left"
|
||||||
|
offset={{ top: 12, left: 0 }}
|
||||||
|
onClose={handleCloseSubmenu}
|
||||||
|
buttonRef={buttonRef}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="vm-header-nav-item-submenu"
|
||||||
|
style={{ background }}
|
||||||
|
onMouseLeave={handleMouseLeave}
|
||||||
|
onMouseEnter={handleMouseEnterPopup}
|
||||||
|
>
|
||||||
|
{submenu.map(sm => (
|
||||||
|
<NavItem
|
||||||
|
key={sm.value}
|
||||||
|
activeMenu={activeMenu}
|
||||||
|
value={sm.value}
|
||||||
|
label={sm.label || ""}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Popper>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default NavSubItem;
|
|
@ -0,0 +1,63 @@
|
||||||
|
@use "src/styles/variables" as *;
|
||||||
|
|
||||||
|
.vm-header-nav {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
|
gap: $padding-global;
|
||||||
|
font-size: $font-size-small;
|
||||||
|
font-weight: bold;
|
||||||
|
|
||||||
|
&-item {
|
||||||
|
position: relative;
|
||||||
|
padding: $padding-global $padding-small;
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: opacity 200ms ease-in;
|
||||||
|
text-transform: uppercase;
|
||||||
|
|
||||||
|
&_sub {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto 14px;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 4px;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
&_active {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
svg {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
transition: transform 200ms ease-in;
|
||||||
|
}
|
||||||
|
|
||||||
|
&_open {
|
||||||
|
svg {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-submenu {
|
||||||
|
display: grid;
|
||||||
|
white-space: nowrap;
|
||||||
|
padding: $padding-small;
|
||||||
|
color: $color-white;
|
||||||
|
border-radius: 2px;
|
||||||
|
opacity: 1;
|
||||||
|
transform-origin: top center;
|
||||||
|
font-size: $font-size-small;
|
||||||
|
font-weight: bold;
|
||||||
|
|
||||||
|
&-item {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,17 +6,18 @@
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
padding: $padding-small $padding-medium;
|
padding: $padding-small $padding-medium;
|
||||||
gap: $padding-large;
|
gap: 0 $padding-large;
|
||||||
|
z-index: 99;
|
||||||
|
|
||||||
&_app {
|
&_app {
|
||||||
padding: $padding-small 0;
|
padding: $padding-small 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 1200px) {
|
&_dark {
|
||||||
gap: $padding-global;
|
.vm-header-button,
|
||||||
|
button:before,
|
||||||
.vm-tabs {
|
button {
|
||||||
gap: 0;
|
background-color: $color-background-block;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,11 +44,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&-nav {
|
|
||||||
font-size: $font-size-small;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__settings {
|
&__settings {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { ReactNode } from "react";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { ErrorIcon, InfoIcon, SuccessIcon, WarningIcon } from "../Icons";
|
import { ErrorIcon, InfoIcon, SuccessIcon, WarningIcon } from "../Icons";
|
||||||
import "./style.scss";
|
import "./style.scss";
|
||||||
|
import { useAppState } from "../../../state/common/StateContext";
|
||||||
|
|
||||||
interface AlertProps {
|
interface AlertProps {
|
||||||
variant?: "success" | "error" | "info" | "warning"
|
variant?: "success" | "error" | "info" | "warning"
|
||||||
|
@ -19,12 +20,14 @@ const icons = {
|
||||||
const Alert: FC<AlertProps> = ({
|
const Alert: FC<AlertProps> = ({
|
||||||
variant,
|
variant,
|
||||||
children }) => {
|
children }) => {
|
||||||
|
const { darkTheme } = useAppState();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={classNames({
|
className={classNames({
|
||||||
"vm-alert": true,
|
"vm-alert": true,
|
||||||
[`vm-alert_${variant}`]: variant
|
[`vm-alert_${variant}`]: variant,
|
||||||
|
"vm-alert_dark": darkTheme
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<div className="vm-alert__icon">{icons[variant || "info"]}</div>
|
<div className="vm-alert__icon">{icons[variant || "info"]}</div>
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
border-radius: $border-radius-medium;
|
border-radius: $border-radius-medium;
|
||||||
box-shadow: $box-shadow;
|
box-shadow: $box-shadow;
|
||||||
font-size: $font-size-medium;
|
font-size: $font-size-medium;
|
||||||
font-weight: 500;
|
font-weight: normal;
|
||||||
color: $color-text;
|
color: $color-text;
|
||||||
line-height: 20px;
|
line-height: 20px;
|
||||||
|
|
||||||
|
@ -75,4 +75,14 @@
|
||||||
background-color: $color-warning;
|
background-color: $color-warning;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&_dark {
|
||||||
|
&:after {
|
||||||
|
opacity: 0.1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&_dark &__content {
|
||||||
|
filter: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ $button-radius: 6px;
|
||||||
padding: 6px 14px;
|
padding: 6px 14px;
|
||||||
font-size: $font-size-small;
|
font-size: $font-size-small;
|
||||||
line-height: 15px;
|
line-height: 15px;
|
||||||
font-weight: 500;
|
font-weight: normal;
|
||||||
min-height: 31px;
|
min-height: 31px;
|
||||||
border-radius: $button-radius;
|
border-radius: $button-radius;
|
||||||
color: $color-white;
|
color: $color-white;
|
||||||
|
@ -21,7 +21,7 @@ $button-radius: 6px;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
|
||||||
&:hover:after {
|
&:hover:after {
|
||||||
background-color: rgba($color-black, 0.05);
|
background-color: $color-hover-black;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:before,
|
&:before,
|
||||||
|
|
|
@ -104,7 +104,7 @@
|
||||||
transition: color 200ms ease, background-color 300ms ease-in-out;
|
transition: color 200ms ease, background-color 300ms ease-in-out;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: rgba($color-black, 0.05);
|
background-color: $color-hover-black;
|
||||||
}
|
}
|
||||||
|
|
||||||
&_empty {
|
&_empty {
|
||||||
|
@ -144,7 +144,7 @@
|
||||||
transition: color 200ms ease, background-color 300ms ease-in-out;
|
transition: color 200ms ease, background-color 300ms ease-in-out;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: rgba($color-black, 0.05);
|
background-color: $color-hover-black;
|
||||||
}
|
}
|
||||||
|
|
||||||
&_selected {
|
&_selected {
|
||||||
|
@ -270,6 +270,10 @@
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
margin-top: $padding-global;
|
margin-top: $padding-global;
|
||||||
|
|
||||||
|
&_dark &__input {
|
||||||
|
border-color: $color-text-disabled;
|
||||||
|
}
|
||||||
|
|
||||||
span {
|
span {
|
||||||
margin: 0 $padding-small;
|
margin: 0 $padding-small;
|
||||||
}
|
}
|
||||||
|
@ -277,11 +281,13 @@
|
||||||
&__input {
|
&__input {
|
||||||
width: 64px;
|
width: 64px;
|
||||||
height: 32px;
|
height: 32px;
|
||||||
border: 1px solid $color-alto;
|
border: $border-divider;
|
||||||
border-radius: $border-radius-small;
|
border-radius: $border-radius-small;
|
||||||
font-size: $font-size-medium;
|
font-size: $font-size-medium;
|
||||||
padding: 2px $padding-small;
|
padding: 2px $padding-small;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
background-color: transparent;
|
||||||
|
color: $color-text;
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
border-color: $color-primary;
|
border-color: $color-primary;
|
||||||
|
|
|
@ -2,6 +2,7 @@ import React, { FC, useEffect, useMemo, useRef, useState } from "preact/compat";
|
||||||
import { Dayjs } from "dayjs";
|
import { Dayjs } from "dayjs";
|
||||||
import { FormEvent, FocusEvent } from "react";
|
import { FormEvent, FocusEvent } from "react";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
|
import { useAppState } from "../../../../state/common/StateContext";
|
||||||
|
|
||||||
interface CalendarTimepickerProps {
|
interface CalendarTimepickerProps {
|
||||||
selectDate: Dayjs
|
selectDate: Dayjs
|
||||||
|
@ -13,6 +14,7 @@ enum TimeUnits { hour, minutes, seconds }
|
||||||
|
|
||||||
|
|
||||||
const TimePicker: FC<CalendarTimepickerProps>= ({ selectDate, onChangeTime, onClose }) => {
|
const TimePicker: FC<CalendarTimepickerProps>= ({ selectDate, onChangeTime, onClose }) => {
|
||||||
|
const { darkTheme } = useAppState();
|
||||||
|
|
||||||
const [activeField, setActiveField] = useState<TimeUnits>(TimeUnits.hour);
|
const [activeField, setActiveField] = useState<TimeUnits>(TimeUnits.hour);
|
||||||
const [hours, setHours] = useState(selectDate.format("HH"));
|
const [hours, setHours] = useState(selectDate.format("HH"));
|
||||||
|
@ -154,7 +156,12 @@ const TimePicker: FC<CalendarTimepickerProps>= ({ selectDate, onChangeTime, onCl
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<div className="vm-calendar-time-picker-fields">
|
<div
|
||||||
|
className={classNames({
|
||||||
|
"vm-calendar-time-picker-fields": true,
|
||||||
|
"vm-calendar-time-picker-fields_dark": darkTheme
|
||||||
|
})}
|
||||||
|
>
|
||||||
<input
|
<input
|
||||||
className="vm-calendar-time-picker-fields__input"
|
className="vm-calendar-time-picker-fields__input"
|
||||||
value={hours}
|
value={hours}
|
||||||
|
|
|
@ -344,3 +344,48 @@ export const TimelineIcon = () => (
|
||||||
></path>
|
></path>
|
||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const WikiIcon = () => (
|
||||||
|
<svg
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="currentColor"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
clipRule="evenodd"
|
||||||
|
d="M21 5C19.89 4.65 18.67 4.5 17.5 4.5C15.55 4.5 13.45 4.9 12 6C10.55 4.9 8.45 4.5 6.5 4.5C5.33 4.5 4.11 4.65 3 5C2.25 5.25 1.6 5.55 1 6V20.6C1 20.85 1.25 21.1 1.5 21.1C1.6 21.1 1.65 21.1 1.75 21.05C3.15 20.3 4.85 20 6.5 20C8.2 20 10.65 20.65 12 21.5C13.35 20.65 15.8 20 17.5 20C19.15 20 20.85 20.3 22.25 21.05C22.35 21.1 22.4 21.1 22.5 21.1C22.75 21.1 23 20.85 23 20.6V6C22.4 5.55 21.75 5.25 21 5ZM21 18.5C19.9 18.15 18.7 18 17.5 18C15.8 18 13.35 18.65 12 19.5C10.65 18.65 8.2 18 6.5 18C5.3 18 4.1 18.15 3 18.5V7C4.1 6.65 5.3 6.5 6.5 6.5C8.2 6.5 10.65 7.15 12 8C13.35 7.15 15.8 6.5 17.5 6.5C18.7 6.5 19.9 6.65 21 7V18.5Z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M17.5 10.5C18.38 10.5 19.23 10.59 20 10.76V9.24C19.21 9.09 18.36 9 17.5 9C15.8 9 14.26 9.29 13 9.83V11.49C14.13 10.85 15.7 10.5 17.5 10.5ZM13 12.49V14.15C14.13 13.51 15.7 13.16 17.5 13.16C18.38 13.16 19.23 13.25 20 13.42V11.9C19.21 11.75 18.36 11.66 17.5 11.66C15.8 11.66 14.26 11.96 13 12.49ZM17.5 14.33C15.8 14.33 14.26 14.62 13 15.16V16.82C14.13 16.18 15.7 15.83 17.5 15.83C18.38 15.83 19.23 15.92 20 16.09V14.57C19.21 14.41 18.36 14.33 17.5 14.33Z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M6.5 10.5C5.62 10.5 4.77 10.59 4 10.76V9.24C4.79 9.09 5.64 9 6.5 9C8.2 9 9.74 9.29 11 9.83V11.49C9.87 10.85 8.3 10.5 6.5 10.5ZM11 12.49V14.15C9.87 13.51 8.3 13.16 6.5 13.16C5.62 13.16 4.77 13.25 4 13.42V11.9C4.79 11.75 5.64 11.66 6.5 11.66C8.2 11.66 9.74 11.96 11 12.49ZM6.5 14.33C8.2 14.33 9.74 14.62 11 15.16V16.82C9.87 16.18 8.3 15.83 6.5 15.83C5.62 15.83 4.77 15.92 4 16.09V14.57C4.79 14.41 5.64 14.33 6.5 14.33Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
);
|
||||||
|
|
||||||
|
export const IssueIcon = () => (
|
||||||
|
<svg
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="currentColor"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M12 2C6.49 2 2 6.49 2 12s4.49 10 10 10 10-4.49 10-10S17.51 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zm3-8c0 1.66-1.34 3-3 3s-3-1.34-3-3 1.34-3 3-3 3 1.34 3 3z"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const QuestionIcon = () => (
|
||||||
|
<svg
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="currentColor"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
clipRule="evenodd"
|
||||||
|
d="M12 2C6.48 2 2 6.48 2 12C2 17.52 6.48 22 12 22C17.52 22 22 17.52 22 12C22 6.48 17.52 2 12 2ZM12 6C9.79 6 8 7.79 8 10H10C10 8.9 10.9 8 12 8C13.1 8 14 8.9 14 10C14 10.8792 13.4202 11.3236 12.7704 11.8217C11.9421 12.4566 11 13.1787 11 15H13C13 13.9046 13.711 13.2833 14.4408 12.6455C15.21 11.9733 16 11.2829 16 10C16 7.79 14.21 6 12 6ZM13 16V18H11V16H13Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
);
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
&-track {
|
&-track {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
background-color: rgba($color-black, 0.05);
|
background-color: $color-hover-black;
|
||||||
border-radius: $border-radius-small;
|
border-radius: $border-radius-small;
|
||||||
|
|
||||||
&__thumb {
|
&__thumb {
|
||||||
|
|
|
@ -8,7 +8,7 @@ $padding-modal: 22px;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
z-index: 100;
|
z-index: 999;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
@ -16,9 +16,11 @@ $padding-modal: 22px;
|
||||||
|
|
||||||
&-content {
|
&-content {
|
||||||
padding: $padding-modal;
|
padding: $padding-modal;
|
||||||
background: $color-white;
|
background: $color-background-block;
|
||||||
box-shadow: 0 0 24px rgba($color-black, 0.07);
|
box-shadow: 0 0 24px rgba($color-black, 0.07);
|
||||||
border-radius: $border-radius-small;
|
border-radius: $border-radius-small;
|
||||||
|
max-height: 90vh;
|
||||||
|
overflow: auto;
|
||||||
|
|
||||||
&-header {
|
&-header {
|
||||||
display: grid;
|
display: grid;
|
||||||
|
|
|
@ -3,6 +3,7 @@ import classNames from "classnames";
|
||||||
import { ArrowDropDownIcon, CloseIcon } from "../Icons";
|
import { ArrowDropDownIcon, CloseIcon } from "../Icons";
|
||||||
import { FormEvent, MouseEvent } from "react";
|
import { FormEvent, MouseEvent } from "react";
|
||||||
import Autocomplete from "../Autocomplete/Autocomplete";
|
import Autocomplete from "../Autocomplete/Autocomplete";
|
||||||
|
import { useAppState } from "../../../state/common/StateContext";
|
||||||
import "./style.scss";
|
import "./style.scss";
|
||||||
|
|
||||||
interface SelectProps {
|
interface SelectProps {
|
||||||
|
@ -26,6 +27,7 @@ const Select: FC<SelectProps> = ({
|
||||||
autofocus,
|
autofocus,
|
||||||
onChange
|
onChange
|
||||||
}) => {
|
}) => {
|
||||||
|
const { darkTheme } = useAppState();
|
||||||
|
|
||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState("");
|
||||||
const autocompleteAnchorEl = useRef<HTMLDivElement>(null);
|
const autocompleteAnchorEl = useRef<HTMLDivElement>(null);
|
||||||
|
@ -106,7 +108,12 @@ const Select: FC<SelectProps> = ({
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="vm-select">
|
<div
|
||||||
|
className={classNames({
|
||||||
|
"vm-select": true,
|
||||||
|
"vm-select_dark": darkTheme
|
||||||
|
})}
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
className="vm-select-input"
|
className="vm-select-input"
|
||||||
onClick={handleToggleList}
|
onClick={handleToggleList}
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
background-color: rgba($color-black, 0.06);
|
background-color: $color-hover-black;
|
||||||
padding: 2px 2px 2px 6px;
|
padding: 2px 2px 2px 6px;
|
||||||
border-radius: $border-radius-small;
|
border-radius: $border-radius-small;
|
||||||
font-size: $font-size;
|
font-size: $font-size;
|
||||||
|
@ -60,6 +60,8 @@
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
min-width: 100px;
|
min-width: 100px;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
|
background-color: transparent;
|
||||||
|
color: $color-text;
|
||||||
|
|
||||||
&:placeholder-shown {
|
&:placeholder-shown {
|
||||||
width: auto;
|
width: auto;
|
||||||
|
|
|
@ -35,7 +35,7 @@
|
||||||
line-height: 2;
|
line-height: 2;
|
||||||
color: $color-text;
|
color: $color-text;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
background-color: $color-white;
|
background-color: $color-background-body;
|
||||||
background-repeat: repeat-x;
|
background-repeat: repeat-x;
|
||||||
border: $border-divider;
|
border: $border-divider;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import React, { CSSProperties, FC } from "preact/compat";
|
import React, { CSSProperties, FC } from "preact/compat";
|
||||||
import "./style.scss";
|
import "./style.scss";
|
||||||
|
import classNames from "classnames";
|
||||||
|
import { getFromStorage } from "../../../utils/storage";
|
||||||
|
|
||||||
interface SpinnerProps {
|
interface SpinnerProps {
|
||||||
containerStyles?: CSSProperties;
|
containerStyles?: CSSProperties;
|
||||||
|
@ -8,7 +10,10 @@ interface SpinnerProps {
|
||||||
|
|
||||||
const Spinner: FC<SpinnerProps> = ({ containerStyles = {}, message }) => (
|
const Spinner: FC<SpinnerProps> = ({ containerStyles = {}, message }) => (
|
||||||
<div
|
<div
|
||||||
className="vm-spinner"
|
className={classNames({
|
||||||
|
"vm-spinner": true,
|
||||||
|
"vm-spinner_dark": getFromStorage("DARK_THEME")
|
||||||
|
})}
|
||||||
style={containerStyles && {}}
|
style={containerStyles && {}}
|
||||||
>
|
>
|
||||||
<div className="half-circle-spinner">
|
<div className="half-circle-spinner">
|
||||||
|
|
|
@ -15,6 +15,10 @@
|
||||||
z-index: 99;
|
z-index: 99;
|
||||||
animation: vm-fade 2s cubic-bezier(0.280, 0.840, 0.420, 1.1);
|
animation: vm-fade 2s cubic-bezier(0.280, 0.840, 0.420, 1.1);
|
||||||
|
|
||||||
|
&_dark {
|
||||||
|
background-color: rgba($color-black, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
&__message {
|
&__message {
|
||||||
margin-top: $padding-medium;
|
margin-top: $padding-medium;
|
||||||
white-space: pre-line;
|
white-space: pre-line;
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import React, { FC, KeyboardEvent, useEffect, useRef, HTMLInputTypeAttribute, ReactNode } from "react";
|
import React, { FC, KeyboardEvent, useEffect, useRef, HTMLInputTypeAttribute, ReactNode } from "react";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { useMemo } from "preact/compat";
|
import { useMemo } from "preact/compat";
|
||||||
|
import { useAppState } from "../../../state/common/StateContext";
|
||||||
import "./style.scss";
|
import "./style.scss";
|
||||||
|
|
||||||
interface TextFieldProps {
|
interface TextFieldProps {
|
||||||
|
@ -38,6 +39,7 @@ const TextField: FC<TextFieldProps> = ({
|
||||||
onFocus,
|
onFocus,
|
||||||
onBlur
|
onBlur
|
||||||
}) => {
|
}) => {
|
||||||
|
const { darkTheme } = useAppState();
|
||||||
|
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
||||||
|
@ -81,6 +83,7 @@ const TextField: FC<TextFieldProps> = ({
|
||||||
className={classNames({
|
className={classNames({
|
||||||
"vm-text-field": true,
|
"vm-text-field": true,
|
||||||
"vm-text-field_textarea": type === "textarea",
|
"vm-text-field_textarea": type === "textarea",
|
||||||
|
"vm-text-field_dark": darkTheme
|
||||||
})}
|
})}
|
||||||
data-replicated-value={value}
|
data-replicated-value={value}
|
||||||
>
|
>
|
||||||
|
|
|
@ -67,6 +67,8 @@
|
||||||
min-height: 34px;
|
min-height: 34px;
|
||||||
resize: none;
|
resize: none;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
background-color: transparent;
|
||||||
|
color: $color-text;
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
border: 1px solid $color-primary;
|
border: 1px solid $color-primary;
|
||||||
|
|
|
@ -2,6 +2,8 @@ import { FC, useEffect } from "preact/compat";
|
||||||
import { getContrastColor } from "../../../utils/color";
|
import { getContrastColor } from "../../../utils/color";
|
||||||
import { getCssVariable, setCssVariable } from "../../../utils/theme";
|
import { getCssVariable, setCssVariable } from "../../../utils/theme";
|
||||||
import { AppParams, getAppModeParams } from "../../../utils/app-mode";
|
import { AppParams, getAppModeParams } from "../../../utils/app-mode";
|
||||||
|
import { getFromStorage } from "../../../utils/storage";
|
||||||
|
import { darkPalette, lightPalette } from "../../../constants/palette";
|
||||||
|
|
||||||
interface StyleVariablesProps {
|
interface StyleVariablesProps {
|
||||||
setLoadingTheme: (val: boolean) => void
|
setLoadingTheme: (val: boolean) => void
|
||||||
|
@ -27,13 +29,6 @@ export const ThemeProvider: FC<StyleVariablesProps> = ({ setLoadingTheme }) => {
|
||||||
setCssVariable("scrollbar-height", `${innerHeight - clientHeight}px`);
|
setCssVariable("scrollbar-height", `${innerHeight - clientHeight}px`);
|
||||||
};
|
};
|
||||||
|
|
||||||
const setAppModePalette = () => {
|
|
||||||
colorVariables.forEach(variable => {
|
|
||||||
const colorFromAppMode = palette[variable as keyof AppParams["palette"]];
|
|
||||||
if (colorFromAppMode) setCssVariable(`color-${variable}`, colorFromAppMode);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const setContrastText = () => {
|
const setContrastText = () => {
|
||||||
colorVariables.forEach(variable => {
|
colorVariables.forEach(variable => {
|
||||||
const color = getCssVariable(`color-${variable}`);
|
const color = getCssVariable(`color-${variable}`);
|
||||||
|
@ -42,11 +37,34 @@ export const ThemeProvider: FC<StyleVariablesProps> = ({ setLoadingTheme }) => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const setAppModePalette = () => {
|
||||||
|
colorVariables.forEach(variable => {
|
||||||
|
const colorFromAppMode = palette[variable as keyof AppParams["palette"]];
|
||||||
|
if (colorFromAppMode) setCssVariable(`color-${variable}`, colorFromAppMode);
|
||||||
|
});
|
||||||
|
|
||||||
|
setContrastText();
|
||||||
|
};
|
||||||
|
|
||||||
|
const setTheme = () => {
|
||||||
|
const darkTheme = getFromStorage("DARK_THEME");
|
||||||
|
const palette = darkTheme ? darkPalette : lightPalette;
|
||||||
|
Object.entries(palette).forEach(([variable, value]) => {
|
||||||
|
setCssVariable(variable, value);
|
||||||
|
});
|
||||||
|
setContrastText();
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setAppModePalette();
|
setAppModePalette();
|
||||||
setScrollbarSize();
|
setScrollbarSize();
|
||||||
setContrastText();
|
setTheme();
|
||||||
setLoadingTheme(false);
|
setLoadingTheme(false);
|
||||||
|
|
||||||
|
window.addEventListener("storage", setTheme);
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener("storage", setTheme);
|
||||||
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -4,6 +4,7 @@ import Trace from "../Trace";
|
||||||
import { ArrowDownIcon } from "../../Main/Icons";
|
import { ArrowDownIcon } from "../../Main/Icons";
|
||||||
import "./style.scss";
|
import "./style.scss";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
|
import { useAppState } from "../../../state/common/StateContext";
|
||||||
|
|
||||||
interface RecursiveProps {
|
interface RecursiveProps {
|
||||||
trace: Trace;
|
trace: Trace;
|
||||||
|
@ -15,6 +16,7 @@ interface OpenLevels {
|
||||||
}
|
}
|
||||||
|
|
||||||
const NestedNav: FC<RecursiveProps> = ({ trace, totalMsec }) => {
|
const NestedNav: FC<RecursiveProps> = ({ trace, totalMsec }) => {
|
||||||
|
const { darkTheme } = useAppState();
|
||||||
const [openLevels, setOpenLevels] = useState({} as OpenLevels);
|
const [openLevels, setOpenLevels] = useState({} as OpenLevels);
|
||||||
|
|
||||||
const handleListClick = (level: number) => () => {
|
const handleListClick = (level: number) => () => {
|
||||||
|
@ -26,7 +28,12 @@ const NestedNav: FC<RecursiveProps> = ({ trace, totalMsec }) => {
|
||||||
const progress = trace.duration / totalMsec * 100;
|
const progress = trace.duration / totalMsec * 100;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="vm-nested-nav">
|
<div
|
||||||
|
className={classNames({
|
||||||
|
"vm-nested-nav": true,
|
||||||
|
"vm-nested-nav_dark": darkTheme,
|
||||||
|
})}
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
className="vm-nested-nav-header"
|
className="vm-nested-nav-header"
|
||||||
onClick={handleListClick(trace.idValue)}
|
onClick={handleListClick(trace.idValue)}
|
||||||
|
|
|
@ -5,6 +5,10 @@
|
||||||
border-radius: $border-radius-small;
|
border-radius: $border-radius-small;
|
||||||
background-color: rgba($color-tropical-blue, 0.4);
|
background-color: rgba($color-tropical-blue, 0.4);
|
||||||
|
|
||||||
|
&_dark {
|
||||||
|
background-color: rgba($color-black, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
&-header {
|
&-header {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: auto 1fr;
|
grid-template-columns: auto 1fr;
|
||||||
|
@ -15,7 +19,7 @@
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: rgba($color-black, 0.06);
|
background-color: $color-hover-black;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__icon {
|
&__icon {
|
||||||
|
|
|
@ -14,5 +14,6 @@
|
||||||
transform: translateY(-32px);
|
transform: translateY(-32px);
|
||||||
font-size: $font-size;
|
font-size: $font-size;
|
||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
|
white-space: pre-wrap;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
37
app/vmui/packages/vmui/src/constants/palette.ts
Normal file
37
app/vmui/packages/vmui/src/constants/palette.ts
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
export const darkPalette = {
|
||||||
|
"color-primary": "#589DF6",
|
||||||
|
"color-secondary": "#316eca",
|
||||||
|
"color-error": "#e5534b",
|
||||||
|
"color-warning": "#c69026",
|
||||||
|
"color-info": "#539bf5",
|
||||||
|
"color-success": "#57ab5a",
|
||||||
|
"color-background-body": "#22272e",
|
||||||
|
"color-background-block": "#2d333b",
|
||||||
|
"color-background-tooltip": "rgba(22, 22, 22, 0.6)",
|
||||||
|
"color-text": "#cdd9e5",
|
||||||
|
"color-text-secondary": "#768390",
|
||||||
|
"color-text-disabled": "#636e7b",
|
||||||
|
"box-shadow": "rgba(0, 0, 0, 0.16) 1px 2px 6px",
|
||||||
|
"box-shadow-popper": "rgba(0, 0, 0, 0.2) 0px 2px 8px 0px",
|
||||||
|
"border-divider": "1px solid rgba(99, 110, 123, 0.5)",
|
||||||
|
"color-hover-black": "rgba(0, 0, 0, 0.12)"
|
||||||
|
};
|
||||||
|
|
||||||
|
export const lightPalette = {
|
||||||
|
"color-primary": "#3F51B5",
|
||||||
|
"color-secondary": "#E91E63",
|
||||||
|
"color-error": "#FD080E",
|
||||||
|
"color-warning": "#FF8308",
|
||||||
|
"color-info": "#03A9F4",
|
||||||
|
"color-success": "#4CAF50",
|
||||||
|
"color-background-body": "#FEFEFF",
|
||||||
|
"color-background-block": "#FFFFFF",
|
||||||
|
"color-background-tooltip": "rgba(97,97,97, 0.92)",
|
||||||
|
"color-text": "#110f0f",
|
||||||
|
"color-text-secondary": "#706F6F",
|
||||||
|
"color-text-disabled": "#A09F9F",
|
||||||
|
"box-shadow": "rgba(0, 0, 0, 0.08) 1px 2px 6px",
|
||||||
|
"box-shadow-popper": "rgba(0, 0, 0, 0.1) 0px 2px 8px 0px",
|
||||||
|
"border-divider": "1px solid rgba(0, 0, 0, 0.15)",
|
||||||
|
"color-hover-black": "rgba(0, 0, 0, 0.06)"
|
||||||
|
};
|
|
@ -4,11 +4,12 @@ import { useFetchQueryOptions } from "../../../hooks/useFetchQueryOptions";
|
||||||
import { ErrorTypes } from "../../../types";
|
import { ErrorTypes } from "../../../types";
|
||||||
import { useQueryDispatch, useQueryState } from "../../../state/query/QueryStateContext";
|
import { useQueryDispatch, useQueryState } from "../../../state/query/QueryStateContext";
|
||||||
import Switch from "../../../components/Main/Switch/Switch";
|
import Switch from "../../../components/Main/Switch/Switch";
|
||||||
import { PlayIcon } from "../../../components/Main/Icons";
|
import { PlayIcon, QuestionIcon } from "../../../components/Main/Icons";
|
||||||
import Button from "../../../components/Main/Button/Button";
|
import Button from "../../../components/Main/Button/Button";
|
||||||
import TextField from "../../../components/Main/TextField/TextField";
|
import TextField from "../../../components/Main/TextField/TextField";
|
||||||
import "./style.scss";
|
import "./style.scss";
|
||||||
import { useMemo } from "preact/compat";
|
import { useMemo } from "preact/compat";
|
||||||
|
import Tooltip from "../../../components/Main/Tooltip/Tooltip";
|
||||||
|
|
||||||
export interface CardinalityConfiguratorProps {
|
export interface CardinalityConfiguratorProps {
|
||||||
onSetHistory: (step: number) => void;
|
onSetHistory: (step: number) => void;
|
||||||
|
@ -92,20 +93,33 @@ const CardinalityConfigurator: FC<CardinalityConfiguratorProps> = ({
|
||||||
onChange={onFocusLabelChange}
|
onChange={onFocusLabelChange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="vm-cardinality-configurator-controls__item">
|
</div>
|
||||||
|
<div className="vm-cardinality-configurator-bottom__autocomplete">
|
||||||
<Switch
|
<Switch
|
||||||
label={"Autocomplete"}
|
label={"Autocomplete"}
|
||||||
value={autocomplete}
|
value={autocomplete}
|
||||||
onChange={onChangeAutocomplete}
|
onChange={onChangeAutocomplete}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div className="vm-cardinality-configurator-bottom">
|
<div className="vm-cardinality-configurator-bottom">
|
||||||
<div className="vm-cardinality-configurator-bottom__info">
|
<div className="vm-cardinality-configurator-bottom__info">
|
||||||
Analyzed <b>{totalSeries}</b> series with <b>{totalLabelValuePairs}</b> "label=value" pairs
|
Analyzed <b>{totalSeries}</b> series with <b>{totalLabelValuePairs}</b> "label=value" pairs
|
||||||
at <b>{date}</b>{match && <span> for series selector <b>{match}</b></span>}.
|
at <b>{date}</b>{match && <span> for series selector <b>{match}</b></span>}.
|
||||||
Show top {topN} entries per table.
|
Show top {topN} entries per table.
|
||||||
</div>
|
</div>
|
||||||
|
<a
|
||||||
|
className="vm-cardinality-configurator-bottom__docs"
|
||||||
|
href="https://victoriametrics.com/blog/cardinality-explorer/"
|
||||||
|
target="_blank"
|
||||||
|
rel="help noreferrer"
|
||||||
|
>
|
||||||
|
<Tooltip title="Example of using">
|
||||||
|
<Button
|
||||||
|
variant="text"
|
||||||
|
startIcon={<QuestionIcon/>}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
</a>
|
||||||
<Button
|
<Button
|
||||||
startIcon={<PlayIcon/>}
|
startIcon={<PlayIcon/>}
|
||||||
onClick={onRunQuery}
|
onClick={onRunQuery}
|
||||||
|
|
|
@ -18,12 +18,18 @@
|
||||||
|
|
||||||
&-bottom {
|
&-bottom {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr auto;
|
grid-template-columns: 1fr auto auto;
|
||||||
align-items: flex-end;
|
align-items: flex-end;
|
||||||
gap: $padding-medium;
|
gap: $padding-small;
|
||||||
|
|
||||||
&__info {
|
&__info {
|
||||||
font-size: $font-size;
|
font-size: $font-size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__docs {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -89,6 +89,7 @@ const TableSettings: FC<TableSettingsProps> = ({ data, defaultColumns = [], onCh
|
||||||
onClick={handleClose}
|
onClick={handleClose}
|
||||||
startIcon={<CloseIcon/>}
|
startIcon={<CloseIcon/>}
|
||||||
size="small"
|
size="small"
|
||||||
|
variant="text"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="vm-table-settings-popper-list">
|
<div className="vm-table-settings-popper-list">
|
||||||
|
|
|
@ -2,20 +2,25 @@
|
||||||
|
|
||||||
.vm-json-form {
|
.vm-json-form {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-rows: auto calc(90vh - 78px - ($padding-medium*3)) auto;
|
grid-template-rows: auto calc(70vh - 78px - ($padding-medium*3)) auto;
|
||||||
gap: $padding-global;
|
gap: $padding-global;
|
||||||
width: 70vw;
|
width: 70vw;
|
||||||
max-width: 1000px;
|
max-width: 1000px;
|
||||||
max-height: 900px;
|
max-height: 900px;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
&_one-field {
|
&_one-field {
|
||||||
grid-template-rows: calc(90vh - 78px - ($padding-medium*3)) auto;
|
grid-template-rows: calc(70vh - 78px - ($padding-medium*3)) auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-text-field_textarea {
|
||||||
}
|
}
|
||||||
|
|
||||||
textarea {
|
textarea {
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
max-height: 900px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-footer {
|
&-footer {
|
||||||
|
|
|
@ -151,7 +151,7 @@ const TracePage: FC = () => {
|
||||||
className="vm-link vm-link_colored"
|
className="vm-link vm-link_colored"
|
||||||
href="https://docs.victoriametrics.com/#query-tracing"
|
href="https://docs.victoriametrics.com/#query-tracing"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer"
|
rel="help noreferrer"
|
||||||
>
|
>
|
||||||
https://docs.victoriametrics.com/#query-tracing
|
https://docs.victoriametrics.com/#query-tracing
|
||||||
</a>
|
</a>
|
||||||
|
|
|
@ -1,18 +1,22 @@
|
||||||
import { getDefaultServer } from "../../utils/default-server-url";
|
import { getDefaultServer } from "../../utils/default-server-url";
|
||||||
import { getQueryStringValue } from "../../utils/query-string";
|
import { getQueryStringValue } from "../../utils/query-string";
|
||||||
|
import { getFromStorage, saveToStorage } from "../../utils/storage";
|
||||||
|
|
||||||
export interface AppState {
|
export interface AppState {
|
||||||
serverUrl: string;
|
serverUrl: string;
|
||||||
tenantId: number;
|
tenantId: number;
|
||||||
|
darkTheme: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Action =
|
export type Action =
|
||||||
| { type: "SET_SERVER", payload: string }
|
| { type: "SET_SERVER", payload: string }
|
||||||
| { type: "SET_TENANT_ID", payload: number }
|
| { type: "SET_TENANT_ID", payload: number }
|
||||||
|
| { type: "SET_DARK_THEME", payload: boolean }
|
||||||
|
|
||||||
export const initialState: AppState = {
|
export const initialState: AppState = {
|
||||||
serverUrl: getDefaultServer(),
|
serverUrl: getDefaultServer(),
|
||||||
tenantId: Number(getQueryStringValue("g0.tenantID", 0)),
|
tenantId: Number(getQueryStringValue("g0.tenantID", 0)),
|
||||||
|
darkTheme: !!getFromStorage("DARK_THEME")
|
||||||
};
|
};
|
||||||
|
|
||||||
export function reducer(state: AppState, action: Action): AppState {
|
export function reducer(state: AppState, action: Action): AppState {
|
||||||
|
@ -27,6 +31,12 @@ export function reducer(state: AppState, action: Action): AppState {
|
||||||
...state,
|
...state,
|
||||||
tenantId: action.payload
|
tenantId: action.payload
|
||||||
};
|
};
|
||||||
|
case "SET_DARK_THEME":
|
||||||
|
saveToStorage("DARK_THEME", action.payload);
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
darkTheme: action.payload
|
||||||
|
};
|
||||||
default:
|
default:
|
||||||
throw new Error();
|
throw new Error();
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
|
|
||||||
&:hover,
|
&:hover,
|
||||||
&_active {
|
&_active {
|
||||||
background-color: rgba($color-black, 0.06);
|
background-color: $color-hover-black;
|
||||||
}
|
}
|
||||||
|
|
||||||
&_multiselect {
|
&_multiselect {
|
||||||
|
|
|
@ -6,10 +6,11 @@
|
||||||
gap: $padding-small;
|
gap: $padding-small;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
background-color: $color-primary;
|
background-color: $color-background-block;
|
||||||
padding: $padding-small $padding-small $padding-small $padding-global;
|
padding: $padding-small $padding-small $padding-small $padding-global;
|
||||||
border-radius: $border-radius-small $border-radius-small 0 0;
|
border-radius: $border-radius-small $border-radius-small 0 0;
|
||||||
color: $color-white;
|
color: $color-text;
|
||||||
|
border-bottom: $border-divider;
|
||||||
|
|
||||||
&__title {
|
&__title {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
transition: background-color 200ms ease;
|
transition: background-color 200ms ease;
|
||||||
|
|
||||||
&:hover:not(&_header) {
|
&:hover:not(&_header) {
|
||||||
background-color: rgba($color-black, 0.05);
|
background-color: $color-hover-black;
|
||||||
}
|
}
|
||||||
|
|
||||||
&_header {
|
&_header {
|
||||||
|
@ -43,7 +43,7 @@
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: rgba($color-black, 0.05);
|
background-color: $color-hover-black;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,7 +54,8 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
&_gray {
|
&_gray {
|
||||||
color: rgba($color-black, 0.4);
|
color: $color-text;
|
||||||
|
opacity: 0.4;
|
||||||
}
|
}
|
||||||
|
|
||||||
&_right {
|
&_right {
|
||||||
|
|
|
@ -9,6 +9,7 @@ html, body, #root {
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
background-attachment: fixed;
|
background-attachment: fixed;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
background-color: $color-background-body;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
|
@ -54,3 +55,24 @@ input[type=number]::-webkit-outer-spin-button {
|
||||||
svg {
|
svg {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Works on Firefox */
|
||||||
|
* {
|
||||||
|
scrollbar-width: thin;
|
||||||
|
scrollbar-color: $color-text-disabled $color-background-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Works on Chrome, Edge, and Safari */
|
||||||
|
*::-webkit-scrollbar {
|
||||||
|
width: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
*::-webkit-scrollbar-track {
|
||||||
|
background: $color-background-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
*::-webkit-scrollbar-thumb {
|
||||||
|
background-color: $color-text-disabled;
|
||||||
|
border-radius: 20px;
|
||||||
|
border: 3px solid $color-background-block;
|
||||||
|
}
|
||||||
|
|
|
@ -30,4 +30,17 @@
|
||||||
/* backgrounds */
|
/* backgrounds */
|
||||||
--color-background-body: #FEFEFF;
|
--color-background-body: #FEFEFF;
|
||||||
--color-background-block: #FFFFFF;
|
--color-background-block: #FFFFFF;
|
||||||
|
--color-background-tooltip: rgba(97,97,97, 0.92);
|
||||||
|
|
||||||
|
/* text */
|
||||||
|
--color-text: #110f0f;
|
||||||
|
--color-text-secondary: #706F6F;
|
||||||
|
--color-text-disabled: #A09F9F;
|
||||||
|
|
||||||
|
/* box-shadow */
|
||||||
|
--box-shadow: rgba(0, 0, 0, 0.08) 1px 2px 6px;
|
||||||
|
--box-shadow-popper: rgba(0, 0, 0, 0.1) 0px 2px 8px 0px;
|
||||||
|
|
||||||
|
--border-divider: 1px solid rgba(0, 0, 0, 0.15);
|
||||||
|
--color-hover-black: rgba(0, 0, 0, 0.06);
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,14 +13,11 @@ $color-warning-text: var(--color-warning-text);
|
||||||
$color-info-text: var(--color-info-text);
|
$color-info-text: var(--color-info-text);
|
||||||
$color-success-text: var(--color-success-text);
|
$color-success-text: var(--color-success-text);
|
||||||
|
|
||||||
$color-text: #110f0f;
|
$color-text: var(--color-text);
|
||||||
$color-text-secondary: rgba($color-text, 0.6);
|
$color-text-secondary: var(--color-text-secondary);
|
||||||
$color-text-disabled: rgba($color-text, 0.4);
|
$color-text-disabled: var(--color-text-disabled);
|
||||||
|
|
||||||
$color-black: #110f0f;
|
$color-black: #110f0f;
|
||||||
$color-dove-gray: #616161;
|
|
||||||
$color-silver: #C4C4C4;
|
|
||||||
$color-alto: #D8D8D8;
|
|
||||||
$color-white: #ffffff;
|
$color-white: #ffffff;
|
||||||
|
|
||||||
$color-dodger-blue: #1A90FF;
|
$color-dodger-blue: #1A90FF;
|
||||||
|
@ -30,8 +27,7 @@ $color-tropical-blue: #C9E3F6;
|
||||||
/************* background *************/
|
/************* background *************/
|
||||||
$color-background-body: var(--color-background-body);
|
$color-background-body: var(--color-background-body);
|
||||||
$color-background-block: var(--color-background-block);
|
$color-background-block: var(--color-background-block);
|
||||||
$color-background-modal: rgba($color-black, 0.7);
|
$color-background-tooltip: var(--color-background-tooltip);
|
||||||
$color-background-tooltip: rgba($color-dove-gray, 0.92);
|
|
||||||
|
|
||||||
|
|
||||||
/************* padding *************/
|
/************* padding *************/
|
||||||
|
@ -51,7 +47,7 @@ $font-size-small: 10px;
|
||||||
|
|
||||||
|
|
||||||
/************* border *************/
|
/************* border *************/
|
||||||
$border-divider: 1px solid rgba($color-black, 0.15);
|
$border-divider: var(--border-divider);
|
||||||
|
|
||||||
|
|
||||||
/************* border-radius *************/
|
/************* border-radius *************/
|
||||||
|
@ -61,6 +57,7 @@ $border-radius-large: 16px;
|
||||||
|
|
||||||
|
|
||||||
/************* box-shadows *************/
|
/************* box-shadows *************/
|
||||||
$box-shadow: 1px 2px 12px rgba($color-black, 0.08);
|
$box-shadow: var(--box-shadow);
|
||||||
$box-shadow-bottom: rgba($color-black, 0.04) 0px 3px 5px;
|
$box-shadow-popper: var(--box-shadow-popper);
|
||||||
$box-shadow-popper: rgba($color-black, 0.1) 0px 2px 8px 0px;
|
|
||||||
|
$color-hover-black: var(--color-hover-black);
|
||||||
|
|
|
@ -7,6 +7,7 @@ export type StorageKeys = "BASIC_AUTH_DATA"
|
||||||
| "SERIES_LIMITS"
|
| "SERIES_LIMITS"
|
||||||
| "TABLE_COMPACT"
|
| "TABLE_COMPACT"
|
||||||
| "TIMEZONE"
|
| "TIMEZONE"
|
||||||
|
| "DARK_THEME"
|
||||||
|
|
||||||
export const saveToStorage = (key: StorageKeys, value: string | boolean | Record<string, unknown>): void => {
|
export const saveToStorage = (key: StorageKeys, value: string | boolean | Record<string, unknown>): void => {
|
||||||
if (value) {
|
if (value) {
|
||||||
|
@ -15,6 +16,7 @@ export const saveToStorage = (key: StorageKeys, value: string | boolean | Record
|
||||||
} else {
|
} else {
|
||||||
removeFromStorage([key]);
|
removeFromStorage([key]);
|
||||||
}
|
}
|
||||||
|
window.dispatchEvent(new Event("storage"));
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: make this aware of data type that is stored
|
// TODO: make this aware of data type that is stored
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { getSecondsFromDuration, roundToMilliseconds } from "../time";
|
||||||
import { AxisRange } from "../../state/graph/reducer";
|
import { AxisRange } from "../../state/graph/reducer";
|
||||||
import { formatTicks, sizeAxis } from "./helpers";
|
import { formatTicks, sizeAxis } from "./helpers";
|
||||||
import { TimeParams } from "../../types";
|
import { TimeParams } from "../../types";
|
||||||
|
import { getCssVariable } from "../theme";
|
||||||
|
|
||||||
// see https://github.com/leeoniya/uPlot/tree/master/docs#axis--grid-opts
|
// see https://github.com/leeoniya/uPlot/tree/master/docs#axis--grid-opts
|
||||||
const timeValues = [
|
const timeValues = [
|
||||||
|
@ -22,10 +23,11 @@ export const getAxes = (series: Series[], unit?: string): Axis[] => Array.from(n
|
||||||
scale: a,
|
scale: a,
|
||||||
show: true,
|
show: true,
|
||||||
size: sizeAxis,
|
size: sizeAxis,
|
||||||
|
stroke: getCssVariable("color-text"),
|
||||||
font: "10px Arial",
|
font: "10px Arial",
|
||||||
values: (u: uPlot, ticks: number[]) => formatTicks(u, ticks, unit)
|
values: (u: uPlot, ticks: number[]) => formatTicks(u, ticks, unit)
|
||||||
};
|
};
|
||||||
if (!a) return { space: 80, values: timeValues };
|
if (!a) return { space: 80, values: timeValues, stroke: getCssVariable("color-text") };
|
||||||
if (!(Number(a) % 2)) return { ...axis, side: 1 };
|
if (!(Number(a) % 2)) return { ...axis, side: 1 };
|
||||||
return axis;
|
return axis;
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
import uPlot from "uplot";
|
import uPlot from "uplot";
|
||||||
|
import {getCssVariable} from "../theme";
|
||||||
|
|
||||||
export const seriesBarsPlugin = (opts) => {
|
export const seriesBarsPlugin = (opts) => {
|
||||||
let pxRatio;
|
let pxRatio;
|
||||||
|
@ -88,7 +89,7 @@ export const seriesBarsPlugin = (opts) => {
|
||||||
u.ctx.save();
|
u.ctx.save();
|
||||||
|
|
||||||
u.ctx.font = font;
|
u.ctx.font = font;
|
||||||
u.ctx.fillStyle = "black";
|
u.ctx.fillStyle = getCssVariable("color-text");
|
||||||
|
|
||||||
uPlot.orient(u, sidx, (
|
uPlot.orient(u, sidx, (
|
||||||
series,
|
series,
|
||||||
|
|
|
@ -15,6 +15,8 @@ The following tip changes can be tested by building VictoriaMetrics components f
|
||||||
|
|
||||||
## tip
|
## tip
|
||||||
|
|
||||||
|
* FEATURE: [vmui](https://docs.victoriametrics.com/#vmui): add dark mode - it can be seleted via `settings` menu in the top right corner. See [this pull request](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/3704).
|
||||||
|
* FEATURE: [vmui](https://docs.victoriametrics.com/#vmui): improve visual appearance of the top menu. See [this feature request](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3678).
|
||||||
* FEATURE: [vmagent](https://docs.victoriametrics.com/vmagent.html): reduce memory usage when sending stale markers for targets, which expose big number of metrics. See [this](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3668) and [this](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3675) issues.
|
* FEATURE: [vmagent](https://docs.victoriametrics.com/vmagent.html): reduce memory usage when sending stale markers for targets, which expose big number of metrics. See [this](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3668) and [this](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3675) issues.
|
||||||
* FEATURE: add `-internStringMaxLen` command-line flag, which can be used for fine-tuning RAM vs CPU usage in certain workloads. For example, if the stored time series contain long labels, then it may be useful reducing the `-internStringMaxLen` in order to reduce memory usage at the cost of increased CPU usage. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3692).
|
* FEATURE: add `-internStringMaxLen` command-line flag, which can be used for fine-tuning RAM vs CPU usage in certain workloads. For example, if the stored time series contain long labels, then it may be useful reducing the `-internStringMaxLen` in order to reduce memory usage at the cost of increased CPU usage. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3692).
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue