Compare commits

..

2 Commits

Author SHA1 Message Date
Haley Wang
7d7d17d192 add changelog 2025-02-10 14:08:32 +08:00
Evgeny Kuzin
0a8b4281e5 fix race using the same list from 2 goroutines 2025-02-07 11:55:45 -05:00
94 changed files with 16837 additions and 4493 deletions

View File

@@ -51,13 +51,20 @@ func RequestHandler(w http.ResponseWriter, r *http.Request) {
lmp := cp.NewLogMessageProcessor("jsonline")
streamName := fmt.Sprintf("remoteAddr=%s, requestURI=%q", httpserver.GetQuotedRemoteAddr(r), r.RequestURI)
processStreamInternal(streamName, reader, cp.TimeField, cp.MsgFields, lmp)
err = processStreamInternal(streamName, reader, cp.TimeField, cp.MsgFields, lmp)
lmp.MustClose()
requestDuration.UpdateDuration(startTime)
if err != nil {
logger.Errorf("jsonline: %s", err)
} else {
// update requestDuration only for successfully parsed requests.
// There is no need in updating requestDuration for request errors,
// since their timings are usually much smaller than the timing for successful request parsing.
requestDuration.UpdateDuration(startTime)
}
}
func processStreamInternal(streamName string, r io.Reader, timeField string, msgFields []string, lmp insertutils.LogMessageProcessor) {
func processStreamInternal(streamName string, r io.Reader, timeField string, msgFields []string, lmp insertutils.LogMessageProcessor) error {
wcr := writeconcurrencylimiter.GetReader(r)
defer writeconcurrencylimiter.PutReader(wcr)
@@ -69,10 +76,10 @@ func processStreamInternal(streamName string, r io.Reader, timeField string, msg
wcr.DecConcurrency()
if err != nil {
errorsTotal.Inc()
logger.Warnf("jsonline: cannot read line #%d in /jsonline request: %s", n, err)
return fmt.Errorf("cannot read line #%d in /jsonline request: %s", n, err)
}
if !ok {
return
return nil
}
n++
}
@@ -89,17 +96,16 @@ func readLine(lr *insertutils.LineReader, timeField string, msgFields []string,
}
p := logstorage.GetJSONParser()
defer logstorage.PutJSONParser(p)
if err := p.ParseLogMessage(line); err != nil {
return true, fmt.Errorf("cannot parse json-encoded line: %w; line contents: %q", err, line)
return false, fmt.Errorf("cannot parse json-encoded log entry: %w", err)
}
ts, err := insertutils.ExtractTimestampFromFields(timeField, p.Fields)
if err != nil {
return true, fmt.Errorf("cannot get timestamp from json-encoded line: %w; line contents: %q", err, line)
return false, fmt.Errorf("cannot get timestamp: %w", err)
}
logstorage.RenameField(p.Fields, msgFields, "_msg")
lmp.AddRow(ts, p.Fields, nil)
logstorage.PutJSONParser(p)
return true, nil
}

View File

@@ -7,14 +7,16 @@ import (
"github.com/VictoriaMetrics/VictoriaMetrics/app/vlinsert/insertutils"
)
func TestProcessStreamInternal(t *testing.T) {
func TestProcessStreamInternal_Success(t *testing.T) {
f := func(data, timeField, msgField string, timestampsExpected []int64, resultExpected string) {
t.Helper()
msgFields := []string{msgField}
tlp := &insertutils.TestLogMessageProcessor{}
r := bytes.NewBufferString(data)
processStreamInternal("test", r, timeField, msgFields, tlp)
if err := processStreamInternal("test", r, timeField, msgFields, tlp); err != nil {
t.Fatalf("unexpected error: %s", err)
}
if err := tlp.Verify(timestampsExpected, resultExpected); err != nil {
t.Fatal(err)
@@ -43,37 +45,22 @@ func TestProcessStreamInternal(t *testing.T) {
resultExpected = `{"log.offset":"71770","log.file.path":"/var/log/auth.log","message":"foobar"}
{"message":"baz"}`
f(data, timeField, msgField, timestampsExpected, resultExpected)
}
func TestProcessStreamInternal_Failure(t *testing.T) {
f := func(data string) {
t.Helper()
tlp := &insertutils.TestLogMessageProcessor{}
r := bytes.NewBufferString(data)
if err := processStreamInternal("test", r, "time", nil, tlp); err == nil {
t.Fatalf("expecting non-nil error")
}
}
// invalid json
data = "foobar"
timeField = "@timestamp"
msgField = "aaa"
timestampsExpected = nil
resultExpected = ``
f(data, timeField, msgField, timestampsExpected, resultExpected)
f("foobar")
// invalid timestamp field
data = `{"time":"foobar"}`
timeField = "time"
msgField = "abc"
timestampsExpected = nil
resultExpected = ``
f(data, timeField, msgField, timestampsExpected, resultExpected)
// invalid lines among valid lines
data = `
dsfodmasd
{"time":"2023-06-06T04:48:11.735Z","log":{"offset":71770,"file":{"path":"/var/log/auth.log"}},"message":"foobar"}
invalid line
{"time":"2023-06-06T04:48:12.735+01:00","message":"baz"}
asbsdf
`
timeField = "time"
msgField = "message"
timestampsExpected = []int64{1686026891735000000, 1686023292735000000}
resultExpected = `{"log.offset":"71770","log.file.path":"/var/log/auth.log","_msg":"foobar"}
{"_msg":"baz"}`
f(data, timeField, msgField, timestampsExpected, resultExpected)
f(`{"time":"foobar"}`)
}

View File

@@ -1 +1 @@
VITE_APP_TYPE=victoriametrics
FAST_REFRESH=false

View File

@@ -1 +0,0 @@
VITE_APP_TYPE=victorialogs

View File

@@ -1 +0,0 @@
VITE_APP_TYPE=vmanomaly

View File

@@ -0,0 +1,48 @@
// eslint-disable-next-line no-undef
module.exports = {
"env": {
"browser": true,
"es2021": true
},
"extends": [
"eslint:recommended",
"plugin:react/recommended",
"plugin:@typescript-eslint/recommended"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaFeatures": { "jsx": true },
"ecmaVersion": 12,
"sourceType": "module"
},
"plugins": [
"react",
"@typescript-eslint"
],
"rules": {
"@typescript-eslint/no-unused-vars": ["warn", { "varsIgnorePattern": "^_" }],
"react/jsx-closing-bracket-location": [1, "line-aligned"],
"react/jsx-max-props-per-line":[1, { "maximum": 1 }],
"react/jsx-first-prop-new-line": [1, "multiline"],
"object-curly-spacing": [2, "always"],
"indent": ["error", 2, { "SwitchCase": 1 }],
"linebreak-style": ["error", "unix"],
"quotes": ["error", "double"],
"semi": ["error", "always"],
"react/prop-types": 0
},
"settings": {
"react": {
"pragma": "React", // Pragma to use, default to "React"
"version": "detect"
},
"linkComponents": [
// Components used as alternatives to <a> for linking, eg. <Link to={ url } />
"Hyperlink",
{
"name": "Link", "linkAttribute": "to"
}
]
}
};

View File

@@ -0,0 +1,42 @@
/* eslint-disable */
const { override, addExternalBabelPlugin, addWebpackAlias, addWebpackPlugin } = require("customize-cra");
const webpack = require("webpack");
const fs = require('fs');
const path = require('path');
// This will replace the default check
const pathIndexHTML = (() => {
switch (process.env.REACT_APP_TYPE) {
case 'logs':
return 'src/html/victorialogs.html';
case 'anomaly':
return 'src/html/vmanomaly.html';
default:
return 'src/html/victoriametrics.html';
}
})();
const fileContent = fs.readFileSync(path.resolve(__dirname, pathIndexHTML), 'utf8');
fs.writeFileSync(path.resolve(__dirname, 'public/index.html'), fileContent);
module.exports = override(
addExternalBabelPlugin("@babel/plugin-proposal-nullish-coalescing-operator"),
addWebpackAlias({
"react": "preact/compat",
"react-dom/test-utils": "preact/test-utils",
"react-dom": "preact/compat", // Must be below test-utils
"react/jsx-runtime": "preact/jsx-runtime"
}),
addWebpackPlugin(
new webpack.NormalModuleReplacementPlugin(
/\.\/App/,
function (resource) {
if (process.env.REACT_APP_TYPE === "logs") {
resource.request = "./AppLogs";
}
if (process.env.REACT_APP_TYPE === "anomaly") {
resource.request = "./AppAnomaly";
}
}
)
)
);

View File

@@ -1,23 +0,0 @@
import { readFile } from "fs/promises";
import { IndexHtmlTransform } from "vite";
/**
* Vite plugin to dynamically load index.html based on the current mode.
* If a specific mode-based index file (e.g., index.victorialogs.html) exists, it is used.
* Otherwise, the default index.html is loaded.
*/
export default function dynamicIndexHtmlPlugin({ mode }) {
return {
name: "vm-dynamic-index-html",
transformIndexHtml: {
order: "pre",
handler: async () => {
try {
return await readFile(`./index.${mode}.html`, "utf8");
} catch (error) {
return await readFile("./index.html", "utf8");
}
}
} as IndexHtmlTransform
};
}

View File

@@ -1,90 +0,0 @@
import react from "eslint-plugin-react";
import typescriptEslint from "@typescript-eslint/eslint-plugin";
import globals from "globals";
import tsParser from "@typescript-eslint/parser";
import path from "node:path";
import { fileURLToPath } from "node:url";
import js from "@eslint/js";
import { FlatCompat } from "@eslint/eslintrc";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const compat = new FlatCompat({
baseDirectory: __dirname,
recommendedConfig: js.configs.recommended,
allConfig: js.configs.all
});
export default [...compat.extends(
"eslint:recommended",
"plugin:react/recommended",
"plugin:@typescript-eslint/recommended",
), {
plugins: {
react,
"@typescript-eslint": typescriptEslint,
},
languageOptions: {
globals: {
...globals.browser,
},
parser: tsParser,
ecmaVersion: 12,
sourceType: "module",
parserOptions: {
ecmaFeatures: {
jsx: true,
},
},
},
settings: {
react: {
pragma: "React",
version: "detect",
},
linkComponents: ["Hyperlink", {
name: "Link",
linkAttribute: "to",
}],
},
rules: {
"@typescript-eslint/no-unused-expressions": ["error", {
allowShortCircuit: true,
allowTernary: true
}],
"@typescript-eslint/no-unused-vars": ["warn", {
"argsIgnorePattern": "^_",
"caughtErrors": "none",
"caughtErrorsIgnorePattern": "^_",
"destructuredArrayIgnorePattern": "^_",
"varsIgnorePattern": "^_",
"ignoreRestSiblings": true
}],
"react/jsx-closing-bracket-location": [1, "line-aligned"],
"react/jsx-max-props-per-line": [1, {
maximum: 1,
}],
"react/jsx-first-prop-new-line": [1, "multiline"],
"object-curly-spacing": [2, "always"],
indent: ["error", 2, {
SwitchCase: 1,
}],
"linebreak-style": ["error", "unix"],
quotes: ["error", "double"],
semi: ["error", "always"],
"react/prop-types": 0,
},
}];

File diff suppressed because it is too large Load Diff

View File

@@ -3,40 +3,50 @@
"version": "0.1.0",
"private": true,
"homepage": "./",
"type": "module",
"dependencies": {
"@types/lodash.debounce": "^4.0.9",
"@types/lodash.get": "^4.4.9",
"@types/qs": "^6.9.18",
"@types/react": "^19.0.8",
"@types/react-input-mask": "^3.0.6",
"@types/lodash.throttle": "^4.1.9",
"@types/node": "^22.5.4",
"@types/qs": "^6.9.15",
"@types/react-input-mask": "^3.0.5",
"@types/react-router-dom": "^5.3.3",
"@types/webpack-env": "^1.18.5",
"classnames": "^2.5.1",
"dayjs": "^1.11.13",
"lodash.debounce": "^4.0.8",
"lodash.get": "^4.4.2",
"marked": "^15.0.6",
"marked-emoji": "^1.4.3",
"preact": "^10.25.4",
"qs": "^6.14.0",
"lodash.throttle": "^4.1.1",
"marked": "^14.1.2",
"marked-emoji": "^1.4.2",
"preact": "^10.23.2",
"qs": "^6.13.0",
"react-input-mask": "^2.0.4",
"react-router-dom": "^7.1.5",
"uplot": "^1.6.31",
"vite": "^6.0.11",
"web-vitals": "^4.2.4"
"react-router-dom": "^6.26.2",
"sass": "^1.78.0",
"source-map-explorer": "^2.5.3",
"typescript": "~4.6.2",
"uplot": "^1.6.30",
"web-vitals": "^4.2.3"
},
"scripts": {
"prestart": "npm run copy-metricsql-docs",
"start": "vite",
"start:logs": "vite --mode victorialogs",
"start:anomaly": "vite --mode vmanomaly",
"build": "vite build",
"build:logs": "vite build --mode victorialogs",
"build:anomaly": "vite build --mode vmanomaly",
"lint": "eslint 'src/**/*.{ts,tsx}'",
"lint:fix": "eslint 'src/**/*.{ts,tsx}' --fix",
"copy-metricsql-docs": "cp ../../../../docs/MetricsQL.md src/assets/MetricsQL.md || true",
"preview": "vite preview"
"start": "react-app-rewired start",
"start:logs": "cross-env REACT_APP_TYPE=logs npm run start",
"start:anomaly": "cross-env REACT_APP_TYPE=anomaly npm run start",
"build": "GENERATE_SOURCEMAP=false react-app-rewired build",
"build:logs": "cross-env REACT_APP_TYPE=logs npm run build",
"build:anomaly": "cross-env REACT_APP_TYPE=anomaly npm run build",
"lint": "eslint src --ext tsx,ts",
"lint:fix": "eslint src --ext tsx,ts --fix",
"analyze": "source-map-explorer 'build/static/js/*.js'",
"copy-metricsql-docs": "cp ../../../../docs/MetricsQL.md src/assets/MetricsQL.md || true"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
@@ -51,24 +61,26 @@
]
},
"devDependencies": {
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6",
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.16.7",
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
"@eslint/eslintrc": "^3.2.0",
"@eslint/js": "^9.19.0",
"@preact/preset-vite": "^2.10.1",
"@types/node": "^22.13.0",
"@typescript-eslint/eslint-plugin": "^8.22.0",
"@typescript-eslint/parser": "^8.22.0",
"@types/react-dom": "^18.3.0",
"@typescript-eslint/eslint-plugin": "^5.15.0",
"@typescript-eslint/parser": "^5.15.0",
"cross-env": "^7.0.3",
"eslint": "^9.19.0",
"eslint-plugin-react": "^7.37.4",
"globals": "^15.14.0",
"http-proxy-middleware": "^3.0.3",
"postcss": "^8.5.1",
"rollup-plugin-visualizer": "^5.14.0",
"sass": "^1.83.4",
"sass-embedded": "^1.83.4",
"typescript": "^5.7.3",
"webpack": "^5.97.1"
"customize-cra": "^1.0.0",
"eslint": "^8.44.0",
"eslint-config-react-app": "^7.0.1",
"eslint-plugin-react": "^7.36.1",
"http-proxy-middleware": "^3.0.2",
"react-app-rewired": "^2.2.1",
"webpack": "^5.94.0"
},
"overrides": {
"react-app-rewired": {
"nth-check": "^2.0.1"
},
"css-select": {
"nth-check": "^2.0.1"
}
}
}

View File

@@ -38,4 +38,4 @@ const AppAnomaly: FC = () => {
</>;
};
export default AppAnomaly;
export default AppAnomaly;

View File

@@ -1,6 +1,7 @@
import React, { FC, useCallback, useEffect, useRef, useState, createPortal } from "preact/compat";
import React, { FC, useCallback, useEffect, useRef, useState } from "preact/compat";
import { MouseEvent as ReactMouseEvent } from "react";
import useEventListener from "../../../hooks/useEventListener";
import ReactDOM from "react-dom";
import classNames from "classnames";
import uPlot from "uplot";
import Button from "../../Main/Button/Button";
@@ -48,7 +49,7 @@ const ChartTooltip: FC<ChartTooltipProps> = ({
onClose && onClose(id);
};
const handleMouseDown = (e: ReactMouseEvent<HTMLButtonElement>) => {
const handleMouseDown = (e: ReactMouseEvent) => {
setMoved(true);
setMoving(true);
const { clientX, clientY } = e;
@@ -106,7 +107,7 @@ const ChartTooltip: FC<ChartTooltipProps> = ({
if (!u) return null;
return createPortal((
return ReactDOM.createPortal((
<div
className={classNames({
"vm-chart-tooltip": true,

View File

@@ -1,5 +1,4 @@
@use "src/styles/variables" as *;
@use 'sass:color';
$color-bar: #33BB55;
$color-bar-highest: #F79420;
@@ -8,7 +7,7 @@ $color-bar-highest: #F79420;
display: grid;
grid-template-columns: auto 1fr;
height: 100%;
padding-bottom: calc($font-size-small / 2);
padding-bottom: calc($font-size-small/2);
overflow: hidden;
&-y-axis {
@@ -55,19 +54,19 @@ $color-bar-highest: #F79420;
flex-grow: 1;
width: 100%;
min-width: 1px;
height: calc(100% - ($font-size-small * 4));
height: calc(100% - ($font-size-small*4));
background-color: $color-bar;
transition: background-color 200ms ease-in;
&:hover {
background-color: color.scale($color-bar, $lightness: 40%);
background-color: lighten($color-bar, 10%);
}
&:first-child {
background-color: $color-bar-highest;
&:hover {
background-color: color.scale($color-bar-highest, $lightness: 40%);
background-color: lighten($color-bar-highest, 10%);
}
}
}

View File

@@ -12,11 +12,14 @@ import Timezones from "./Timezones/Timezones";
import ThemeControl from "../ThemeControl/ThemeControl";
import useDeviceDetect from "../../../hooks/useDeviceDetect";
import useBoolean from "../../../hooks/useBoolean";
import { AppType } from "../../../types/appType";
import SwitchMarkdownParsing from "../LogsSettings/MarkdownParsing/SwitchMarkdownParsing";
import { APP_TYPE_LOGS } from "../../../constants/appType";
const title = "Settings";
const { REACT_APP_TYPE } = process.env;
const isLogsApp = REACT_APP_TYPE === AppType.logs;
export interface ChildComponentHandle {
handleApply: () => void;
}
@@ -45,21 +48,21 @@ const GlobalSettings: FC = () => {
const controls = [
{
show: !appModeEnable && !APP_TYPE_LOGS,
show: !appModeEnable && !isLogsApp,
component: <ServerConfigurator
ref={serverSettingRef}
onClose={handleClose}
/>
},
{
show: !APP_TYPE_LOGS,
show: !isLogsApp,
component: <LimitsConfigurator
ref={limitsSettingRef}
onClose={handleClose}
/>
},
{
show: APP_TYPE_LOGS,
show: isLogsApp,
component: <SwitchMarkdownParsing/>
},
{

View File

@@ -36,7 +36,7 @@ export class QueryAutocompleteCache {
put(key: QueryAutocompleteCacheItem, value: string[]) {
if (this.map.size >= this.maxSize) {
const firstKey = this.map.keys().next().value;
firstKey && this.map.delete(firstKey);
this.map.delete(firstKey);
}
this.map.set(JSON.stringify(key), value);
}

View File

@@ -1,6 +1,7 @@
import React, { FC, useEffect, useRef, useState } from "preact/compat";
import { KeyboardEvent } from "react";
import { ErrorTypes } from "../../../types";
import TextField, { TextFieldKeyboardEvent } from "../../Main/TextField/TextField";
import TextField from "../../Main/TextField/TextField";
import "./style.scss";
import { QueryStats } from "../../../api/types";
import { partialWarning, seriesFetchedWarning } from "./warningText";
@@ -80,7 +81,7 @@ const QueryEditor: FC<QueryEditorProps> = ({
setCaretPositionInput([caretPosition, caretPosition]);
};
const handleKeyDown = (e: TextFieldKeyboardEvent) => {
const handleKeyDown = (e: KeyboardEvent) => {
const { key, ctrlKey, metaKey, shiftKey } = e;
const value = (e.target as HTMLTextAreaElement).value || "";

View File

@@ -137,7 +137,12 @@ const StepConfigurator: FC = () => {
startIcon={<TimelineIcon/>}
onClick={toggleOpenOptions}
>
STEP {customStep}
<p>
STEP
<p className="vm-step-control__value">
{customStep}
</p>
</p>
</Button>
</Tooltip>
)}

View File

@@ -8,6 +8,11 @@
text-transform: none;
}
&__value {
display: inline;
margin-left: 3px;
}
&-popper {
display: grid;
gap: $padding-small;

View File

@@ -14,8 +14,8 @@ interface ButtonProps {
disabled?: boolean
children?: ReactNode
className?: string
onClick?: (e: ReactMouseEvent<HTMLButtonElement>) => void
onMouseDown?: (e: ReactMouseEvent<HTMLButtonElement>) => void
onClick?: (e: ReactMouseEvent<HTMLButtonElement, MouseEvent>) => void
onMouseDown?: (e: ReactMouseEvent<HTMLButtonElement, MouseEvent>) => void
}
const Button: FC<ButtonProps> = ({

View File

@@ -46,7 +46,7 @@ const DateTimeInput: FC<DateTimeInputProps> = ({
onChange(maskedValue);
};
const handleKeyUp = (e: KeyboardEvent<HTMLInputElement>) => {
const handleKeyUp = (e: KeyboardEvent) => {
if (e.key === "Enter") {
onChange(maskedValue);
setAwaitChangeForEnter(true);

View File

@@ -89,8 +89,7 @@ const TimePicker: FC<CalendarTimepickerProps>= ({ selectDate, onChangeTime, onCl
};
const handleFocusInput = (unit: TimeUnits, e: FocusEvent<HTMLInputElement>) => {
const target = e.target as HTMLInputElement;
target && target.select();
e.target.select();
setActiveField(unit);
};

View File

@@ -1,4 +1,5 @@
import React, { FC, useCallback, useEffect, createPortal } from "preact/compat";
import React, { FC, useCallback, useEffect } from "preact/compat";
import ReactDOM from "react-dom";
import { CloseIcon } from "../Icons";
import Button from "../Button/Button";
import { ReactNode, MouseEvent } from "react";
@@ -57,7 +58,7 @@ const Modal: FC<ModalProps> = ({
useEventListener("popstate", handlePopstate);
useEventListener("keyup", handleKeyUp);
return createPortal((
return ReactDOM.createPortal((
<div
className={classNames({
"vm-modal": true,

View File

@@ -1,15 +1,6 @@
import React, {
FC,
MouseEvent as ReactMouseEvent,
ReactNode,
useEffect,
useMemo,
useRef,
useState,
useCallback,
createPortal
} 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";
@@ -17,6 +8,7 @@ import Button from "../Button/Button";
import { CloseIcon } from "../Icons";
import { useLocation, useNavigate } from "react-router-dom";
import useEventListener from "../../../hooks/useEventListener";
import { useCallback } from "preact/compat";
interface PopperProps {
children: ReactNode
@@ -127,7 +119,7 @@ const Popper: FC<PopperProps> = ({
return position;
}, [buttonRef, placement, isOpen, children, fullWidth]);
const handleClickClose = (e: ReactMouseEvent<HTMLButtonElement>) => {
const handleClickClose = (e: ReactMouseEvent<HTMLButtonElement, MouseEvent>) => {
e.stopPropagation();
onClose();
};
@@ -164,7 +156,7 @@ const Popper: FC<PopperProps> = ({
return (
<>
{(isOpen || !popperSize.width) && createPortal((
{(isOpen || !popperSize.width) && ReactDOM.createPortal((
<div
className={classNames({
"vm-popper": true,

View File

@@ -11,7 +11,7 @@ interface MultipleSelectedValueProps {
const MultipleSelectedValue: FC<MultipleSelectedValueProps> = ({ values, onRemoveItem }) => {
const { isMobile } = useDeviceDetect();
const createHandleClick = (value: string) => (e: MouseEvent<HTMLDivElement>) => {
const createHandleClick = (value: string) => (e: MouseEvent) => {
onRemoveItem(value);
e.stopPropagation();
};

View File

@@ -92,7 +92,7 @@ const Select: FC<SelectProps> = ({
setSearch((e.target as HTMLInputElement).value);
};
const createHandleClick = (value: string) => (e: MouseEvent<HTMLDivElement>) => {
const createHandleClick = (value: string) => (e: MouseEvent) => {
handleSelected(value);
e.stopPropagation();
};

View File

@@ -15,8 +15,6 @@ import useDeviceDetect from "../../../hooks/useDeviceDetect";
import TextFieldMessage from "./TextFieldMessage";
import "./style.scss";
export type TextFieldKeyboardEvent = KeyboardEvent<HTMLInputElement | HTMLTextAreaElement>;
interface TextFieldProps {
label?: string,
value?: string | number
@@ -33,7 +31,7 @@ interface TextFieldProps {
caretPosition?: [number, number]
onChange?: (value: string) => void
onEnter?: () => void
onKeyDown?: (e: TextFieldKeyboardEvent) => void
onKeyDown?: (e: KeyboardEvent) => void
onFocus?: () => void
onBlur?: () => void
onChangeCaret?: (position: [number, number]) => void
@@ -86,7 +84,7 @@ const TextField: FC<TextFieldProps> = ({
updateCaretPosition(e.currentTarget);
};
const handleKeyDown = (e: TextFieldKeyboardEvent) => {
const handleKeyDown = (e: KeyboardEvent<HTMLInputElement | HTMLTextAreaElement>) => {
onKeyDown && onKeyDown(e);
const { key, ctrlKey, metaKey } = e;
const isEnter = key === "Enter";
@@ -97,7 +95,7 @@ const TextField: FC<TextFieldProps> = ({
}
};
const handleKeyUp = (e: TextFieldKeyboardEvent) => {
const handleKeyUp = (e: KeyboardEvent<HTMLInputElement | HTMLTextAreaElement>) => {
updateCaretPosition(e.currentTarget);
};

View File

@@ -1,6 +1,8 @@
import React, { FC, useEffect, useMemo, useRef, useState, Fragment, createPortal } from "preact/compat";
import React, { FC, useEffect, useMemo, useRef, useState, Fragment } from "preact/compat";
import ReactDOM from "react-dom";
import "./style.scss";
import { ReactNode } from "react";
import { ExoticComponent } from "react";
import useDeviceDetect from "../../../hooks/useDeviceDetect";
interface TooltipProps {
@@ -23,7 +25,7 @@ const Tooltip: FC<TooltipProps> = ({
const [isOpen, setIsOpen] = useState(false);
const [popperSize, setPopperSize] = useState({ width: 0, height: 0 });
const buttonRef = useRef<ReactNode>(null);
const buttonRef = useRef<ExoticComponent>(null);
const popperRef = useRef<HTMLDivElement>(null);
const onScrollWindow = () => setIsOpen(false);
@@ -118,7 +120,7 @@ const Tooltip: FC<TooltipProps> = ({
{children}
</Fragment>
{!isMobile && isOpen && createPortal((
{!isMobile && isOpen && ReactDOM.createPortal((
<div
className="vm-tooltip"
ref={popperRef}

View File

@@ -8,8 +8,8 @@ import Switch from "../../Main/Switch/Switch";
import { arrayEquals } from "../../../utils/array";
import classNames from "classnames";
import useBoolean from "../../../hooks/useBoolean";
import TextField, { TextFieldKeyboardEvent } from "../../Main/TextField/TextField";
import { useState } from "react";
import TextField from "../../Main/TextField/TextField";
import { KeyboardEvent, useState } from "react";
import Modal from "../../Main/Modal/Modal";
import { useSearchParams } from "react-router-dom";
@@ -96,7 +96,7 @@ const TableSettings: FC<TableSettingsProps> = ({
setIndexFocusItem(-1);
};
const handleKeyDown = (e: TextFieldKeyboardEvent) => {
const handleKeyDown = (e: KeyboardEvent) => {
const arrowUp = e.key === "ArrowUp";
const arrowDown = e.key === "ArrowDown";
const enter = e.key === "Enter";

View File

@@ -6,8 +6,8 @@ const dateColumns = ["date", "timestamp", "time"];
export function descendingComparator<T>(a: T, b: T, orderBy: keyof T) {
const valueA = a[orderBy];
const valueB = b[orderBy];
const parsedValueA = dateColumns.includes(String(orderBy)) ? dayjs(`${valueA}`).unix() : valueA;
const parsedValueB = dateColumns.includes(String(orderBy)) ? dayjs(`${valueB}`).unix() : valueB;
const parsedValueA = dateColumns.includes(`${orderBy}`) ? dayjs(`${valueA}`).unix() : valueA;
const parsedValueB = dateColumns.includes(`${orderBy}`) ? dayjs(`${valueB}`).unix() : valueB;
if (parsedValueB < parsedValueA) {
return -1;
}

View File

@@ -1,12 +0,0 @@
export enum AppType {
victoriametrics = "victoriametrics",
victorialogs = "victorialogs",
vmanomaly = "vmanomaly",
}
export const APP_TYPE = import.meta.env.VITE_APP_TYPE;
export const APP_TYPE_VM = APP_TYPE === AppType.victoriametrics;
export const APP_TYPE_LOGS = APP_TYPE === AppType.victorialogs;
export const APP_TYPE_ANOMALY = APP_TYPE === AppType.vmanomaly;

View File

@@ -1,7 +1,6 @@
import { useAppDispatch } from "../state/common/StateContext";
import { useEffect, useState } from "preact/compat";
import { ErrorTypes } from "../types";
import { APP_TYPE_VM } from "../constants/appType";
const useFetchFlags = () => {
const dispatch = useAppDispatch();
@@ -11,7 +10,7 @@ const useFetchFlags = () => {
useEffect(() => {
const fetchAppConfig = async () => {
if (!APP_TYPE_VM) return;
if (process.env.REACT_APP_TYPE) return;
setError("");
setIsLoading(true);

View File

@@ -5,7 +5,6 @@ import { useTimeDispatch } from "../state/time/TimeStateContext";
import { getFromStorage } from "../utils/storage";
import dayjs from "dayjs";
import { getBrowserTimezone } from "../utils/time";
import { APP_TYPE_VM } from "../constants/appType";
const disabledDefaultTimezone = Boolean(getFromStorage("DISABLED_DEFAULT_TIMEZONE"));
@@ -29,7 +28,7 @@ const useFetchDefaultTimezone = () => {
};
const fetchDefaultTimezone = async () => {
if (!serverUrl || !APP_TYPE_VM) return;
if (!serverUrl || process.env.REACT_APP_TYPE) return;
setError("");
setIsLoading(true);

View File

@@ -1,7 +1,6 @@
import { useAppDispatch, useAppState } from "../state/common/StateContext";
import { useEffect, useState } from "preact/compat";
import { ErrorTypes } from "../types";
import { APP_TYPE_VM } from "../constants/appType";
const useFetchFlags = () => {
const { serverUrl } = useAppState();
@@ -12,7 +11,7 @@ const useFetchFlags = () => {
useEffect(() => {
const fetchFlags = async () => {
if (!serverUrl || !APP_TYPE_VM) return;
if (!serverUrl || process.env.REACT_APP_TYPE) return;
setError("");
setIsLoading(true);

View File

@@ -12,8 +12,8 @@ import { useCustomPanelState } from "../state/customPanel/CustomPanelStateContex
import { isHistogramData } from "../utils/metric";
import { useGraphState } from "../state/graph/GraphStateContext";
import { getStepFromDuration } from "../utils/time";
import { AppType } from "../types/appType";
import { getQueryStringValue } from "../utils/query-string";
import { APP_TYPE_ANOMALY } from "../constants/appType";
interface FetchQueryParams {
predefinedQuery?: string[]
@@ -49,6 +49,8 @@ interface FetchDataParams {
hideQuery?: number[]
}
const isAnomalyUI = AppType.anomaly === process.env.REACT_APP_TYPE;
export const useFetchQuery = ({
predefinedQuery,
visible,
@@ -132,7 +134,7 @@ export const useFetchQuery = ({
}
const preventChangeType = !!getQueryStringValue("display_mode", null);
isHistogramResult = !APP_TYPE_ANOMALY && isDisplayChart && !preventChangeType && isHistogramData(resp.data.result);
isHistogramResult = !isAnomalyUI && isDisplayChart && !preventChangeType && isHistogramData(resp.data.result);
seriesLimit = isHistogramResult ? Infinity : defaultLimit;
const freeTempSize = seriesLimit - tempData.length;
resp.data.result.slice(0, freeTempSize).forEach((d: MetricBase) => {

View File

@@ -2,9 +2,9 @@
<html lang="en">
<head>
<meta charset="utf-8"/>
<link rel="icon" href="/favicon.svg" />
<link rel="apple-touch-icon" href="/favicon.svg" />
<link rel="mask-icon" href="/favicon.svg" color="#000000">
<link rel="icon" href="%PUBLIC_URL%/favicon.svg" />
<link rel="apple-touch-icon" href="%PUBLIC_URL%/favicon.svg" />
<link rel="mask-icon" href="%PUBLIC_URL%/favicon.svg" color="#000000">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=5"/>
<meta name="theme-color" content="#000000"/>
@@ -13,13 +13,13 @@
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="/manifest.json"/>
<link rel="manifest" href="%PUBLIC_URL%/manifest.json"/>
<!--
Notice the use of in the tags above.
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "/favicon.ico" will
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
@@ -29,7 +29,7 @@
<meta name="twitter:title" content="UI for VictoriaLogs">
<meta name="twitter:site" content="@https://victoriametrics.com/products/victorialogs/">
<meta name="twitter:description" content="Explore your log data with VictoriaLogs UI">
<meta name="twitter:image" content="/preview.jpg">
<meta name="twitter:image" content="%PUBLIC_URL%/preview.jpg">
<meta property="og:type" content="website">
<meta property="og:title" content="UI for VictoriaLogs">
@@ -49,6 +49,5 @@
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
<script type="module" src="/src/index.tsx"></script>
</body>
</html>

View File

@@ -2,9 +2,9 @@
<html lang="en">
<head>
<meta charset="utf-8"/>
<link rel="icon" href="/favicon.svg"/>
<link rel="apple-touch-icon" href="/favicon.svg"/>
<link rel="mask-icon" href="/favicon.svg" color="#000000">
<link rel="icon" href="%PUBLIC_URL%/favicon.svg" />
<link rel="apple-touch-icon" href="%PUBLIC_URL%/favicon.svg" />
<link rel="mask-icon" href="%PUBLIC_URL%/favicon.svg" color="#000000">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=5"/>
<meta name="theme-color" content="#000000"/>
@@ -13,24 +13,24 @@
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="/manifest.json"/>
<link rel="manifest" href="%PUBLIC_URL%/manifest.json"/>
<!--
Notice the use of in the tags above.
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "/favicon.ico" will
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>vmui</title>
<script src="/dashboards/index.js" type="module"></script>
<script src="%PUBLIC_URL%/dashboards/index.js" type="module"></script>
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="UI for VictoriaMetrics">
<meta name="twitter:site" content="@https://victoriametrics.com/">
<meta name="twitter:description" content="Explore and troubleshoot your VictoriaMetrics data">
<meta name="twitter:image" content="/preview.jpg">
<meta name="twitter:image" content="%PUBLIC_URL%/preview.jpg">
<meta property="og:type" content="website">
<meta property="og:title" content="UI for VictoriaMetrics">
@@ -50,6 +50,5 @@
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
<script type="module" src="/src/index.tsx"></script>
</body>
</html>

View File

@@ -2,9 +2,9 @@
<html lang="en">
<head>
<meta charset="utf-8"/>
<link rel="icon" href="/favicon.svg" />
<link rel="apple-touch-icon" href="/favicon.svg" />
<link rel="mask-icon" href="/favicon.svg" color="#000000">
<link rel="icon" href="%PUBLIC_URL%/favicon.svg" />
<link rel="apple-touch-icon" href="%PUBLIC_URL%/favicon.svg" />
<link rel="mask-icon" href="%PUBLIC_URL%/favicon.svg" color="#000000">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=5"/>
<meta name="theme-color" content="#000000"/>
@@ -13,13 +13,13 @@
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="/manifest.json"/>
<link rel="manifest" href="%PUBLIC_URL%/manifest.json"/>
<!--
Notice the use of in the tags above.
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "/favicon.ico" will
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
@@ -29,7 +29,7 @@
<meta name="twitter:title" content="UI for VictoriaMetrics Anomaly Detection">
<meta name="twitter:site" content="@https://victoriametrics.com/products/enterprise/anomaly-detection/">
<meta name="twitter:description" content="Detect anomalies in your metrics with VictoriaMetrics Anomaly Detection UI">
<meta name="twitter:image" content="/preview.jpg">
<meta name="twitter:image" content="%PUBLIC_URL%/preview.jpg">
<meta property="og:type" content="website">
<meta property="og:title" content="UI for VictoriaMetrics Anomaly Detection">
@@ -49,6 +49,5 @@
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
<script type="module" src="/src/index.tsx"></script>
</body>
</html>

View File

@@ -3,23 +3,9 @@ import "./constants/dayjsPlugins";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
import "./styles/style.scss";
import { APP_TYPE, AppType } from "./constants/appType";
import AppLogs from "./AppLogs";
import AppAnomaly from "./AppAnomaly";
const getAppComponent = () => {
switch (APP_TYPE) {
case AppType.victorialogs:
return <AppLogs/>;
case AppType.vmanomaly:
return <AppAnomaly/>;
default:
return <App/>;
}
};
const root = document.getElementById("root");
if (root) render(getAppComponent(), root);
if (root) render(<App />, root);
// If you want to start measuring performance in your app, pass a function

View File

@@ -13,16 +13,19 @@ import HeaderControls, { ControlsProps } from "./HeaderControls/HeaderControls";
import useDeviceDetect from "../../hooks/useDeviceDetect";
import useWindowSize from "../../hooks/useWindowSize";
import { ComponentType } from "react";
import { APP_TYPE, AppType } from "../../constants/appType";
import { AppType } from "../../types/appType";
export interface HeaderProps {
controlsComponent: ComponentType<ControlsProps>
}
const { REACT_APP_TYPE } = process.env;
const isCustomApp = REACT_APP_TYPE === AppType.logs || REACT_APP_TYPE === AppType.anomaly;
const Logo = () => {
switch (APP_TYPE) {
case AppType.victorialogs:
switch (REACT_APP_TYPE) {
case AppType.logs:
return <LogoLogsIcon/>;
case AppType.vmanomaly:
case AppType.anomaly:
return <LogoAnomalyIcon/>;
default:
return <LogoIcon/>;
@@ -78,7 +81,10 @@ const Header: FC<HeaderProps> = ({ controlsComponent }) => {
<>
{!appModeEnable && (
<div
className="vm-header-logo"
className={classNames({
"vm-header-logo": true,
"vm-header-logo_logs": isCustomApp
})}
onClick={onClickLogo}
style={{ color }}
>
@@ -96,6 +102,7 @@ const Header: FC<HeaderProps> = ({ controlsComponent }) => {
className={classNames({
"vm-header-logo": true,
"vm-header-logo_mobile": true,
"vm-header-logo_logs": isCustomApp
})}
onClick={onClickLogo}
style={{ color }}

View File

@@ -8,13 +8,16 @@ import MenuBurger from "../../../components/Main/MenuBurger/MenuBurger";
import useDeviceDetect from "../../../hooks/useDeviceDetect";
import "./style.scss";
import useBoolean from "../../../hooks/useBoolean";
import { APP_TYPE_LOGS } from "../../../constants/appType";
import { AppType } from "../../../types/appType";
interface SidebarHeaderProps {
background: string
color: string
}
const { REACT_APP_TYPE } = process.env;
const isLogsApp = REACT_APP_TYPE === AppType.logs;
const SidebarHeader: FC<SidebarHeaderProps> = ({
background,
color,
@@ -61,7 +64,7 @@ const SidebarHeader: FC<SidebarHeaderProps> = ({
/>
</div>
<div className="vm-header-sidebar-menu-settings">
{!isMobile && !APP_TYPE_LOGS && <ShortcutKeys showTitle={true}/>}
{!isMobile && !isLogsApp && <ShortcutKeys showTitle={true}/>}
</div>
</div>
</div>;

View File

@@ -47,14 +47,14 @@
justify-content: flex-start;
cursor: pointer;
width: 100%;
max-width: 75px;
min-width: 75px;
max-width: 65px;
min-width: 65px;
margin-bottom: 2px;
overflow: hidden;
svg {
max-width: 75px;
min-width: 75px;
max-width: 65px;
min-width: 65px;
}
&_mobile {
@@ -62,5 +62,10 @@
min-width: 65px;
margin: 0 auto;
}
&_logs, &_logs svg {
max-width: 75px;
min-width: 75px;
}
}
}

View File

@@ -15,7 +15,7 @@ const EnhancedTable: FC<TableProps> = ({
const [orderBy, setOrderBy] = useState<keyof Data>(defaultSortColumn);
const handleRequestSort = (
event: MouseEvent<HTMLTableCellElement>,
event: MouseEvent<unknown>,
property: keyof Data,
) => {
const isAsc = orderBy === property && order === "asc";

View File

@@ -7,7 +7,7 @@ import Tooltip from "../../../components/Main/Tooltip/Tooltip";
export function EnhancedTableHead(props: EnhancedHeaderTableProps) {
const { order, orderBy, onRequestSort, headerCells } = props;
const createSortHandler = (property: keyof Data) => (event: MouseEvent<HTMLTableCellElement>) => {
const createSortHandler = (property: keyof Data) => (event: MouseEvent<unknown>) => {
onRequestSort(event, property);
};

View File

@@ -9,7 +9,7 @@ export interface HeadCell {
}
export interface EnhancedHeaderTableProps {
onRequestSort: (event: MouseEvent<HTMLTableCellElement>, property: keyof Data) => void;
onRequestSort: (event: MouseEvent<unknown>, property: keyof Data) => void;
order: Order;
orderBy: string;
rowCount: number;

View File

@@ -3,11 +3,10 @@ import { useCustomPanelDispatch, useCustomPanelState } from "../../state/customP
import { ChartIcon, CodeIcon, TableIcon } from "../../components/Main/Icons";
import Tabs from "../../components/Main/Tabs/Tabs";
import { DisplayType } from "../../types";
import { ReactNode } from "react";
type DisplayTab = {
value: DisplayType
icon: ReactNode
icon: JSX.Element
label: string
prometheusCode: number
}

View File

@@ -114,7 +114,7 @@ const QueryConfigurator: FC<QueryConfiguratorProps> = ({
setStateQuery(prev => prev.filter((q, i) => i !== index));
};
const handleToggleHideQuery = (e: ReactMouseEvent<HTMLButtonElement>, index: number) => {
const handleToggleHideQuery = (e: ReactMouseEvent<HTMLButtonElement, MouseEvent>, index: number) => {
const { ctrlKey, metaKey } = e;
const ctrlMetaKey = ctrlKey || metaKey;
@@ -160,7 +160,7 @@ const QueryConfigurator: FC<QueryConfiguratorProps> = ({
setHideQuery(prev => prev.includes(i) ? prev.filter(n => n !== i) : prev.map(n => n > i ? n - 1 : n));
};
const createHandlerHideQuery = (i: number) => (e: ReactMouseEvent<HTMLButtonElement>) => {
const createHandlerHideQuery = (i: number) => (e: ReactMouseEvent<HTMLButtonElement, MouseEvent>) => {
handleToggleHideQuery(e, i);
};

View File

@@ -1,5 +1,4 @@
@use "src/styles/variables" as *;
@use 'sass:color';
$font-size-logs: var(--font-size-logs, $font-size-small);
@@ -80,8 +79,8 @@ $font-size-logs: var(--font-size-logs, $font-size-small);
&__pair {
order: 0;
padding: calc($padding-global / 2) $padding-global;
background-color: color.scale($color-tropical-blue, $lightness: 60%);
color: color.scale($color-tropical-blue, $lightness: -60%);
background-color: lighten($color-tropical-blue, 6%);
color: darken($color-dodger-blue, 20%);
border-radius: $border-radius-medium;
transition: background-color 0.3s ease-in, transform 0.1s ease-in, opacity 0.3s ease-in;
white-space: nowrap;
@@ -106,7 +105,7 @@ $font-size-logs: var(--font-size-logs, $font-size-small);
}
&_dark {
color: color.scale($color-dodger-blue, $lightness: 40%);
color: lighten($color-dodger-blue, 20%);
background-color: $color-background-body;
opacity: 0.8;

View File

@@ -44,7 +44,7 @@ const PredefinedDashboard: FC<PredefinedDashboardProps> = ({
setPanelsWidth(width);
}, [resize, sizeSection]);
const handleMouseDown = (e: ReactMouseEvent<HTMLButtonElement>, i: number) => {
const handleMouseDown = (e: ReactMouseEvent<HTMLButtonElement, MouseEvent>, i: number) => {
setResize({
start: e.clientX,
target: i,

View File

@@ -3,7 +3,6 @@ import { DashboardSettings, ErrorTypes } from "../../../types";
import { useAppState } from "../../../state/common/StateContext";
import { useDashboardsDispatch } from "../../../state/dashboards/DashboardsStateContext";
import { getAppModeEnable } from "../../../utils/app-mode";
import { APP_TYPE_VM } from "../../../constants/appType";
const importModule = async (filename: string) => {
const data = await fetch(`./dashboards/${filename}`);
@@ -35,7 +34,7 @@ export const useFetchDashboards = (): {
};
const fetchRemoteDashboards = async () => {
if (!serverUrl || !APP_TYPE_VM) return;
if (!serverUrl || process.env.REACT_APP_TYPE) return;
setError("");
setIsLoading(true);

View File

@@ -108,12 +108,10 @@ const QueryAnalyzer: FC = () => {
};
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
if (!e.target) return;
const target = e.target as HTMLInputElement;
setError("");
const files = Array.from(target.files || []);
const files = Array.from(e.target.files || []);
handleReadFiles(files);
target.value = "";
e.target.value = "";
};
const handleCloseError = () => {

View File

@@ -1,4 +1,4 @@
import React, { FC, useEffect, useMemo } from "react";
import React, { FC, useEffect, useMemo, KeyboardEvent } from "react";
import { useFetchTopQueries } from "./hooks/useFetchTopQueries";
import Spinner from "../../components/Main/Spinner/Spinner";
import TopQueryPanel from "./TopQueryPanel/TopQueryPanel";
@@ -8,7 +8,7 @@ import dayjs from "dayjs";
import { TopQueryStats } from "../../types";
import Button from "../../components/Main/Button/Button";
import { PlayIcon } from "../../components/Main/Icons";
import TextField, { TextFieldKeyboardEvent } from "../../components/Main/TextField/TextField";
import TextField from "../../components/Main/TextField/TextField";
import Alert from "../../components/Main/Alert/Alert";
import Tooltip from "../../components/Main/Tooltip/Tooltip";
import "./style.scss";
@@ -55,7 +55,7 @@ const TopQueries: FC = () => {
setMaxLifetime(value);
};
const onKeyDown = (e: TextFieldKeyboardEvent) => {
const onKeyDown = (e: KeyboardEvent) => {
if (e.key === "Enter") fetch();
};

View File

@@ -56,12 +56,10 @@ const TracePage: FC = () => {
};
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
if (!e.target) return;
const target = e.target as HTMLInputElement;
setErrors([]);
const files = Array.from(target.files || []);
const files = Array.from(e.target.files || []);
handleReadFiles(files);
target.value = "";
e.target.value = "";
};
const handleTraceDelete = (trace: Trace) => {

View File

@@ -0,0 +1 @@
/// <reference types="react-scripts" />

View File

@@ -1,4 +1,4 @@
import { APP_TYPE_LOGS } from "../constants/appType";
import { AppType } from "../types/appType";
const router = {
home: "/",
@@ -34,12 +34,15 @@ export interface RouterOptions {
header: RouterOptionsHeader
}
const { REACT_APP_TYPE } = process.env;
const isLogsApp = REACT_APP_TYPE === AppType.logs;
const routerOptionsDefault = {
header: {
tenant: true,
stepControl: !APP_TYPE_LOGS,
timeSelector: !APP_TYPE_LOGS,
executionControls: !APP_TYPE_LOGS,
stepControl: !isLogsApp,
timeSelector: !isLogsApp,
executionControls: !isLogsApp,
}
};

View File

@@ -2,9 +2,11 @@ import { getAppModeEnable } from "../utils/app-mode";
import { useDashboardsState } from "../state/dashboards/DashboardsStateContext";
import { useAppState } from "../state/common/StateContext";
import { useMemo } from "preact/compat";
import { AppType } from "../types/appType";
import { processNavigationItems } from "./utils";
import { getAnomalyNavigation, getDefaultNavigation, getLogsNavigation } from "./navigation";
import { APP_TYPE, AppType } from "../constants/appType";
const appType = process.env.REACT_APP_TYPE;
const useNavigationMenu = () => {
const appModeEnable = getAppModeEnable();
@@ -23,10 +25,10 @@ const useNavigationMenu = () => {
const menu = useMemo(() => {
switch (APP_TYPE) {
case AppType.victorialogs:
switch (appType) {
case AppType.logs:
return getLogsNavigation();
case AppType.vmanomaly:
case AppType.anomaly:
return getAnomalyNavigation();
default:
return getDefaultNavigation(navigationConfig);

View File

@@ -28,7 +28,7 @@ export function reducer(state: LogsState, action: LogsAction): LogsState {
case "SET_AUTOCOMPLETE_CACHE": {
if (state.autocompleteCache.size >= AUTOCOMPLETE_LIMITS.cacheLimit) {
const firstKey = state.autocompleteCache.keys().next().value;
firstKey && state.autocompleteCache.delete(firstKey);
state.autocompleteCache.delete(firstKey);
}
state.autocompleteCache.set(action.payload.key, action.payload.value);

View File

@@ -0,0 +1,4 @@
export enum AppType {
logs = "logs",
anomaly = "anomaly",
}

View File

@@ -4,7 +4,7 @@ export const arrayEquals = (a: (string|number)[], b: (string|number)[]) => {
export function groupByMultipleKeys<T>(items: T[], keys: (keyof T)[]): { keys: string[], values: T[] }[] {
const groups = items.reduce((result, item) => {
const compositeKey = keys.map(key => `${String(key)}: ${item[key] || "-"}`).join("|");
const compositeKey = keys.map(key => `${key}: ${item[key] || "-"}`).join("|");
(result[compositeKey] = result[compositeKey] || []).push(item);

View File

@@ -1,12 +1,12 @@
import React, { ComponentProps, FC, ReactNode } from "react";
import React, { ComponentProps, FC } from "react";
type Props = { children: ReactNode };
type Props = { children: JSX.Element };
export const combineComponents = (...components: FC<Props>[]): FC<Props> => {
return components.reduce(
(AccumulatedComponents, CurrentComponent) => {
// eslint-disable-next-line react/display-name
return ({ children }: ComponentProps<FC<Props>>): ReactNode => (
return ({ children }: ComponentProps<FC<Props>>): JSX.Element => (
<AccumulatedComponents>
<CurrentComponent>{children}</CurrentComponent>
</AccumulatedComponents>

View File

@@ -1,7 +1,8 @@
import { getAppModeParams } from "./app-mode";
import { replaceTenantId } from "./tenants";
import { APP_TYPE, AppType } from "../constants/appType";
import { AppType } from "../types/appType";
import { getFromStorage } from "./storage";
const { REACT_APP_TYPE } = process.env;
export const getDefaultServer = (tenantId?: string): string => {
const { serverURL } = getAppModeParams();
@@ -11,10 +12,10 @@ export const getDefaultServer = (tenantId?: string): string => {
const defaultURL = window.location.href.replace(/\/(?:prometheus\/)?(?:graph|vmui)\/.*/, "/prometheus");
const url = serverURL || storageURL || defaultURL;
switch (APP_TYPE) {
case AppType.victorialogs:
switch (REACT_APP_TYPE) {
case AppType.logs:
return logsURL;
case AppType.vmanomaly:
case AppType.anomaly:
return storageURL || anomalyURL;
default:
return tenantId ? replaceTenantId(url, tenantId) : url;

View File

@@ -3,7 +3,7 @@ import dayjs, { UnitTypeShort } from "dayjs";
import { getQueryStringValue } from "./query-string";
import { DATE_ISO_FORMAT } from "../constants/date";
import timezones from "../constants/timezones";
import { APP_TYPE_LOGS } from "../constants/appType";
import { AppType } from "../types/appType";
const MAX_ITEMS_PER_CHART = window.innerWidth / 4;
const MAX_ITEMS_PER_HISTOGRAM = window.innerWidth / 40;
@@ -160,10 +160,11 @@ export const dateFromSeconds = (epochTimeInSeconds: number): Date => {
const getYesterday = () => dayjs().tz().subtract(1, "day").endOf("day").toDate();
const getToday = () => dayjs().tz().endOf("day").toDate();
const isLogsApp = process.env.REACT_APP_TYPE === AppType.logs;
export const relativeTimeOptions: RelativeTimeOption[] = [
{ title: "Last 5 minutes", duration: "5m", isDefault: APP_TYPE_LOGS },
{ title: "Last 5 minutes", duration: "5m", isDefault: isLogsApp },
{ title: "Last 15 minutes", duration: "15m" },
{ title: "Last 30 minutes", duration: "30m", isDefault: !APP_TYPE_LOGS },
{ title: "Last 30 minutes", duration: "30m", isDefault: !isLogsApp },
{ title: "Last 1 hour", duration: "1h" },
{ title: "Last 3 hours", duration: "3h" },
{ title: "Last 6 hours", duration: "6h" },

View File

@@ -1,2 +0,0 @@
/// <reference types="vite/client" />
/// <reference types="vite/types/importMeta.d.ts" />

View File

@@ -1,7 +1,6 @@
{
"compilerOptions": {
"target": "ESNext",
"types": ["vite/client"],
"target": "es5",
"lib": [
"dom",
"dom.iterable",
@@ -20,13 +19,7 @@
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"downlevelIteration": true,
"paths": {
"react": ["./node_modules/preact/compat/"],
"react/jsx-runtime": ["./node_modules/preact/jsx-runtime"],
"react-dom": ["./node_modules/preact/compat/"],
"react-dom/*": ["./node_modules/preact/compat/*"]
}
"downlevelIteration": true
},
"include": [
"src"

View File

@@ -1,39 +0,0 @@
import * as path from "path";
import { defineConfig } from "vite";
import preact from "@preact/preset-vite";
import dynamicIndexHtmlPlugin from "./config/plugins/dynamicIndexHtml";
export default defineConfig(({ mode }) => {
return {
base: "",
plugins: [
preact(),
dynamicIndexHtmlPlugin({ mode })
],
assetsInclude: ["**/*.md"],
server: {
open: true,
port: 3000,
},
resolve: {
alias: {
"src": path.resolve(__dirname, "src"),
},
},
build: {
outDir: "./build",
rollupOptions: {
output: {
manualChunks(id) {
if (id.includes("node_modules")) {
return "vendor";
}
}
}
}
},
};
});

View File

@@ -44,7 +44,7 @@ services:
# storing logs and serving read queries.
victorialogs:
container_name: victorialogs
image: victoriametrics/victoria-logs:v1.9.0-victorialogs
image: victoriametrics/victoria-logs:v1.8.0-victorialogs
command:
- "--storageDataPath=/vlogs"
- "--httpListenAddr=:9428"

View File

@@ -1,7 +1,7 @@
services:
# meta service will be ignored by compose
.victorialogs:
image: docker.io/victoriametrics/victoria-logs:v1.9.0-victorialogs
image: docker.io/victoriametrics/victoria-logs:v1.8.0-victorialogs
command:
- -storageDataPath=/vlogs
- -loggerFormat=json

View File

@@ -3,7 +3,7 @@ version: "3"
services:
# Run `make package-victoria-logs` to build victoria-logs image
vlogs:
image: docker.io/victoriametrics/victoria-logs:v1.9.0-victorialogs
image: docker.io/victoriametrics/victoria-logs:v1.8.0-victorialogs
volumes:
- vlogs:/vlogs
ports:

View File

@@ -16,21 +16,15 @@ according to [these docs](https://docs.victoriametrics.com/victorialogs/quicksta
## tip
## [v1.9.0](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.9.0-victorialogs)
Released at 2025-02-10
* FEATURE: [LogsQL](https://docs.victoriametrics.com/victorialogs/logsql/): improve performance for [`stats by (...) ...`](https://docs.victoriametrics.com/victorialogs/logsql/#stats-pipe) by up to 30% when it is applied to big number of `by (...)` groups.
* FEATURE: [LogsQL](https://docs.victoriametrics.com/victorialogs/logsql/): improve performance for [`top` pipe](https://docs.victoriametrics.com/victorialogs/logsql/#top-pipe) by up to 30% when it is applied to big number of unique values.
* FEATURE: [`stats` pipe](https://docs.victoriametrics.com/victorialogs/logsql/#stats-pipe): improve performance for [`count_uniq`](https://docs.victoriametrics.com/victorialogs/logsql/#count_uniq-stats) and [`count_uniq_hash`](https://docs.victoriametrics.com/victorialogs/logsql/#count_uniq_hash-stats) functions by up to 30% when they are applied to big number of unique values.
* FEATURE: [`block_stats` pipe](https://docs.victoriametrics.com/victorialogs/logsql/#block_stats-pipe): return the path to the part where every data block is stored. The path to the part is returned in the `part_path` field. This allows investigating the distribution of data blocks among parts.
* FEATURE: reduce VictoriaLogs startup time by multiple times when it opens a large datastore with big [retention](https://docs.victoriametrics.com/victorialogs/#retention).
* FEATURE: [data ingestion](https://docs.victoriametrics.com/victorialogs/data-ingestion/): accept timestamps with microsecond and nanosecond precision at [`_time` field](https://docs.victoriametrics.com/victorialogs/keyconcepts/#time-field).
* FEATURE: [JSON lines data ingestion](https://docs.victoriametrics.com/victorialogs/data-ingestion/#json-stream-api): continue parsing lines after encountering parse errors for some lines. Previously the input JSON lines' stream was closed after the first parse error.
* FEATURE: [web UI](https://docs.victoriametrics.com/victorialogs/querying/#web-ui): add the `_msg` field to the list of fields for the group view, allowing users to select multiple fields, including `_msg`, for log display.
* BUGFIX: [LogsQL](https://docs.victoriametrics.com/victorialogs/logsql/): properly limit [`concurrency` query option](https://docs.victoriametrics.com/victorialogs/logsql/#query-options) for [`stats`](https://docs.victoriametrics.com/victorialogs/logsql/#stats-pipe), [`uniq`](https://docs.victoriametrics.com/victorialogs/logsql/#uniq-pipe) and [`top`](https://docs.victoriametrics.com/victorialogs/logsql/#top-pipe). This prevents from `runtime error: index out of range` panic. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/8201).
* BUGFIX: [`sort` pipe](https://docs.victoriametrics.com/victorialogs/logsql/#sort-pipe): properly sort [RFC3339 timestamps](https://www.rfc-editor.org/rfc/rfc3339) with variable sub-second precision. Previosuly such timestamps were sorted using [natural sorting](https://en.wikipedia.org/wiki/Natural_sort_order), and this could lead to unexpected results. For example, `2025-02-21T10:20:30.9Z` was incorrectly considered smaller than `2025-02-21T10:20:30.012Z`, since the last one had higher decimal value after the last dot.
* BUGFIX: [data ingestion](https://docs.victoriametrics.com/victorialogs/data-ingestion/): drop log entries with too long field names and log the dropped log entries with the `ignoring log entry with too long field name` message, so human operators could notice and fix the ingestion of incorrect logs ASAP. Previously too long field names were silently truncated to shorter values. This isn't what most users expect. See [why VictoriaLogs has a limit on the field name length](https://docs.victoriametrics.com/victorialogs/faq/#what-is-the-maximum-supported-field-name-length).
* BUGFIX: [web UI](https://docs.victoriametrics.com/victorialogs/querying/#web-ui): fix transparency for bars in the hits bar chart to improve visibility. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/8152).
* BUGFIX: [web UI](https://docs.victoriametrics.com/victorialogs/querying/#web-ui): fix `Group by field` dropdown menu not displaying any options in Group View settings. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/8153).

View File

@@ -33,8 +33,8 @@ Just download archive for the needed Operating system and architecture, unpack i
For example, the following commands download VictoriaLogs archive for Linux/amd64, unpack and run it:
```sh
curl -L -O https://github.com/VictoriaMetrics/VictoriaMetrics/releases/download/v1.9.0-victorialogs/victoria-logs-linux-amd64-v1.9.0-victorialogs.tar.gz
tar xzf victoria-logs-linux-amd64-v1.9.0-victorialogs.tar.gz
curl -L -O https://github.com/VictoriaMetrics/VictoriaMetrics/releases/download/v1.8.0-victorialogs/victoria-logs-linux-amd64-v1.8.0-victorialogs.tar.gz
tar xzf victoria-logs-linux-amd64-v1.8.0-victorialogs.tar.gz
./victoria-logs-prod
```
@@ -58,7 +58,7 @@ Here is the command to run VictoriaLogs in a Docker container:
```sh
docker run --rm -it -p 9428:9428 -v ./victoria-logs-data:/victoria-logs-data \
docker.io/victoriametrics/victoria-logs:v1.9.0-victorialogs
docker.io/victoriametrics/victoria-logs:v1.8.0-victorialogs
```
See also:

View File

@@ -23,8 +23,8 @@ or from [docker images](https://hub.docker.com/r/victoriametrics/vlogscli/tags).
### Running `vlogscli` from release binary
```sh
curl -L -O https://github.com/VictoriaMetrics/VictoriaMetrics/releases/download/v1.9.0-victorialogs/vlogscli-linux-amd64-v1.9.0-victorialogs.tar.gz
tar xzf vlogscli-linux-amd64-v1.9.0-victorialogs.tar.gz
curl -L -O https://github.com/VictoriaMetrics/VictoriaMetrics/releases/download/v1.8.0-victorialogs/vlogscli-linux-amd64-v1.8.0-victorialogs.tar.gz
tar xzf vlogscli-linux-amd64-v1.8.0-victorialogs.tar.gz
./vlogscli-prod
```

View File

@@ -20,9 +20,11 @@ See also [LTS releases](https://docs.victoriametrics.com/lts-releases/).
* SECURITY: upgrade Go builder from Go1.23.5 to Go1.23.6. See the list of issues addressed in [Go1.23.6](https://github.com/golang/go/issues?q=milestone%3AGo1.23.6+label%3ACherryPickApproved).
* BUGFIX: [vmalert](https://docs.victoriametrics.com/vmalert/): fix polluted alert messages when multiple Alertmanager instances are configured as notifiers with alert_relabel_configs.. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/8040), and thanks to @evkuzin for [the pull request](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/8258).
## [v1.111.0](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.111.0)
Released at 2025-02-10
Released at 2025-02-07
**Update note 1: [Single-node VictoriaMetrics](https://docs.victoriametrics.com/) and [vmstorage](https://docs.victoriametrics.com/victoriametrics/) stop exposing `vm_index_search_duration_seconds` histogram metric. This metric records time spent on search operations in the index. It was introduced in [v1.56.0](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.56.0). However, this metric was used neither in dashboards nor in alerting rules. It also has high cardinality because index search operations latency can differ by 3 orders of magnitude. Hence, dropping it as unused.**
@@ -38,33 +40,6 @@ Released at 2025-02-10
* BUGFIX: [Single-node VictoriaMetrics](https://docs.victoriametrics.com/) and [vmselect](https://docs.victoriametrics.com/cluster-victoriametrics/): fix discrepancies when using `or` binary operator. See [this](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7759) and [this](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7640) issues for details.
* BUGFIX: [vmsingle](https://docs.victoriametrics.com/single-server-victoriametrics/) and `vmstorage` in [VictoriaMetrics cluster](https://docs.victoriametrics.com/cluster-victoriametrics/): properly update number of unique series for [cardinality limiter](https://docs.victoriametrics.com/#cardinality-limiter) on ingestion. Previously, limit could undercount the real number of the ingested unique series.
## [v1.110.1](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.110.1)
Released at 2025-02-10
**v1.110.x is a line of [LTS releases](https://docs.victoriametrics.com/lts-releases/). It contains important up-to-date bugfixes for [VictoriaMetrics enterprise](https://docs.victoriametrics.com/enterprise.html).
All these fixes are also included in [the latest community release](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/latest).
The v1.110.x line will be supported for at least 12 months since [v1.110.0](https://docs.victoriametrics.com/changelog/#v11100) release**
* BUGFIX: [vmagent](https://docs.victoriametrics.com/vmagent/): properly perform graceful shutdown for kafka client.
* BUGFIX: [vmagent](https://docs.victoriametrics.com/vmagent/): properly add message metadata headers for kafka remoteWrite target.
* BUGFIX: [vmui](https://docs.victoriametrics.com/#vmui): improve clipboard error handling in tables and code snippets by showing detailed messages with possible reasons. See [this pull request](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/7778).
* BUGFIX: [vmsingle](https://docs.victoriametrics.com/single-server-victoriametrics/) and `vmstorage` in [VictoriaMetrics cluster](https://docs.victoriametrics.com/cluster-victoriametrics/): properly update number of unique series for [cardinality limiter](https://docs.victoriametrics.com/#cardinality-limiter) on ingestion. Previously, limit could undercount the real number of the ingested unique series.
* BUGFIX: [Single-node VictoriaMetrics](https://docs.victoriametrics.com/) and [vmselect](https://docs.victoriametrics.com/cluster-victoriametrics/): fix discrepancies when using `or` binary operator. See [this](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7759) and [this](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7640) issues for details.
* BUGFIX: [vmui](https://docs.victoriametrics.com/#vmui) for [VictoriaMetrics enterprise](https://docs.victoriametrics.com/enterprise.html) components: properly display enterprise features when the enterprise version is used.
## [v1.102.13](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.102.13)
Released at 2025-02-10
**v1.102.x is a line of [LTS releases](https://docs.victoriametrics.com/lts-releases/). It contains important up-to-date bugfixes for [VictoriaMetrics enterprise](https://docs.victoriametrics.com/enterprise.html).
All these fixes are also included in [the latest community release](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/latest).
The v1.102.x line will be supported for at least 12 months since [v1.102.0](https://docs.victoriametrics.com/changelog/#v11020) release**
* BUGFIX: [export API](https://docs.victoriametrics.com/#how-to-export-time-series): cancel export process on client connection close. Previously client connection close was ignored and VictoriaMetrics started to hog CPU by exporting metrics to nowhere until it export all of them.
* BUGFIX: [Single-node VictoriaMetrics](https://docs.victoriametrics.com/) and [vmselect](https://docs.victoriametrics.com/cluster-victoriametrics/): fix discrepancies when using `or` binary operator. See [this](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7759) and [this](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7640) issues for details.
* BUGFIX: [vmsingle](https://docs.victoriametrics.com/single-server-victoriametrics/) and `vmstorage` in [VictoriaMetrics cluster](https://docs.victoriametrics.com/cluster-victoriametrics/): properly update number of unique series for [cardinality limiter](https://docs.victoriametrics.com/#cardinality-limiter) on ingestion. Previously, limit could undercount the real number of the ingested unique series.
## [v1.102.12](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.102.12)
Released at 2025-01-28

View File

@@ -1,25 +1,6 @@
## Next release
- TODO
## 0.8.16
**Release date:** 07 Feb 2025
![Helm: v3](https://img.shields.io/badge/Helm-v3.14%2B-informational?color=informational&logo=helm&link=https%3A%2F%2Fgithub.com%2Fhelm%2Fhelm%2Freleases%2Ftag%2Fv3.14.0) ![AppVersion: v1.8.0](https://img.shields.io/badge/v1.8.0-success?logo=VictoriaMetrics&labelColor=gray&link=https%3A%2F%2Fdocs.victoriametrics.com%2Fvictorialogs%2Fchangelog%23v180)
- add `.Values.server.vmServiceScrape` for [VMOperator](https://docs.victoriametrics.com/operator/) [VMServiceScrape](https://docs.victoriametrics.com/operator/api/#vmservicescrape) resource
- update victorialogs version to [v1.8.0](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.8.0-victorialogs)
## 0.8.15
**Release date:** 06 Feb 2025
![Helm: v3](https://img.shields.io/badge/Helm-v3.14%2B-informational?color=informational&logo=helm&link=https%3A%2F%2Fgithub.com%2Fhelm%2Fhelm%2Freleases%2Ftag%2Fv3.14.0) ![AppVersion: v1.6.1](https://img.shields.io/badge/v1.6.1-success?logo=VictoriaMetrics&labelColor=gray&link=https%3A%2F%2Fdocs.victoriametrics.com%2Fvictorialogs%2Fchangelog%23v161)
- added ability to override default headless service .Values.server.service.clusterIP with empty value
- vector chart 0.37.x -> 0.40.x
- updated common dependency 0.0.37 -> 0.0.39
## 0.8.14

View File

@@ -1,6 +1,6 @@
![Version](https://img.shields.io/badge/0.8.16-gray?logo=Helm&labelColor=gray&link=https%3A%2F%2Fdocs.victoriametrics.com%2Fhelm%2Fvictoria-logs-single%2Fchangelog%2F%230816)
![Version](https://img.shields.io/badge/0.8.14-gray?logo=Helm&labelColor=gray&link=https%3A%2F%2Fdocs.victoriametrics.com%2Fhelm%2Fvictoria-logs-single%2Fchangelog%2F%230814)
![ArtifactHub](https://img.shields.io/badge/ArtifactHub-informational?logoColor=white&color=417598&logo=artifacthub&link=https%3A%2F%2Fartifacthub.io%2Fpackages%2Fhelm%2Fvictoriametrics%2Fvictoria-logs-single)
![License](https://img.shields.io/github/license/VictoriaMetrics/helm-charts?labelColor=green&label=&link=https%3A%2F%2Fgithub.com%2FVictoriaMetrics%2Fhelm-charts%2Fblob%2Fmaster%2FLICENSE)
![Slack](https://img.shields.io/badge/Join-4A154B?logo=slack&link=https%3A%2F%2Fslack.victoriametrics.com)
@@ -910,7 +910,7 @@ readOnlyRootFilesystem: true
<td>server.service.clusterIP</td>
<td>string</td>
<td><pre class="helm-vars-default-value language-yaml" lang="">
<code class="language-yaml">None
<code class="language-yaml">""
</code>
</pre>
</td>
@@ -1179,69 +1179,6 @@ readOnlyRootFilesystem: true
</pre>
</td>
<td><p>Pod topologySpreadConstraints</p>
</td>
</tr>
<tr>
<td>server.vmServiceScrape.annotations</td>
<td>object</td>
<td><pre class="helm-vars-default-value language-yaml" lang="plaintext">
<code class="language-yaml">{}
</code>
</pre>
</td>
<td></td>
</tr>
<tr>
<td>server.vmServiceScrape.enabled</td>
<td>bool</td>
<td><pre class="helm-vars-default-value language-yaml" lang="">
<code class="language-yaml">false
</code>
</pre>
</td>
<td><p>Enable deployment of VMServiceScrape for server component. This is Victoria Metrics operator object</p>
</td>
</tr>
<tr>
<td>server.vmServiceScrape.extraLabels</td>
<td>object</td>
<td><pre class="helm-vars-default-value language-yaml" lang="plaintext">
<code class="language-yaml">{}
</code>
</pre>
</td>
<td></td>
</tr>
<tr>
<td>server.vmServiceScrape.metricRelabelings</td>
<td>list</td>
<td><pre class="helm-vars-default-value language-yaml" lang="plaintext">
<code class="language-yaml">[]
</code>
</pre>
</td>
<td></td>
</tr>
<tr>
<td>server.vmServiceScrape.relabelings</td>
<td>list</td>
<td><pre class="helm-vars-default-value language-yaml" lang="plaintext">
<code class="language-yaml">[]
</code>
</pre>
</td>
<td><p>Commented. TLS configuration to use when scraping the endpoint tlsConfig: insecureSkipVerify: true</p>
</td>
</tr>
<tr>
<td>server.vmServiceScrape.targetPort</td>
<td>string</td>
<td><pre class="helm-vars-default-value language-yaml" lang="">
<code class="language-yaml">http
</code>
</pre>
</td>
<td><p>target port</p>
</td>
</tr>
<tr>

View File

@@ -1,14 +1,6 @@
## Next release
- updated common dependency 0.0.37 -> 0.0.39
## 0.15.7
**Release date:** 05 Feb 2025
![Helm: v3](https://img.shields.io/badge/Helm-v3.14%2B-informational?color=informational&logo=helm&link=https%3A%2F%2Fgithub.com%2Fhelm%2Fhelm%2Freleases%2Ftag%2Fv3.14.0) ![AppVersion: v1.110.0](https://img.shields.io/badge/v1.110.0-success?logo=VictoriaMetrics&labelColor=gray&link=https%3A%2F%2Fdocs.victoriametrics.com%2Fchangelog%23v11100)
- added `.Values.allowedMetricsEndpoints` to set allowed scrape endpoints. See [this issue](https://github.com/VictoriaMetrics/helm-charts/issues/1970) for details.
- TODO
## 0.15.6

View File

@@ -1,6 +1,6 @@
![Version](https://img.shields.io/badge/0.15.7-gray?logo=Helm&labelColor=gray&link=https%3A%2F%2Fdocs.victoriametrics.com%2Fhelm%2Fvictoria-metrics-agent%2Fchangelog%2F%230157)
![Version](https://img.shields.io/badge/0.15.6-gray?logo=Helm&labelColor=gray&link=https%3A%2F%2Fdocs.victoriametrics.com%2Fhelm%2Fvictoria-metrics-agent%2Fchangelog%2F%230156)
![ArtifactHub](https://img.shields.io/badge/ArtifactHub-informational?logoColor=white&color=417598&logo=artifacthub&link=https%3A%2F%2Fartifacthub.io%2Fpackages%2Fhelm%2Fvictoriametrics%2Fvictoria-metrics-agent)
![License](https://img.shields.io/github/license/VictoriaMetrics/helm-charts?labelColor=green&label=&link=https%3A%2F%2Fgithub.com%2FVictoriaMetrics%2Fhelm-charts%2Fblob%2Fmaster%2FLICENSE)
![Slack](https://img.shields.io/badge/Join-4A154B?logo=slack&link=https%3A%2F%2Fslack.victoriametrics.com)
@@ -164,16 +164,6 @@ Change the values according to the need of the environment in ``victoria-metrics
<td><p>Pod affinity</p>
</td>
</tr>
<tr>
<td>allowedMetricsEndpoints[0]</td>
<td>string</td>
<td><pre class="helm-vars-default-value language-yaml" lang="">
<code class="language-yaml">/metrics
</code>
</pre>
</td>
<td></td>
</tr>
<tr>
<td>annotations</td>
<td>object</td>

View File

@@ -1,6 +1,6 @@
## Next release
- updated common dependency 0.0.37 -> 0.0.39
- TODO
## 0.13.8

View File

@@ -1,6 +1,6 @@
## Next release
- updated common dependency 0.0.37 -> 0.0.39
- TODO
## 1.7.2

View File

@@ -1,6 +1,6 @@
## Next release
- updated common dependency 0.0.37 -> 0.0.39
- TODO
## 0.8.6

View File

@@ -626,17 +626,6 @@ name: ""
</pre>
</td>
<td><p>Existing secret name</p>
</td>
</tr>
<tr>
<td>lifecycle</td>
<td>object</td>
<td><pre class="helm-vars-default-value language-yaml" lang="plaintext">
<code class="language-yaml">{}
</code>
</pre>
</td>
<td><p>Specify pod lifecycle</p>
</td>
</tr>
<tr>

View File

@@ -2,24 +2,6 @@
- TODO
## 0.17.6
**Release date:** 06 Feb 2025
![Helm: v3](https://img.shields.io/badge/Helm-v3.14%2B-informational?color=informational&logo=helm&link=https%3A%2F%2Fgithub.com%2Fhelm%2Fhelm%2Freleases%2Ftag%2Fv3.14.0) ![AppVersion: v1.110.0](https://img.shields.io/badge/v1.110.0-success?logo=VictoriaMetrics&labelColor=gray&link=https%3A%2F%2Fdocs.victoriametrics.com%2Fchangelog%23v11100)
- Reverted enabling headless service for vmselect by default.
## 0.17.5
**Release date:** 06 Feb 2025
![Helm: v3](https://img.shields.io/badge/Helm-v3.14%2B-informational?color=informational&logo=helm&link=https%3A%2F%2Fgithub.com%2Fhelm%2Fhelm%2Freleases%2Ftag%2Fv3.14.0) ![AppVersion: v1.110.0](https://img.shields.io/badge/v1.110.0-success?logo=VictoriaMetrics&labelColor=gray&link=https%3A%2F%2Fdocs.victoriametrics.com%2Fchangelog%23v11100)
- added ability to override default headless service `.Values.vmselect.service.clusterIP` with empty value
- added `.Values.common.image.tag` to set the same tag for all cluster components.
- updated common dependency 0.0.37 -> 0.0.39
## 0.17.4
**Release date:** 04 Feb 2025

View File

@@ -1,6 +1,6 @@
![Version](https://img.shields.io/badge/0.17.6-gray?logo=Helm&labelColor=gray&link=https%3A%2F%2Fdocs.victoriametrics.com%2Fhelm%2Fvictoria-metrics-cluster%2Fchangelog%2F%230176)
![Version](https://img.shields.io/badge/0.17.4-gray?logo=Helm&labelColor=gray&link=https%3A%2F%2Fdocs.victoriametrics.com%2Fhelm%2Fvictoria-metrics-cluster%2Fchangelog%2F%230174)
![ArtifactHub](https://img.shields.io/badge/ArtifactHub-informational?logoColor=white&color=417598&logo=artifacthub&link=https%3A%2F%2Fartifacthub.io%2Fpackages%2Fhelm%2Fvictoriametrics%2Fvictoria-metrics-cluster)
![License](https://img.shields.io/github/license/VictoriaMetrics/helm-charts?labelColor=green&label=&link=https%3A%2F%2Fgithub.com%2FVictoriaMetrics%2Fhelm-charts%2Fblob%2Fmaster%2FLICENSE)
![Slack](https://img.shields.io/badge/Join-4A154B?logo=slack&link=https%3A%2F%2Fslack.victoriametrics.com)
@@ -145,19 +145,7 @@ Change the values according to the need of the environment in ``victoria-metrics
</code>
</pre>
</td>
<td><p>use SRV discovery for storageNode and selectNode flags for enterprise version</p>
</td>
</tr>
<tr>
<td>common.image</td>
<td>object</td>
<td><pre class="helm-vars-default-value language-yaml" lang="plaintext">
<code class="language-yaml">tag: ""
</code>
</pre>
</td>
<td><p>common for all components image configuration</p>
</td>
<td></td>
</tr>
<tr>
<td>extraObjects</td>
@@ -212,17 +200,6 @@ Change the values according to the need of the environment in ``victoria-metrics
</pre>
</td>
<td><p>Image registry, that can be shared across multiple helm charts</p>
</td>
</tr>
<tr>
<td>global.image.vm.tag</td>
<td>string</td>
<td><pre class="helm-vars-default-value language-yaml" lang="">
<code class="language-yaml">""
</code>
</pre>
</td>
<td><p>Image tag for all vm charts</p>
</td>
</tr>
<tr>
@@ -705,17 +682,6 @@ loggerFormat: json
</pre>
</td>
<td><p>Init containers for vmauth</p>
</td>
</tr>
<tr>
<td>vmauth.lifecycle</td>
<td>object</td>
<td><pre class="helm-vars-default-value language-yaml" lang="plaintext">
<code class="language-yaml">{}
</code>
</pre>
</td>
<td><p>Specify pod lifecycle</p>
</td>
</tr>
<tr>
@@ -1523,17 +1489,6 @@ loggerFormat: json
</pre>
</td>
<td><p>Init containers for vminsert</p>
</td>
</tr>
<tr>
<td>vminsert.lifecycle</td>
<td>object</td>
<td><pre class="helm-vars-default-value language-yaml" lang="plaintext">
<code class="language-yaml">{}
</code>
</pre>
</td>
<td><p>Specify pod lifecycle</p>
</td>
</tr>
<tr>
@@ -2386,17 +2341,6 @@ loggerFormat: json
</pre>
</td>
<td><p>Init containers for vmselect</p>
</td>
</tr>
<tr>
<td>vmselect.lifecycle</td>
<td>object</td>
<td><pre class="helm-vars-default-value language-yaml" lang="plaintext">
<code class="language-yaml">{}
</code>
</pre>
</td>
<td><p>Specify pod lifecycle</p>
</td>
</tr>
<tr>
@@ -3269,17 +3213,6 @@ loggerFormat: json
</pre>
</td>
<td><p>Init containers for vmstorage</p>
</td>
</tr>
<tr>
<td>vmstorage.lifecycle</td>
<td>object</td>
<td><pre class="helm-vars-default-value language-yaml" lang="plaintext">
<code class="language-yaml">{}
</code>
</pre>
</td>
<td><p>Specify pod lifecycle</p>
</td>
</tr>
<tr>

View File

@@ -4,14 +4,6 @@
- TODO
## 0.0.39
**Release date:** 05 Feb 2025
![Helm: v3](https://img.shields.io/badge/Helm-v3.14%2B-informational?color=informational&logo=helm&link=https%3A%2F%2Fgithub.com%2Fhelm%2Fhelm%2Freleases%2Ftag%2Fv3.14.0)
- Fix overwrite per service empty registry
## 0.0.38
**Release date:** 04 Feb 2025

View File

@@ -1,6 +1,6 @@
![Version](https://img.shields.io/badge/0.0.39-gray?logo=Helm&labelColor=gray&link=https%3A%2F%2Fdocs.victoriametrics.com%2Fhelm%2Fvictoria-metrics-common%2Fchangelog%2F%230039)
![Version](https://img.shields.io/badge/0.0.38-gray?logo=Helm&labelColor=gray&link=https%3A%2F%2Fdocs.victoriametrics.com%2Fhelm%2Fvictoria-metrics-common%2Fchangelog%2F%230038)
![ArtifactHub](https://img.shields.io/badge/ArtifactHub-informational?logoColor=white&color=417598&logo=artifacthub&link=https%3A%2F%2Fartifacthub.io%2Fpackages%2Fhelm%2Fvictoriametrics%2Fvictoria-metrics-common)
![License](https://img.shields.io/github/license/VictoriaMetrics/helm-charts?labelColor=green&label=&link=https%3A%2F%2Fgithub.com%2FVictoriaMetrics%2Fhelm-charts%2Fblob%2Fmaster%2FLICENSE)
![Slack](https://img.shields.io/badge/Join-4A154B?logo=slack&link=https%3A%2F%2Fslack.victoriametrics.com)

View File

@@ -1,6 +1,6 @@
## Next release
- updated common dependency 0.0.37 -> 0.0.39
- TODO
## 0.7.4

View File

@@ -1,6 +1,6 @@
## Next release
- updated common dependency 0.0.37 -> 0.0.39
- TODO
## 0.6.6

View File

@@ -2,31 +2,6 @@
- TODO
## 0.36.0
**Release date:** 07 Feb 2025
![Helm: v3](https://img.shields.io/badge/Helm-v3.14%2B-informational?color=informational&logo=helm&link=https%3A%2F%2Fgithub.com%2Fhelm%2Fhelm%2Freleases%2Ftag%2Fv3.14.0) ![AppVersion: v1.110.0](https://img.shields.io/badge/v1.110.0-success?logo=VictoriaMetrics&labelColor=gray&link=https%3A%2F%2Fdocs.victoriametrics.com%2Fchangelog%23v11100)
- updates operator to [v0.53.0](https://github.com/VictoriaMetrics/operator/releases/tag/v0.53.0) version
## 0.35.7
**Release date:** 06 Feb 2025
![Helm: v3](https://img.shields.io/badge/Helm-v3.14%2B-informational?color=informational&logo=helm&link=https%3A%2F%2Fgithub.com%2Fhelm%2Fhelm%2Freleases%2Ftag%2Fv3.14.0) ![AppVersion: v1.110.0](https://img.shields.io/badge/v1.110.0-success?logo=VictoriaMetrics&labelColor=gray&link=https%3A%2F%2Fdocs.victoriametrics.com%2Fchangelog%23v11100)
- Added .Values.alertmanager.useManagedConfig to switch storing Alertmanager config in VMAlertmanagerConfig CR instead of k8s Secret. See [this issue](https://github.com/VictoriaMetrics/helm-charts/issues/1968).
- updated common dependency 0.0.37 -> 0.0.39
## 0.35.6
**Release date:** 05 Feb 2025
![Helm: v3](https://img.shields.io/badge/Helm-v3.14%2B-informational?color=informational&logo=helm&link=https%3A%2F%2Fgithub.com%2Fhelm%2Fhelm%2Freleases%2Ftag%2Fv3.14.0) ![AppVersion: v1.110.0](https://img.shields.io/badge/v1.110.0-success?logo=VictoriaMetrics&labelColor=gray&link=https%3A%2F%2Fdocs.victoriametrics.com%2Fchangelog%23v11100)
- Use GrafanaDatasource name sanitizing to fix plugin import
## 0.35.5
**Release date:** 04 Feb 2025

View File

@@ -1,6 +1,6 @@
![Version](https://img.shields.io/badge/0.36.0-gray?logo=Helm&labelColor=gray&link=https%3A%2F%2Fdocs.victoriametrics.com%2Fhelm%2Fvictoria-metrics-k8s-stack%2Fchangelog%2F%230360)
![Version](https://img.shields.io/badge/0.35.5-gray?logo=Helm&labelColor=gray&link=https%3A%2F%2Fdocs.victoriametrics.com%2Fhelm%2Fvictoria-metrics-k8s-stack%2Fchangelog%2F%230355)
![ArtifactHub](https://img.shields.io/badge/ArtifactHub-informational?logoColor=white&color=417598&logo=artifacthub&link=https%3A%2F%2Fartifacthub.io%2Fpackages%2Fhelm%2Fvictoriametrics%2Fvictoria-metrics-k8s-stack)
![License](https://img.shields.io/github/license/VictoriaMetrics/helm-charts?labelColor=green&label=&link=https%3A%2F%2Fgithub.com%2FVictoriaMetrics%2Fhelm-charts%2Fblob%2Fmaster%2FLICENSE)
![Slack](https://img.shields.io/badge/Join-4A154B?logo=slack&link=https%3A%2F%2Fslack.victoriametrics.com)
@@ -515,6 +515,8 @@ Change the values according to the need of the environment in ``victoria-metrics
- name: blackhole
route:
receiver: blackhole
templates:
- /etc/vm/configs/**/*.tmpl
</code>
</pre>
</td>
@@ -611,17 +613,6 @@ selectAllByDefault: true
</pre>
</td>
<td><p>Extra alert templates</p>
</td>
</tr>
<tr>
<td>alertmanager.useManagedConfig</td>
<td>bool</td>
<td><pre class="helm-vars-default-value language-yaml" lang="">
<code class="language-yaml">false
</code>
</pre>
</td>
<td><p>enable storing .Values.alertmanager.config in VMAlertmanagerConfig instead of k8s Secret</p>
</td>
</tr>
<tr>

View File

@@ -1,22 +1,6 @@
## Next release
- updated common dependency 0.0.37 -> 0.0.39
## 0.42.0
**Release date:** 05 Feb 2025
![Helm: v3](https://img.shields.io/badge/Helm-v3.14%2B-informational?color=informational&logo=helm&link=https%3A%2F%2Fgithub.com%2Fhelm%2Fhelm%2Freleases%2Ftag%2Fv3.14.0) ![AppVersion: v0.53.0](https://img.shields.io/badge/v0.53.0-success?logo=VictoriaMetrics&labelColor=gray&link=https%3A%2F%2Fdocs.victoriametrics.com%2Foperator%2Fchangelog%23v0530)
- updates operator to [v0.53.0](https://github.com/VictoriaMetrics/operator/releases/tag/v0.53.0) version
## 0.41.2
**Release date:** 05 Feb 2025
![Helm: v3](https://img.shields.io/badge/Helm-v3.14%2B-informational?color=informational&logo=helm&link=https%3A%2F%2Fgithub.com%2Fhelm%2Fhelm%2Freleases%2Ftag%2Fv3.14.0) ![AppVersion: v0.52.0](https://img.shields.io/badge/v0.52.0-success?logo=VictoriaMetrics&labelColor=gray&link=https%3A%2F%2Fdocs.victoriametrics.com%2Foperator%2Fchangelog%23v0520)
- added `.Values.allowedMetricsEndpoints`
- TODO
## 0.41.1

View File

@@ -1,6 +1,6 @@
![Version](https://img.shields.io/badge/0.42.0-gray?logo=Helm&labelColor=gray&link=https%3A%2F%2Fdocs.victoriametrics.com%2Fhelm%2Fvictoria-metrics-operator%2Fchangelog%2F%230420)
![Version](https://img.shields.io/badge/0.41.1-gray?logo=Helm&labelColor=gray&link=https%3A%2F%2Fdocs.victoriametrics.com%2Fhelm%2Fvictoria-metrics-operator%2Fchangelog%2F%230411)
![ArtifactHub](https://img.shields.io/badge/ArtifactHub-informational?logoColor=white&color=417598&logo=artifacthub&link=https%3A%2F%2Fartifacthub.io%2Fpackages%2Fhelm%2Fvictoriametrics%2Fvictoria-metrics-operator)
![License](https://img.shields.io/github/license/VictoriaMetrics/helm-charts?labelColor=green&label=&link=https%3A%2F%2Fgithub.com%2FVictoriaMetrics%2Fhelm-charts%2Fblob%2Fmaster%2FLICENSE)
![Slack](https://img.shields.io/badge/Join-4A154B?logo=slack&link=https%3A%2F%2Fslack.victoriametrics.com)
@@ -384,26 +384,6 @@ duration: 45800h0m0s
<td><p>Pod affinity</p>
</td>
</tr>
<tr>
<td>allowedMetricsEndpoints[0]</td>
<td>string</td>
<td><pre class="helm-vars-default-value language-yaml" lang="">
<code class="language-yaml">/metrics
</code>
</pre>
</td>
<td></td>
</tr>
<tr>
<td>allowedMetricsEndpoints[1]</td>
<td>string</td>
<td><pre class="helm-vars-default-value language-yaml" lang="">
<code class="language-yaml">/metrics/resources
</code>
</pre>
</td>
<td></td>
</tr>
<tr>
<td>annotations</td>
<td>object</td>

View File

@@ -2,15 +2,6 @@
- TODO
## 0.13.9
**Release date:** 06 Feb 2025
![Helm: v3](https://img.shields.io/badge/Helm-v3.14%2B-informational?color=informational&logo=helm&link=https%3A%2F%2Fgithub.com%2Fhelm%2Fhelm%2Freleases%2Ftag%2Fv3.14.0) ![AppVersion: v1.110.0](https://img.shields.io/badge/v1.110.0-success?logo=VictoriaMetrics&labelColor=gray&link=https%3A%2F%2Fdocs.victoriametrics.com%2Foperator%2Fchangelog%23v11100)
- added ability to override default headless service .Values.server.service.clusterIP with empty value
- updated common dependency 0.0.37 -> 0.0.39
## 0.13.8
**Release date:** 27 Jan 2025

View File

@@ -1,6 +1,6 @@
![Version](https://img.shields.io/badge/0.13.9-gray?logo=Helm&labelColor=gray&link=https%3A%2F%2Fdocs.victoriametrics.com%2Fhelm%2Fvictoria-metrics-single%2Fchangelog%2F%230139)
![Version](https://img.shields.io/badge/0.13.8-gray?logo=Helm&labelColor=gray&link=https%3A%2F%2Fdocs.victoriametrics.com%2Fhelm%2Fvictoria-metrics-single%2Fchangelog%2F%230138)
![ArtifactHub](https://img.shields.io/badge/ArtifactHub-informational?logoColor=white&color=417598&logo=artifacthub&link=https%3A%2F%2Fartifacthub.io%2Fpackages%2Fhelm%2Fvictoriametrics%2Fvictoria-metrics-single)
![License](https://img.shields.io/github/license/VictoriaMetrics/helm-charts?labelColor=green&label=&link=https%3A%2F%2Fgithub.com%2FVictoriaMetrics%2Fhelm-charts%2Fblob%2Fmaster%2FLICENSE)
![Slack](https://img.shields.io/badge/Join-4A154B?logo=slack&link=https%3A%2F%2Fslack.victoriametrics.com)
@@ -1206,7 +1206,7 @@ scrape_configs:
<td>server.service.clusterIP</td>
<td>string</td>
<td><pre class="helm-vars-default-value language-yaml" lang="">
<code class="language-yaml">None
<code class="language-yaml">""
</code>
</pre>
</td>

View File

@@ -708,28 +708,16 @@ func lessString(a, b string) bool {
return false
}
if iA, okA := tryParseInt64(a); okA {
if iB, okB := tryParseInt64(b); okB {
return iA < iB
}
nA, okA := tryParseUint64(a)
nB, okB := tryParseUint64(b)
if okA && okB {
return nA < nB
}
if uA, okA := tryParseUint64(a); okA {
if uB, okB := tryParseUint64(b); okB {
return uA < uB
}
}
if tsA, okA := TryParseTimestampRFC3339Nano(a); okA {
if tsB, okB := TryParseTimestampRFC3339Nano(b); okB {
return tsA < tsB
}
}
if fA, okA := tryParseNumber(a); okA {
if fB, okB := tryParseNumber(b); okB {
return fA < fB
}
fA, okA := tryParseNumber(a)
fB, okB := tryParseNumber(b)
if okA && okB {
return fA < fB
}
return stringsutil.LessNatural(a, b)

View File

@@ -1,55 +0,0 @@
package logstorage
import (
"testing"
)
func TestLessString(t *testing.T) {
f := func(a, b string, resultExpected bool) {
t.Helper()
result := lessString(a, b)
if result != resultExpected {
t.Fatalf("unexpected result for lessString(%q, %q); got %v; want %v", a, b, result, resultExpected)
}
}
f("", "", false)
f("a", "", false)
f("", "a", true)
f("foo", "bar", false)
f("bar", "foo", true)
f("foo", "foo", false)
f("foo1", "foo", false)
f("foo", "foo1", true)
// integers
f("123", "9", false)
f("9", "123", true)
f("-123", "9", true)
f("9", "-123", false)
// floating point numbers
f("1e3", "5", false)
f("5", "1e3", true)
// timestamps
f("2025-01-15T10:20:30.1", "2025-01-15T10:20:30.09", false)
f("2025-01-15T10:20:30.09", "2025-01-15T10:20:30.1", true)
// versions
f("v1.23.4", "v1.23.10", true)
f("v1.23.10", "v1.23.4", false)
// durations
f("1h", "5s", false)
f("5s", "1h", true)
// bytes
f("1MB", "5KB", false)
f("5KB", "1MB", true)
f("1.5M", "5.1K", false)
f("5.1K", "1.5M", true)
f("1.5M", "1.5M", false)
}