Compare commits

..

17 Commits

Author SHA1 Message Date
Zakhar Bessarab
6ed31b9180 docs/changelog: mention LTS releases, update release date
Signed-off-by: Zakhar Bessarab <z.bessarab@victoriametrics.com>
2025-02-10 20:23:02 +04:00
Aliaksandr Valialkin
00fb218269 deployment/docker: update VictoriaLogs docker image from v1.8.0-victorialogs to v1.9.0-victorialogs
See https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.9.0-victorialogs
2025-02-10 15:29:52 +01:00
Aliaksandr Valialkin
aa0ac1d0ed docs/VictoriaLogs/CHANGELOG.md: cut v1.9.0-victorialogs release 2025-02-10 15:21:56 +01:00
Aliaksandr Valialkin
81d359507d lib/logstorage: properly compare RFC3339 timestamps with sub-second precision in lessString()
Previously RFC3339 timestamps with sub-second precision could be incorrectly compared by lessString().
For example, 2025-01-20T10:20:30.1Z was incorrectly treated as smaller than 2025-01-20T10:20:30.09Z,
because the first timestamp has smaller decimal number after the last dot than the second timestamp.
2025-02-10 15:00:59 +01:00
Aliaksandr Valialkin
ebac07bcf6 app/vlinsert: continue parsing JSON lines in the input stream after parse errors
Previosly the parsing of the input stream was stopped after the first parse error.
This isn't what most users expect when ingesting JSON lines in a stream where some JSON lines may be invalid.
2025-02-10 15:00:58 +01:00
Github Actions
92bfb7ba15 Automatic update helm docs from VictoriaMetrics/helm-charts@0fe31fd (#8256)
Automated changes by
[create-pull-request](https://github.com/peter-evans/create-pull-request)
GitHub action

Signed-off-by: Github Actions <133988544+victoriametrics-bot@users.noreply.github.com>
Co-authored-by: zekker6 <1367798+zekker6@users.noreply.github.com>
2025-02-10 13:22:42 +01:00
Yury Molodov
6a8fa799c6 vmui: migrate build process to Vite (#8213)
### Describe Your Changes

- Migrated build process from Webpack (CRA) to Vite  
Reason for migration: `create-react-app` has been
[deprecated](b532a58792)
and contains outdated dependencies that haven’t been updated for over
two years, leading to security vulnerabilities. Additionally, build
speed improved by more than 2x.
- Updated dependencies and fixed TypeScript typings in accordance with
the updates


b532a58792

### Checklist

The following checks are **mandatory**:

- [ ] My change adheres [VictoriaMetrics contributing
guidelines](https://docs.victoriametrics.com/contributing/).
2025-02-10 10:59:11 +01:00
Aliaksandr Valialkin
5e7866c3a0 deployment/docker: update Go builder from Go1.23.5 to Go1.23.6
See https://github.com/golang/go/issues?q=milestone%3AGo1.23.6+label%3ACherryPickApproved
2025-02-09 22:50:04 +01:00
Aliaksandr Valialkin
c26cbf57dd app/vlinsert: accept timestamps with microsecond and nanosecond precision at _time field 2025-02-09 22:41:38 +01:00
Zakhar Bessarab
ee8f852a83 docs: cut v1.111.0
Signed-off-by: Zakhar Bessarab <z.bessarab@victoriametrics.com>
2025-02-07 19:42:11 +04:00
Zakhar Bessarab
80a5f6863b docs: update refs to new release
Signed-off-by: Zakhar Bessarab <z.bessarab@victoriametrics.com>
2025-02-07 18:53:01 +04:00
Zakhar Bessarab
8e576854b3 make vmui-update
Signed-off-by: Zakhar Bessarab <z.bessarab@victoriametrics.com>
2025-02-07 18:41:53 +04:00
hagen1778
5dbca072bb dashboards: update vmagent dashboard
* add troubleshooting link
* add panels for CPU and mem usage
* rm unnecessary links from panels
* update to grafana v11

Signed-off-by: hagen1778 <roman@victoriametrics.com>
2025-02-06 16:58:07 +01:00
hagen1778
375548ca4b deployment: bump grafana to grafana/grafana:11.5.0
Signed-off-by: hagen1778 <roman@victoriametrics.com>
2025-02-06 16:16:13 +01:00
hagen1778
f0424facf7 deployment: bump alertmanager to prom/alertmanager:v0.28.0
Signed-off-by: hagen1778 <roman@victoriametrics.com>
2025-02-06 16:15:34 +01:00
Aliaksandr Valialkin
48602a1ae8 lib/logstorage: optimize performance for stats, top and uniq pipes a bit
Split unique values (groups) into shards according to the configured concurrency
during processing of the matching rows if the number of unique values exceeds the hardcoded threshold.
Previously this splitting was performed unconditionally at the merge stage when merging independently
calculated per-CPU states into a single state. It is faster to perform the split during rows processing
if the number of unique values is big.

This gives up to 30% perfromance improvements when these pipes are applied to big number of unique values (groups).
2025-02-06 13:46:32 +01:00
Roman Khavronenko
0a6f6d0bba app/vmselect/netstorage: stop exposing `vm_index_search_duration_seconds 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.

See
[example](https://play.victoriametrics.com/select/accounting/1/6a716b0f-38bc-4856-90ce-448fd713e3fe/prometheus/graph/#/cardinality?date=2025-02-05&match=vm_index_search_duration_seconds_bucket&topN=10&focusLabel=).

 Hence, dropping it as unused.

---------
Signed-off-by: hagen1778 <roman@victoriametrics.com>
2025-02-06 13:40:52 +01:00
131 changed files with 6600 additions and 18417 deletions

View File

@@ -2,20 +2,19 @@ package insertutils
import (
"fmt"
"math"
"strconv"
"time"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logstorage"
)
// ExtractTimestampRFC3339NanoFromFields extracts RFC3339 timestamp in nanoseconds from the field with the name timeField at fields.
// ExtractTimestampFromFields extracts timestamp in nanoseconds from the field with the name timeField at fields.
//
// The value for the timeField is set to empty string after returning from the function,
// so it could be ignored during data ingestion.
//
// The current timestamp is returned if fields do not contain a field with timeField name or if the timeField value is empty.
func ExtractTimestampRFC3339NanoFromFields(timeField string, fields []logstorage.Field) (int64, error) {
func ExtractTimestampFromFields(timeField string, fields []logstorage.Field) (int64, error) {
for i := range fields {
f := &fields[i]
if f.Name != timeField {
@@ -48,22 +47,24 @@ func parseTimestamp(s string) (int64, error) {
return nsecs, nil
}
// ParseUnixTimestamp parses s as unix timestamp in either seconds or milliseconds and returns the parsed timestamp in nanoseconds.
// ParseUnixTimestamp parses s as unix timestamp in seconds, milliseconds, microseconds or nanoseconds and returns the parsed timestamp in nanoseconds.
func ParseUnixTimestamp(s string) (int64, error) {
n, err := strconv.ParseInt(s, 10, 64)
if err != nil {
return 0, fmt.Errorf("cannot parse unix timestamp from %q: %w", s, err)
}
if n < (1<<31) && n >= (-1<<31) {
// The timestamp is in seconds. Convert it to milliseconds
n *= 1e3
// The timestamp is in seconds.
return n * 1e9, nil
}
if n > int64(math.MaxInt64)/1e6 {
return 0, fmt.Errorf("too big timestamp in milliseconds: %d; mustn't exceed %d", n, int64(math.MaxInt64)/1e6)
if n < 1e3*(1<<31) && n >= 1e3*(-1<<31) {
// The timestamp is in milliseconds.
return n * 1e6, nil
}
if n < int64(math.MinInt64)/1e6 {
return 0, fmt.Errorf("too small timestamp in milliseconds: %d; must be bigger than %d", n, int64(math.MinInt64)/1e6)
if n < 1e6*(1<<31) && n >= 1e6*(-1<<31) {
// The timestamp is in microseconds.
return n * 1e3, nil
}
n *= 1e6
// The timestamp is in nanoseconds
return n, nil
}

View File

@@ -6,11 +6,11 @@ import (
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logstorage"
)
func TestExtractTimestampRFC3339NanoFromFields_Success(t *testing.T) {
func TestExtractTimestampFromFields_Success(t *testing.T) {
f := func(timeField string, fields []logstorage.Field, nsecsExpected int64) {
t.Helper()
nsecs, err := ExtractTimestampRFC3339NanoFromFields(timeField, fields)
nsecs, err := ExtractTimestampFromFields(timeField, fields)
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
@@ -51,6 +51,18 @@ func TestExtractTimestampRFC3339NanoFromFields_Success(t *testing.T) {
{Name: "foo", Value: "bar"},
}, 1718773640123456789)
// Unix timestamp in nanoseconds
f("time", []logstorage.Field{
{Name: "foo", Value: "bar"},
{Name: "time", Value: "1718773640123456789"},
}, 1718773640123456789)
// Unix timestamp in microseconds
f("time", []logstorage.Field{
{Name: "foo", Value: "bar"},
{Name: "time", Value: "1718773640123456"},
}, 1718773640123456000)
// Unix timestamp in milliseconds
f("time", []logstorage.Field{
{Name: "foo", Value: "bar"},
@@ -64,14 +76,14 @@ func TestExtractTimestampRFC3339NanoFromFields_Success(t *testing.T) {
}, 1718773640000000000)
}
func TestExtractTimestampRFC3339NanoFromFields_Error(t *testing.T) {
func TestExtractTimestampFromFields_Error(t *testing.T) {
f := func(s string) {
t.Helper()
fields := []logstorage.Field{
{Name: "time", Value: s},
}
nsecs, err := ExtractTimestampRFC3339NanoFromFields("time", fields)
nsecs, err := ExtractTimestampFromFields("time", fields)
if err == nil {
t.Fatalf("expecting non-nil error")
}
@@ -80,6 +92,7 @@ func TestExtractTimestampRFC3339NanoFromFields_Error(t *testing.T) {
}
}
// invalid time
f("foobar")
// incomplete time

View File

@@ -51,20 +51,13 @@ func RequestHandler(w http.ResponseWriter, r *http.Request) {
lmp := cp.NewLogMessageProcessor("jsonline")
streamName := fmt.Sprintf("remoteAddr=%s, requestURI=%q", httpserver.GetQuotedRemoteAddr(r), r.RequestURI)
err = processStreamInternal(streamName, reader, cp.TimeField, cp.MsgFields, lmp)
processStreamInternal(streamName, reader, cp.TimeField, cp.MsgFields, lmp)
lmp.MustClose()
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)
}
requestDuration.UpdateDuration(startTime)
}
func processStreamInternal(streamName string, r io.Reader, timeField string, msgFields []string, lmp insertutils.LogMessageProcessor) error {
func processStreamInternal(streamName string, r io.Reader, timeField string, msgFields []string, lmp insertutils.LogMessageProcessor) {
wcr := writeconcurrencylimiter.GetReader(r)
defer writeconcurrencylimiter.PutReader(wcr)
@@ -76,10 +69,10 @@ func processStreamInternal(streamName string, r io.Reader, timeField string, msg
wcr.DecConcurrency()
if err != nil {
errorsTotal.Inc()
return fmt.Errorf("cannot read line #%d in /jsonline request: %s", n, err)
logger.Warnf("jsonline: cannot read line #%d in /jsonline request: %s", n, err)
}
if !ok {
return nil
return
}
n++
}
@@ -96,16 +89,17 @@ func readLine(lr *insertutils.LineReader, timeField string, msgFields []string,
}
p := logstorage.GetJSONParser()
defer logstorage.PutJSONParser(p)
if err := p.ParseLogMessage(line); err != nil {
return false, fmt.Errorf("cannot parse json-encoded log entry: %w", err)
return true, fmt.Errorf("cannot parse json-encoded line: %w; line contents: %q", err, line)
}
ts, err := insertutils.ExtractTimestampRFC3339NanoFromFields(timeField, p.Fields)
ts, err := insertutils.ExtractTimestampFromFields(timeField, p.Fields)
if err != nil {
return false, fmt.Errorf("cannot get timestamp: %w", err)
return true, fmt.Errorf("cannot get timestamp from json-encoded line: %w; line contents: %q", err, line)
}
logstorage.RenameField(p.Fields, msgFields, "_msg")
lmp.AddRow(ts, p.Fields, nil)
logstorage.PutJSONParser(p)
return true, nil
}

View File

@@ -7,16 +7,14 @@ import (
"github.com/VictoriaMetrics/VictoriaMetrics/app/vlinsert/insertutils"
)
func TestProcessStreamInternal_Success(t *testing.T) {
func TestProcessStreamInternal(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)
if err := processStreamInternal("test", r, timeField, msgFields, tlp); err != nil {
t.Fatalf("unexpected error: %s", err)
}
processStreamInternal("test", r, timeField, msgFields, tlp)
if err := tlp.Verify(timestampsExpected, resultExpected); err != nil {
t.Fatal(err)
@@ -45,22 +43,37 @@ func TestProcessStreamInternal_Success(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
f("foobar")
data = "foobar"
timeField = "@timestamp"
msgField = "aaa"
timestampsExpected = nil
resultExpected = ``
f(data, timeField, msgField, timestampsExpected, resultExpected)
// invalid timestamp field
f(`{"time":"foobar"}`)
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)
}

View File

@@ -560,7 +560,7 @@ func processLine(line []byte, currentYear int, timezone *time.Location, useLocal
if useLocalTimestamp {
ts = time.Now().UnixNano()
} else {
nsecs, err := insertutils.ExtractTimestampRFC3339NanoFromFields("timestamp", p.Fields)
nsecs, err := insertutils.ExtractTimestampFromFields("timestamp", p.Fields)
if err != nil {
return fmt.Errorf("cannot get timestamp from syslog line %q: %w", line, err)
}

View File

@@ -2,7 +2,6 @@ package notifier
import (
"context"
"fmt"
"testing"
"time"
@@ -28,12 +27,10 @@ func TestBlackHoleNotifier_Send(t *testing.T) {
}
func TestBlackHoleNotifier_Close(t *testing.T) {
addr := "blackhole-close"
bh := newBlackHoleNotifier()
bh.addr = addr
if err := bh.Send(context.Background(), []Alert{{
GroupID: 0,
Name: "alert1",
Name: "alert0",
Start: time.Now().UTC(),
End: time.Now().UTC(),
Annotations: map[string]string{"a": "b", "c": "d", "e": "f"},
@@ -44,10 +41,10 @@ func TestBlackHoleNotifier_Close(t *testing.T) {
bh.Close()
defaultMetrics := metricset.GetDefaultSet()
alertMetricName := fmt.Sprintf("vmalert_alerts_sent_total{addr=%q}", addr)
alertMetricName := "vmalert_alerts_sent_total{addr=\"blackhole\"}"
for _, name := range defaultMetrics.ListMetricNames() {
if name == alertMetricName {
t.Fatalf("Metric name should have unregistered. But still present")
t.Fatalf("Metric name should have unregistered.But still present")
}
}
}

View File

@@ -1,56 +1,14 @@
package utils
import (
"sync"
"sync/atomic"
"github.com/VictoriaMetrics/metrics"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
)
import "github.com/VictoriaMetrics/metrics"
type namedMetric struct {
Name string
}
var usedMetrics map[string]*atomic.Int64
var usedMetricMu sync.Mutex
func trackUsedMetric(name string) {
usedMetricMu.Lock()
defer usedMetricMu.Unlock()
if usedMetrics == nil {
usedMetrics = make(map[string]*atomic.Int64)
}
if _, ok := usedMetrics[name]; !ok {
usedMetrics[name] = &atomic.Int64{}
}
usedMetrics[name].Add(1)
}
// Unregister removes the metric by name from default registry
func (nm namedMetric) Unregister() {
if usedMetrics == nil {
logger.Fatalf("BUG: unregistered metric %q before registering", nm.Name)
}
usedMetricMu.Lock()
counter, ok := usedMetrics[nm.Name]
if !ok {
logger.Fatalf("BUG: unregistered metric %q before registering", nm.Name)
}
current := counter.Add(-1)
usedMetricMu.Unlock()
if current < 0 {
logger.Fatalf("BUG: negative metric counter for %q", nm.Name)
}
if current == 0 {
metrics.UnregisterMetric(nm.Name)
}
metrics.UnregisterMetric(nm.Name)
}
// Gauge is a metrics.Gauge with Name
@@ -61,7 +19,6 @@ type Gauge struct {
// GetOrCreateGauge creates a new Gauge with the given name
func GetOrCreateGauge(name string, f func() float64) *Gauge {
trackUsedMetric(name)
return &Gauge{
namedMetric: namedMetric{Name: name},
Gauge: metrics.GetOrCreateGauge(name, f),
@@ -76,7 +33,6 @@ type Counter struct {
// GetOrCreateCounter creates a new Counter with the given name
func GetOrCreateCounter(name string) *Counter {
trackUsedMetric(name)
return &Counter{
namedMetric: namedMetric{Name: name},
Counter: metrics.GetOrCreateCounter(name),
@@ -91,7 +47,6 @@ type Summary struct {
// GetOrCreateSummary creates a new Summary with the given name
func GetOrCreateSummary(name string) *Summary {
trackUsedMetric(name)
return &Summary{
namedMetric: namedMetric{Name: name},
Summary: metrics.GetOrCreateSummary(name),

View File

@@ -1,52 +0,0 @@
package utils
import (
"testing"
"github.com/VictoriaMetrics/metrics"
)
func isMetricRegistered(name string) bool {
metricNames := metrics.GetDefaultSet().ListMetricNames()
for _, mn := range metricNames {
if mn == name {
return true
}
}
return false
}
func TestMetricIsUnregistered(t *testing.T) {
metricName := "example_runs_total"
c := GetOrCreateCounter(metricName)
if !isMetricRegistered(metricName) {
t.Errorf("Expected metric %s to be present", metricName)
}
c.Unregister()
if isMetricRegistered(metricName) {
t.Errorf("Expected metric %s to be unregistered", metricName)
}
}
func TestMetricIsRemovedIfNoUses(t *testing.T) {
metricName := "example_runs_total"
c := GetOrCreateCounter(metricName)
c2 := GetOrCreateCounter(metricName)
if !isMetricRegistered(metricName) {
t.Errorf("Expected metric %s to be present", metricName)
}
c.Unregister()
// metric should still be registered since c2 is using it
if !isMetricRegistered(metricName) {
t.Errorf("Expected metric %s to be present", metricName)
}
c2.Unregister()
if isMetricRegistered(metricName) {
t.Errorf("Expected metric %s to be unregistered", metricName)
}
}

View File

@@ -8,7 +8,6 @@ import (
"sort"
"sync"
"sync/atomic"
"time"
"unsafe"
"github.com/VictoriaMetrics/metrics"
@@ -1002,9 +1001,7 @@ func ExportBlocks(qt *querytracer.Tracer, sq *storage.SearchQuery, deadline sear
sr := getStorageSearch()
defer putStorageSearch(sr)
startTime := time.Now()
sr.Init(qt, vmstorage.Storage, tfss, tr, sq.MaxMetrics, deadline.Deadline())
indexSearchDuration.UpdateDuration(startTime)
// Start workers that call f in parallel on available CPU cores.
workCh := make(chan *exportWork, gomaxprocs*8)
@@ -1142,9 +1139,7 @@ func ProcessSearchQuery(qt *querytracer.Tracer, sq *storage.SearchQuery, deadlin
defer vmstorage.WG.Done()
sr := getStorageSearch()
startTime := time.Now()
maxSeriesCount := sr.Init(qt, vmstorage.Storage, tfss, tr, sq.MaxMetrics, deadline.Deadline())
indexSearchDuration.UpdateDuration(startTime)
type blockRefs struct {
brs []blockRef
}
@@ -1296,8 +1291,6 @@ func ProcessSearchQuery(qt *querytracer.Tracer, sq *storage.SearchQuery, deadlin
return &rss, nil
}
var indexSearchDuration = metrics.NewHistogram(`vm_index_search_duration_seconds`)
type blockRef struct {
partRef storage.PartRef
addr tmpBlockAddr

View File

@@ -1,13 +1,13 @@
{
"files": {
"main.css": "./static/css/main.af583aad.css",
"main.js": "./static/js/main.1413b18d.js",
"main.css": "./static/css/main.7fa18e1b.css",
"main.js": "./static/js/main.ba08300f.js",
"static/js/685.f772060c.chunk.js": "./static/js/685.f772060c.chunk.js",
"static/media/MetricsQL.md": "./static/media/MetricsQL.a00044c91d9781cf8557.md",
"index.html": "./index.html"
},
"entrypoints": [
"static/css/main.af583aad.css",
"static/js/main.1413b18d.js"
"static/css/main.7fa18e1b.css",
"static/js/main.ba08300f.js"
]
}

View File

@@ -1 +1 @@
<!doctype html><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"><meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=5"/><meta name="theme-color" content="#000000"/><meta name="description" content="Explore and troubleshoot your VictoriaMetrics data"/><link rel="manifest" href="./manifest.json"/><title>vmui</title><script src="./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 property="og:type" content="website"><meta property="og:title" content="UI for VictoriaMetrics"><meta property="og:url" content="https://victoriametrics.com/"><meta property="og:description" content="Explore and troubleshoot your VictoriaMetrics data"><script defer="defer" src="./static/js/main.1413b18d.js"></script><link href="./static/css/main.af583aad.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="./favicon.svg"/><link rel="apple-touch-icon" href="./favicon.svg"/><link rel="mask-icon" href="./favicon.svg" color="#000000"><meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=5"/><meta name="theme-color" content="#000000"/><meta name="description" content="Explore and troubleshoot your VictoriaMetrics data"/><link rel="manifest" href="./manifest.json"/><title>vmui</title><script src="./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 property="og:type" content="website"><meta property="og:title" content="UI for VictoriaMetrics"><meta property="og:url" content="https://victoriametrics.com/"><meta property="og:description" content="Explore and troubleshoot your VictoriaMetrics data"><script defer="defer" src="./static/js/main.ba08300f.js"></script><link href="./static/css/main.7fa18e1b.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,4 +1,4 @@
FROM golang:1.23.5 AS build-web-stage
FROM golang:1.23.6 AS build-web-stage
COPY build /build
WORKDIR /build

View File

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

View File

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

View File

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

View File

@@ -1,48 +0,0 @@
// 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

@@ -1,42 +0,0 @@
/* 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

@@ -0,0 +1,23 @@
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

@@ -0,0 +1,90 @@
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,
},
}];

View File

@@ -2,9 +2,9 @@
<html lang="en">
<head>
<meta charset="utf-8"/>
<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">
<link rel="icon" href="/favicon.svg"/>
<link rel="apple-touch-icon" href="/favicon.svg"/>
<link rel="mask-icon" href="/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="%PUBLIC_URL%/manifest.json"/>
<link rel="manifest" href="/manifest.json"/>
<!--
Notice the use of %PUBLIC_URL% in the tags above.
Notice the use of 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", "%PUBLIC_URL%/favicon.ico" will
Unlike "/favicon.ico" or "favicon.ico", "/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="%PUBLIC_URL%/dashboards/index.js" type="module"></script>
<script src="/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="%PUBLIC_URL%/preview.jpg">
<meta name="twitter:image" content="/preview.jpg">
<meta property="og:type" content="website">
<meta property="og:title" content="UI for VictoriaMetrics">
@@ -50,5 +50,6 @@
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="%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">
<link rel="icon" href="/favicon.svg" />
<link rel="apple-touch-icon" href="/favicon.svg" />
<link rel="mask-icon" href="/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="%PUBLIC_URL%/manifest.json"/>
<link rel="manifest" href="/manifest.json"/>
<!--
Notice the use of %PUBLIC_URL% in the tags above.
Notice the use of 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", "%PUBLIC_URL%/favicon.ico" will
Unlike "/favicon.ico" or "favicon.ico", "/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="%PUBLIC_URL%/preview.jpg">
<meta name="twitter:image" content="/preview.jpg">
<meta property="og:type" content="website">
<meta property="og:title" content="UI for VictoriaLogs">
@@ -49,5 +49,6 @@
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="%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">
<link rel="icon" href="/favicon.svg" />
<link rel="apple-touch-icon" href="/favicon.svg" />
<link rel="mask-icon" href="/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="%PUBLIC_URL%/manifest.json"/>
<link rel="manifest" href="/manifest.json"/>
<!--
Notice the use of %PUBLIC_URL% in the tags above.
Notice the use of 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", "%PUBLIC_URL%/favicon.ico" will
Unlike "/favicon.ico" or "favicon.ico", "/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="%PUBLIC_URL%/preview.jpg">
<meta name="twitter:image" content="/preview.jpg">
<meta property="og:type" content="website">
<meta property="og:title" content="UI for VictoriaMetrics Anomaly Detection">
@@ -49,5 +49,6 @@
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>

File diff suppressed because it is too large Load Diff

View File

@@ -3,50 +3,40 @@
"version": "0.1.0",
"private": true,
"homepage": "./",
"type": "module",
"dependencies": {
"@types/lodash.debounce": "^4.0.9",
"@types/lodash.get": "^4.4.9",
"@types/lodash.throttle": "^4.1.9",
"@types/node": "^22.5.4",
"@types/qs": "^6.9.15",
"@types/react-input-mask": "^3.0.5",
"@types/qs": "^6.9.18",
"@types/react": "^19.0.8",
"@types/react-input-mask": "^3.0.6",
"@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",
"lodash.throttle": "^4.1.1",
"marked": "^14.1.2",
"marked-emoji": "^1.4.2",
"preact": "^10.23.2",
"qs": "^6.13.0",
"marked": "^15.0.6",
"marked-emoji": "^1.4.3",
"preact": "^10.25.4",
"qs": "^6.14.0",
"react-input-mask": "^2.0.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"
"react-router-dom": "^7.1.5",
"uplot": "^1.6.31",
"vite": "^6.0.11",
"web-vitals": "^4.2.4"
},
"scripts": {
"prestart": "npm run copy-metricsql-docs",
"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"
]
"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"
},
"browserslist": {
"production": [
@@ -61,26 +51,24 @@
]
},
"devDependencies": {
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.16.7",
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6",
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
"@types/react-dom": "^18.3.0",
"@typescript-eslint/eslint-plugin": "^5.15.0",
"@typescript-eslint/parser": "^5.15.0",
"@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",
"cross-env": "^7.0.3",
"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"
}
"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"
}
}

View File

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

View File

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

View File

@@ -1,4 +1,5 @@
@use "src/styles/variables" as *;
@use 'sass:color';
$color-bar: #33BB55;
$color-bar-highest: #F79420;
@@ -7,7 +8,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 {
@@ -54,19 +55,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: lighten($color-bar, 10%);
background-color: color.scale($color-bar, $lightness: 40%);
}
&:first-child {
background-color: $color-bar-highest;
&:hover {
background-color: lighten($color-bar-highest, 10%);
background-color: color.scale($color-bar-highest, $lightness: 40%);
}
}
}

View File

@@ -12,14 +12,11 @@ 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;
}
@@ -48,21 +45,21 @@ const GlobalSettings: FC = () => {
const controls = [
{
show: !appModeEnable && !isLogsApp,
show: !appModeEnable && !APP_TYPE_LOGS,
component: <ServerConfigurator
ref={serverSettingRef}
onClose={handleClose}
/>
},
{
show: !isLogsApp,
show: !APP_TYPE_LOGS,
component: <LimitsConfigurator
ref={limitsSettingRef}
onClose={handleClose}
/>
},
{
show: isLogsApp,
show: APP_TYPE_LOGS,
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;
this.map.delete(firstKey);
firstKey && this.map.delete(firstKey);
}
this.map.set(JSON.stringify(key), value);
}

View File

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

View File

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

View File

@@ -8,11 +8,6 @@
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, MouseEvent>) => void
onMouseDown?: (e: ReactMouseEvent<HTMLButtonElement, MouseEvent>) => void
onClick?: (e: ReactMouseEvent<HTMLButtonElement>) => void
onMouseDown?: (e: ReactMouseEvent<HTMLButtonElement>) => void
}
const Button: FC<ButtonProps> = ({

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,15 @@
import React, { FC, MouseEvent as ReactMouseEvent, ReactNode, useEffect, useMemo, useRef, useState } from "react";
import React, {
FC,
MouseEvent as ReactMouseEvent,
ReactNode,
useEffect,
useMemo,
useRef,
useState,
useCallback,
createPortal
} from "react";
import classNames from "classnames";
import ReactDOM from "react-dom";
import "./style.scss";
import useClickOutside from "../../../hooks/useClickOutside";
import useDeviceDetect from "../../../hooks/useDeviceDetect";
@@ -8,7 +17,6 @@ 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
@@ -119,7 +127,7 @@ const Popper: FC<PopperProps> = ({
return position;
}, [buttonRef, placement, isOpen, children, fullWidth]);
const handleClickClose = (e: ReactMouseEvent<HTMLButtonElement, MouseEvent>) => {
const handleClickClose = (e: ReactMouseEvent<HTMLButtonElement>) => {
e.stopPropagation();
onClose();
};
@@ -156,7 +164,7 @@ const Popper: FC<PopperProps> = ({
return (
<>
{(isOpen || !popperSize.width) && ReactDOM.createPortal((
{(isOpen || !popperSize.width) && 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) => {
const createHandleClick = (value: string) => (e: MouseEvent<HTMLDivElement>) => {
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) => {
const createHandleClick = (value: string) => (e: MouseEvent<HTMLDivElement>) => {
handleSelected(value);
e.stopPropagation();
};

View File

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

View File

@@ -1,8 +1,6 @@
import React, { FC, useEffect, useMemo, useRef, useState, Fragment } from "preact/compat";
import ReactDOM from "react-dom";
import React, { FC, useEffect, useMemo, useRef, useState, Fragment, createPortal } from "preact/compat";
import "./style.scss";
import { ReactNode } from "react";
import { ExoticComponent } from "react";
import useDeviceDetect from "../../../hooks/useDeviceDetect";
interface TooltipProps {
@@ -25,7 +23,7 @@ const Tooltip: FC<TooltipProps> = ({
const [isOpen, setIsOpen] = useState(false);
const [popperSize, setPopperSize] = useState({ width: 0, height: 0 });
const buttonRef = useRef<ExoticComponent>(null);
const buttonRef = useRef<ReactNode>(null);
const popperRef = useRef<HTMLDivElement>(null);
const onScrollWindow = () => setIsOpen(false);
@@ -120,7 +118,7 @@ const Tooltip: FC<TooltipProps> = ({
{children}
</Fragment>
{!isMobile && isOpen && ReactDOM.createPortal((
{!isMobile && isOpen && 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 from "../../Main/TextField/TextField";
import { KeyboardEvent, useState } from "react";
import TextField, { TextFieldKeyboardEvent } from "../../Main/TextField/TextField";
import { 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: KeyboardEvent) => {
const handleKeyDown = (e: TextFieldKeyboardEvent) => {
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(`${orderBy}`) ? dayjs(`${valueA}`).unix() : valueA;
const parsedValueB = dateColumns.includes(`${orderBy}`) ? dayjs(`${valueB}`).unix() : valueB;
const parsedValueA = dateColumns.includes(String(orderBy)) ? dayjs(`${valueA}`).unix() : valueA;
const parsedValueB = dateColumns.includes(String(orderBy)) ? dayjs(`${valueB}`).unix() : valueB;
if (parsedValueB < parsedValueA) {
return -1;
}

View File

@@ -0,0 +1,12 @@
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,6 +1,7 @@
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();
@@ -10,7 +11,7 @@ const useFetchFlags = () => {
useEffect(() => {
const fetchAppConfig = async () => {
if (process.env.REACT_APP_TYPE) return;
if (!APP_TYPE_VM) return;
setError("");
setIsLoading(true);

View File

@@ -5,6 +5,7 @@ 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"));
@@ -28,7 +29,7 @@ const useFetchDefaultTimezone = () => {
};
const fetchDefaultTimezone = async () => {
if (!serverUrl || process.env.REACT_APP_TYPE) return;
if (!serverUrl || !APP_TYPE_VM) return;
setError("");
setIsLoading(true);

View File

@@ -1,6 +1,7 @@
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();
@@ -11,7 +12,7 @@ const useFetchFlags = () => {
useEffect(() => {
const fetchFlags = async () => {
if (!serverUrl || process.env.REACT_APP_TYPE) return;
if (!serverUrl || !APP_TYPE_VM) 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,8 +49,6 @@ interface FetchDataParams {
hideQuery?: number[]
}
const isAnomalyUI = AppType.anomaly === process.env.REACT_APP_TYPE;
export const useFetchQuery = ({
predefinedQuery,
visible,
@@ -134,7 +132,7 @@ export const useFetchQuery = ({
}
const preventChangeType = !!getQueryStringValue("display_mode", null);
isHistogramResult = !isAnomalyUI && isDisplayChart && !preventChangeType && isHistogramData(resp.data.result);
isHistogramResult = !APP_TYPE_ANOMALY && 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

@@ -3,9 +3,23 @@ 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(<App />, root);
if (root) render(getAppComponent(), root);
// If you want to start measuring performance in your app, pass a function

View File

@@ -13,19 +13,16 @@ import HeaderControls, { ControlsProps } from "./HeaderControls/HeaderControls";
import useDeviceDetect from "../../hooks/useDeviceDetect";
import useWindowSize from "../../hooks/useWindowSize";
import { ComponentType } from "react";
import { AppType } from "../../types/appType";
import { APP_TYPE, AppType } from "../../constants/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 (REACT_APP_TYPE) {
case AppType.logs:
switch (APP_TYPE) {
case AppType.victorialogs:
return <LogoLogsIcon/>;
case AppType.anomaly:
case AppType.vmanomaly:
return <LogoAnomalyIcon/>;
default:
return <LogoIcon/>;
@@ -81,10 +78,7 @@ const Header: FC<HeaderProps> = ({ controlsComponent }) => {
<>
{!appModeEnable && (
<div
className={classNames({
"vm-header-logo": true,
"vm-header-logo_logs": isCustomApp
})}
className="vm-header-logo"
onClick={onClickLogo}
style={{ color }}
>
@@ -102,7 +96,6 @@ 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,16 +8,13 @@ import MenuBurger from "../../../components/Main/MenuBurger/MenuBurger";
import useDeviceDetect from "../../../hooks/useDeviceDetect";
import "./style.scss";
import useBoolean from "../../../hooks/useBoolean";
import { AppType } from "../../../types/appType";
import { APP_TYPE_LOGS } from "../../../constants/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,
@@ -64,7 +61,7 @@ const SidebarHeader: FC<SidebarHeaderProps> = ({
/>
</div>
<div className="vm-header-sidebar-menu-settings">
{!isMobile && !isLogsApp && <ShortcutKeys showTitle={true}/>}
{!isMobile && !APP_TYPE_LOGS && <ShortcutKeys showTitle={true}/>}
</div>
</div>
</div>;

View File

@@ -47,14 +47,14 @@
justify-content: flex-start;
cursor: pointer;
width: 100%;
max-width: 65px;
min-width: 65px;
max-width: 75px;
min-width: 75px;
margin-bottom: 2px;
overflow: hidden;
svg {
max-width: 65px;
min-width: 65px;
max-width: 75px;
min-width: 75px;
}
&_mobile {
@@ -62,10 +62,5 @@
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<unknown>,
event: MouseEvent<HTMLTableCellElement>,
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<unknown>) => {
const createSortHandler = (property: keyof Data) => (event: MouseEvent<HTMLTableCellElement>) => {
onRequestSort(event, property);
};

View File

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

View File

@@ -3,10 +3,11 @@ 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: JSX.Element
icon: ReactNode
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, MouseEvent>, index: number) => {
const handleToggleHideQuery = (e: ReactMouseEvent<HTMLButtonElement>, 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, MouseEvent>) => {
const createHandlerHideQuery = (i: number) => (e: ReactMouseEvent<HTMLButtonElement>) => {
handleToggleHideQuery(e, i);
};

View File

@@ -1,4 +1,5 @@
@use "src/styles/variables" as *;
@use 'sass:color';
$font-size-logs: var(--font-size-logs, $font-size-small);
@@ -79,8 +80,8 @@ $font-size-logs: var(--font-size-logs, $font-size-small);
&__pair {
order: 0;
padding: calc($padding-global / 2) $padding-global;
background-color: lighten($color-tropical-blue, 6%);
color: darken($color-dodger-blue, 20%);
background-color: color.scale($color-tropical-blue, $lightness: 60%);
color: color.scale($color-tropical-blue, $lightness: -60%);
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;
@@ -105,7 +106,7 @@ $font-size-logs: var(--font-size-logs, $font-size-small);
}
&_dark {
color: lighten($color-dodger-blue, 20%);
color: color.scale($color-dodger-blue, $lightness: 40%);
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, MouseEvent>, i: number) => {
const handleMouseDown = (e: ReactMouseEvent<HTMLButtonElement>, i: number) => {
setResize({
start: e.clientX,
target: i,

View File

@@ -3,6 +3,7 @@ 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}`);
@@ -34,7 +35,7 @@ export const useFetchDashboards = (): {
};
const fetchRemoteDashboards = async () => {
if (!serverUrl || process.env.REACT_APP_TYPE) return;
if (!serverUrl || !APP_TYPE_VM) return;
setError("");
setIsLoading(true);

View File

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

View File

@@ -1,4 +1,4 @@
import React, { FC, useEffect, useMemo, KeyboardEvent } from "react";
import React, { FC, useEffect, useMemo } 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 from "../../components/Main/TextField/TextField";
import TextField, { TextFieldKeyboardEvent } 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: KeyboardEvent) => {
const onKeyDown = (e: TextFieldKeyboardEvent) => {
if (e.key === "Enter") fetch();
};

View File

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

View File

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

View File

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

View File

@@ -2,11 +2,9 @@ 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";
const appType = process.env.REACT_APP_TYPE;
import { APP_TYPE, AppType } from "../constants/appType";
const useNavigationMenu = () => {
const appModeEnable = getAppModeEnable();
@@ -25,10 +23,10 @@ const useNavigationMenu = () => {
const menu = useMemo(() => {
switch (appType) {
case AppType.logs:
switch (APP_TYPE) {
case AppType.victorialogs:
return getLogsNavigation();
case AppType.anomaly:
case AppType.vmanomaly:
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;
state.autocompleteCache.delete(firstKey);
firstKey && state.autocompleteCache.delete(firstKey);
}
state.autocompleteCache.set(action.payload.key, action.payload.value);

View File

@@ -1,4 +0,0 @@
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 => `${key}: ${item[key] || "-"}`).join("|");
const compositeKey = keys.map(key => `${String(key)}: ${item[key] || "-"}`).join("|");
(result[compositeKey] = result[compositeKey] || []).push(item);

View File

@@ -1,12 +1,12 @@
import React, { ComponentProps, FC } from "react";
import React, { ComponentProps, FC, ReactNode } from "react";
type Props = { children: JSX.Element };
type Props = { children: ReactNode };
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>>): JSX.Element => (
return ({ children }: ComponentProps<FC<Props>>): ReactNode => (
<AccumulatedComponents>
<CurrentComponent>{children}</CurrentComponent>
</AccumulatedComponents>

View File

@@ -1,8 +1,7 @@
import { getAppModeParams } from "./app-mode";
import { replaceTenantId } from "./tenants";
import { AppType } from "../types/appType";
import { APP_TYPE, AppType } from "../constants/appType";
import { getFromStorage } from "./storage";
const { REACT_APP_TYPE } = process.env;
export const getDefaultServer = (tenantId?: string): string => {
const { serverURL } = getAppModeParams();
@@ -12,10 +11,10 @@ export const getDefaultServer = (tenantId?: string): string => {
const defaultURL = window.location.href.replace(/\/(?:prometheus\/)?(?:graph|vmui)\/.*/, "/prometheus");
const url = serverURL || storageURL || defaultURL;
switch (REACT_APP_TYPE) {
case AppType.logs:
switch (APP_TYPE) {
case AppType.victorialogs:
return logsURL;
case AppType.anomaly:
case AppType.vmanomaly:
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 { AppType } from "../types/appType";
import { APP_TYPE_LOGS } from "../constants/appType";
const MAX_ITEMS_PER_CHART = window.innerWidth / 4;
const MAX_ITEMS_PER_HISTOGRAM = window.innerWidth / 40;
@@ -160,11 +160,10 @@ 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: isLogsApp },
{ title: "Last 5 minutes", duration: "5m", isDefault: APP_TYPE_LOGS },
{ title: "Last 15 minutes", duration: "15m" },
{ title: "Last 30 minutes", duration: "30m", isDefault: !isLogsApp },
{ title: "Last 30 minutes", duration: "30m", isDefault: !APP_TYPE_LOGS },
{ title: "Last 1 hour", duration: "1h" },
{ title: "Last 3 hours", duration: "3h" },
{ title: "Last 6 hours", duration: "6h" },

View File

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

View File

@@ -1,6 +1,7 @@
{
"compilerOptions": {
"target": "es5",
"target": "ESNext",
"types": ["vite/client"],
"lib": [
"dom",
"dom.iterable",
@@ -19,7 +20,13 @@
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"downlevelIteration": true
"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/*"]
}
},
"include": [
"src"

View File

@@ -0,0 +1,39 @@
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";
}
}
}
}
},
};
});

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -6,7 +6,7 @@ ROOT_IMAGE ?= alpine:3.21.2
ROOT_IMAGE_SCRATCH ?= scratch
CERTS_IMAGE := alpine:3.21.2
GO_BUILDER_IMAGE := golang:1.23.5-alpine
GO_BUILDER_IMAGE := golang:1.23.6-alpine
BUILDER_IMAGE := local/builder:2.0.0-$(shell echo $(GO_BUILDER_IMAGE) | tr :/ __)-1
BASE_IMAGE := local/base:1.1.4-$(shell echo $(ROOT_IMAGE) | tr :/ __)-$(shell echo $(CERTS_IMAGE) | tr :/ __)
DOCKER ?= docker

View File

@@ -152,7 +152,7 @@ services:
# and distributes them according to --config.file.
alertmanager:
container_name: alertmanager
image: prom/alertmanager:v0.27.0
image: prom/alertmanager:v0.28.0
volumes:
- ./alertmanager.yml:/config/alertmanager.yml
command:

View File

@@ -44,7 +44,7 @@ services:
# storing logs and serving read queries.
victorialogs:
container_name: victorialogs
image: victoriametrics/victoria-logs:v1.8.0-victorialogs
image: victoriametrics/victoria-logs:v1.9.0-victorialogs
command:
- "--storageDataPath=/vlogs"
- "--httpListenAddr=:9428"
@@ -126,7 +126,7 @@ services:
# and distributes them according to --config.file.
alertmanager:
container_name: alertmanager
image: prom/alertmanager:v0.27.0
image: prom/alertmanager:v0.28.0
volumes:
- ./alertmanager.yml:/config/alertmanager.yml
command:

View File

@@ -93,7 +93,7 @@ services:
# and distributes them according to --config.file.
alertmanager:
container_name: alertmanager
image: prom/alertmanager:v0.27.0
image: prom/alertmanager:v0.28.0
volumes:
- ./alertmanager.yml:/config/alertmanager.yml
command:

View File

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

View File

@@ -89,7 +89,7 @@ services:
- "--licenseFile=/license"
alertmanager:
container_name: alertmanager
image: prom/alertmanager:v0.27.0
image: prom/alertmanager:v0.28.0
volumes:
- ./alertmanager.yml:/config/alertmanager.yml
command:

View File

@@ -18,7 +18,7 @@ services:
- vlogs
generator:
image: golang:1.23.5-alpine
image: golang:1.23.6-alpine
restart: always
working_dir: /go/src/app
volumes:

View File

@@ -2,7 +2,7 @@ version: '3'
services:
generator:
image: golang:1.23.5-alpine
image: golang:1.23.6-alpine
restart: always
working_dir: /go/src/app
volumes:

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.8.0-victorialogs
image: docker.io/victoriametrics/victoria-logs:v1.9.0-victorialogs
volumes:
- vlogs:/vlogs
ports:
@@ -58,7 +58,7 @@ services:
- ./vmsingle/promscrape.yml:/promscrape.yml
grafana:
image: grafana/grafana:9.2.7
image: grafana/grafana:11.5.0
depends_on: [vmsingle]
ports:
- 3000:3000

View File

@@ -16,11 +16,21 @@ 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.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
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
./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.8.0-victorialogs
docker.io/victoriametrics/victoria-logs:v1.9.0-victorialogs
```
See also:

View File

@@ -56,7 +56,8 @@ Otherwise the timestamp field must be in one of the following formats:
If timezone information is missing (for example, `2023-06-20 15:32:10`),
then the time is parsed in the local timezone of the host where VictoriaLogs runs.
- Unix timestamp in seconds or in milliseconds. For example, `1686026893` (seconds) or `1686026893735` (milliseconds).
- Unix timestamp in seconds, milliseconds, microseconds or nanoseconds. For example, `1686026893` (seconds), `1686026893735` (milliseconds),
`1686026893735321` (microseconds) or `1686026893735321098` (nanoseconds).
See [these docs](https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model) for details on fields,
which must be present in the ingested log messages.
@@ -111,7 +112,8 @@ Otherwise the timestamp field must be in one of the following formats:
If timezone information is missing (for example, `2023-06-20 15:32:10`),
then the time is parsed in the local timezone of the host where VictoriaLogs runs.
- Unix timestamp in seconds or in milliseconds. For example, `1686026893` (seconds) or `1686026893735` (milliseconds).
- Unix timestamp in seconds, milliseconds, microseconds or nanoseconds. For example, `1686026893` (seconds), `1686026893735` (milliseconds),
`1686026893735321` (microseconds) or `1686026893735321098` (nanoseconds).
See [these docs](https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model) for details on fields,
which must be present in the ingested log messages.

View File

@@ -146,7 +146,8 @@ The timestamp field must be in one of the following formats:
If timezone information is missing (for example, `2023-06-20 15:32:10`),
then the time is parsed in the local timezone of the host where VictoriaLogs runs.
- Unix timestamp in seconds or in milliseconds. For example, `1686026893` (seconds) or `1686026893735` (milliseconds).
- Unix timestamp in seconds, milliseconds, microseconds or nanoseconds. For example, `1686026893` (seconds), `1686026893735` (milliseconds),
`1686026893735321` (microseconds) or `1686026893735321098` (nanoseconds).
For example, the following [log entry](#data-model) contains valid timestamp with millisecond precision in the `_time` field:

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.8.0-victorialogs/vlogscli-linux-amd64-v1.8.0-victorialogs.tar.gz
tar xzf vlogscli-linux-amd64-v1.8.0-victorialogs.tar.gz
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
./vlogscli-prod
```

View File

@@ -18,6 +18,14 @@ See also [LTS releases](https://docs.victoriametrics.com/lts-releases/).
## tip
* 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).
## [v1.111.0](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.111.0)
Released at 2025-02-10
**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.**
* FEATURE: [Single-node VictoriaMetrics](https://docs.victoriametrics.com/) and [vmstorage](https://docs.victoriametrics.com/cluster-victoriametrics/): improve startup times when opening a storage with the [retention](https://docs.victoriametrics.com/#retention) exceeding a few months.
* FEATURE: [vmui](https://docs.victoriametrics.com/#vmui): add the ability to switch the heatmap to a line chart. Now, vmui would suggest to switch to line graph display if heatmap can't be properly rendered. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/8057).
* FEATURE: [vmauth](https://docs.victoriametrics.com/vmauth/): add `-httpInternalListenAddr` cmd-line flag to serve internal HTTP routes `/metrics`, `/flags`, etc. It allows properly route requests to backends with the same service routes as vmauth. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/6468) and [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7345) for details.
@@ -29,7 +37,33 @@ See also [LTS releases](https://docs.victoriametrics.com/lts-releases/).
* 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.
* 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.
* BUGFIX: [vmalert](https://docs.victoriametrics.com/vmalert/): do not unregister group metrics if the group is still in use. Previously, this could lead to group metrics being absent even though rules group is still running. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/8229) for details.
## [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)

View File

@@ -1,6 +1,25 @@
## 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.14-gray?logo=Helm&labelColor=gray&link=https%3A%2F%2Fdocs.victoriametrics.com%2Fhelm%2Fvictoria-logs-single%2Fchangelog%2F%230814)
![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)
![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">""
<code class="language-yaml">None
</code>
</pre>
</td>
@@ -1179,6 +1179,69 @@ 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,6 +1,14 @@
## Next release
- TODO
- 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.
## 0.15.6

Some files were not shown because too many files have changed in this diff Show More