mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2026-05-30 07:10:55 +03:00
Compare commits
2 Commits
v1.10.1-vi
...
test-tmp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7d7d17d192 | ||
|
|
0a8b4281e5 |
2
.github/workflows/main.yml
vendored
2
.github/workflows/main.yml
vendored
@@ -90,7 +90,7 @@ jobs:
|
||||
- name: Publish coverage
|
||||
uses: codecov/codecov-action@v5
|
||||
with:
|
||||
files: ./coverage.txt
|
||||
file: ./coverage.txt
|
||||
|
||||
integration-test:
|
||||
name: integration-test
|
||||
|
||||
2
Makefile
2
Makefile
@@ -567,7 +567,7 @@ golangci-lint: install-golangci-lint
|
||||
golangci-lint run
|
||||
|
||||
install-golangci-lint:
|
||||
which golangci-lint || curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(shell go env GOPATH)/bin v1.64.4
|
||||
which golangci-lint || curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(shell go env GOPATH)/bin v1.63.4
|
||||
|
||||
remove-golangci-lint:
|
||||
rm -rf `which golangci-lint`
|
||||
|
||||
@@ -129,11 +129,6 @@ func handleJournald(r *http.Request, w http.ResponseWriter) {
|
||||
return
|
||||
}
|
||||
|
||||
// systemd starting release v258 will support compression, which starts working after negotiation: it expects supported compression
|
||||
// algorithms list in Accept-Encoding response header in a format "<algorithm_1>[:<priority_1>][;<algorithm_2>:<priority_2>]"
|
||||
// See https://github.com/systemd/systemd/pull/34822
|
||||
w.Header().Set("Accept-Encoding", "zstd")
|
||||
|
||||
// update requestJournaldDuration only for successfully parsed requests
|
||||
// There is no need in updating requestJournaldDuration for request errors,
|
||||
// since their timings are usually much smaller than the timing for successful request parsing.
|
||||
|
||||
@@ -51,13 +51,20 @@ func RequestHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
lmp := cp.NewLogMessageProcessor("jsonline")
|
||||
streamName := fmt.Sprintf("remoteAddr=%s, requestURI=%q", httpserver.GetQuotedRemoteAddr(r), r.RequestURI)
|
||||
processStreamInternal(streamName, reader, cp.TimeField, cp.MsgFields, lmp)
|
||||
err = processStreamInternal(streamName, reader, cp.TimeField, cp.MsgFields, lmp)
|
||||
lmp.MustClose()
|
||||
|
||||
requestDuration.UpdateDuration(startTime)
|
||||
if err != nil {
|
||||
logger.Errorf("jsonline: %s", err)
|
||||
} else {
|
||||
// update requestDuration only for successfully parsed requests.
|
||||
// There is no need in updating requestDuration for request errors,
|
||||
// since their timings are usually much smaller than the timing for successful request parsing.
|
||||
requestDuration.UpdateDuration(startTime)
|
||||
}
|
||||
}
|
||||
|
||||
func processStreamInternal(streamName string, r io.Reader, timeField string, msgFields []string, lmp insertutils.LogMessageProcessor) {
|
||||
func processStreamInternal(streamName string, r io.Reader, timeField string, msgFields []string, lmp insertutils.LogMessageProcessor) error {
|
||||
wcr := writeconcurrencylimiter.GetReader(r)
|
||||
defer writeconcurrencylimiter.PutReader(wcr)
|
||||
|
||||
@@ -69,10 +76,10 @@ func processStreamInternal(streamName string, r io.Reader, timeField string, msg
|
||||
wcr.DecConcurrency()
|
||||
if err != nil {
|
||||
errorsTotal.Inc()
|
||||
logger.Warnf("jsonline: cannot read line #%d in /jsonline request: %s", n, err)
|
||||
return fmt.Errorf("cannot read line #%d in /jsonline request: %s", n, err)
|
||||
}
|
||||
if !ok {
|
||||
return
|
||||
return nil
|
||||
}
|
||||
n++
|
||||
}
|
||||
@@ -89,17 +96,16 @@ func readLine(lr *insertutils.LineReader, timeField string, msgFields []string,
|
||||
}
|
||||
|
||||
p := logstorage.GetJSONParser()
|
||||
defer logstorage.PutJSONParser(p)
|
||||
|
||||
if err := p.ParseLogMessage(line); err != nil {
|
||||
return true, fmt.Errorf("cannot parse json-encoded line: %w; line contents: %q", err, line)
|
||||
return false, fmt.Errorf("cannot parse json-encoded log entry: %w", err)
|
||||
}
|
||||
ts, err := insertutils.ExtractTimestampFromFields(timeField, p.Fields)
|
||||
if err != nil {
|
||||
return true, fmt.Errorf("cannot get timestamp from json-encoded line: %w; line contents: %q", err, line)
|
||||
return false, fmt.Errorf("cannot get timestamp: %w", err)
|
||||
}
|
||||
logstorage.RenameField(p.Fields, msgFields, "_msg")
|
||||
lmp.AddRow(ts, p.Fields, nil)
|
||||
logstorage.PutJSONParser(p)
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
@@ -7,14 +7,16 @@ import (
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/app/vlinsert/insertutils"
|
||||
)
|
||||
|
||||
func TestProcessStreamInternal(t *testing.T) {
|
||||
func TestProcessStreamInternal_Success(t *testing.T) {
|
||||
f := func(data, timeField, msgField string, timestampsExpected []int64, resultExpected string) {
|
||||
t.Helper()
|
||||
|
||||
msgFields := []string{msgField}
|
||||
tlp := &insertutils.TestLogMessageProcessor{}
|
||||
r := bytes.NewBufferString(data)
|
||||
processStreamInternal("test", r, timeField, msgFields, tlp)
|
||||
if err := processStreamInternal("test", r, timeField, msgFields, tlp); err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
|
||||
if err := tlp.Verify(timestampsExpected, resultExpected); err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -43,37 +45,22 @@ func TestProcessStreamInternal(t *testing.T) {
|
||||
resultExpected = `{"log.offset":"71770","log.file.path":"/var/log/auth.log","message":"foobar"}
|
||||
{"message":"baz"}`
|
||||
f(data, timeField, msgField, timestampsExpected, resultExpected)
|
||||
}
|
||||
|
||||
func TestProcessStreamInternal_Failure(t *testing.T) {
|
||||
f := func(data string) {
|
||||
t.Helper()
|
||||
|
||||
tlp := &insertutils.TestLogMessageProcessor{}
|
||||
r := bytes.NewBufferString(data)
|
||||
if err := processStreamInternal("test", r, "time", nil, tlp); err == nil {
|
||||
t.Fatalf("expecting non-nil error")
|
||||
}
|
||||
}
|
||||
|
||||
// invalid json
|
||||
data = "foobar"
|
||||
timeField = "@timestamp"
|
||||
msgField = "aaa"
|
||||
timestampsExpected = nil
|
||||
resultExpected = ``
|
||||
f(data, timeField, msgField, timestampsExpected, resultExpected)
|
||||
f("foobar")
|
||||
|
||||
// invalid timestamp field
|
||||
data = `{"time":"foobar"}`
|
||||
timeField = "time"
|
||||
msgField = "abc"
|
||||
timestampsExpected = nil
|
||||
resultExpected = ``
|
||||
f(data, timeField, msgField, timestampsExpected, resultExpected)
|
||||
|
||||
// invalid lines among valid lines
|
||||
data = `
|
||||
dsfodmasd
|
||||
|
||||
{"time":"2023-06-06T04:48:11.735Z","log":{"offset":71770,"file":{"path":"/var/log/auth.log"}},"message":"foobar"}
|
||||
invalid line
|
||||
{"time":"2023-06-06T04:48:12.735+01:00","message":"baz"}
|
||||
asbsdf
|
||||
|
||||
`
|
||||
timeField = "time"
|
||||
msgField = "message"
|
||||
timestampsExpected = []int64{1686026891735000000, 1686023292735000000}
|
||||
resultExpected = `{"log.offset":"71770","log.file.path":"/var/log/auth.log","_msg":"foobar"}
|
||||
{"_msg":"baz"}`
|
||||
f(data, timeField, msgField, timestampsExpected, resultExpected)
|
||||
f(`{"time":"foobar"}`)
|
||||
}
|
||||
|
||||
@@ -126,18 +126,6 @@ func pushFieldsFromScopeLogs(sc *pb.ScopeLogs, commonFields []logstorage.Field,
|
||||
Value: attr.Value.FormatString(),
|
||||
})
|
||||
}
|
||||
if len(lr.TraceID) > 0 {
|
||||
fields = append(fields, logstorage.Field{
|
||||
Name: "trace_id",
|
||||
Value: lr.TraceID,
|
||||
})
|
||||
}
|
||||
if len(lr.SpanID) > 0 {
|
||||
fields = append(fields, logstorage.Field{
|
||||
Name: "span_id",
|
||||
Value: lr.SpanID,
|
||||
})
|
||||
}
|
||||
fields = append(fields, logstorage.Field{
|
||||
Name: "severity",
|
||||
Value: lr.FormatSeverity(),
|
||||
|
||||
12
app/vlselect/vmui/asset-manifest.json
Normal file
12
app/vlselect/vmui/asset-manifest.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"files": {
|
||||
"main.css": "./static/css/main.02a1c6cb.css",
|
||||
"main.js": "./static/js/main.55c8060b.js",
|
||||
"static/js/685.f772060c.chunk.js": "./static/js/685.f772060c.chunk.js",
|
||||
"index.html": "./index.html"
|
||||
},
|
||||
"entrypoints": [
|
||||
"static/css/main.02a1c6cb.css",
|
||||
"static/js/main.55c8060b.js"
|
||||
]
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
||||
.uplot,.uplot *,.uplot *:before,.uplot *:after{box-sizing:border-box}.uplot{font-family:system-ui,-apple-system,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";line-height:1.5;width:min-content}.u-title{text-align:center;font-size:18px;font-weight:700}.u-wrap{position:relative;-webkit-user-select:none;user-select:none}.u-over,.u-under{position:absolute}.u-under{overflow:hidden}.uplot canvas{display:block;position:relative;width:100%;height:100%}.u-axis{position:absolute}.u-legend{font-size:14px;margin:auto;text-align:center}.u-inline{display:block}.u-inline *{display:inline-block}.u-inline tr{margin-right:16px}.u-legend th{font-weight:600}.u-legend th>*{vertical-align:middle;display:inline-block}.u-legend .u-marker{width:1em;height:1em;margin-right:4px;background-clip:padding-box!important}.u-inline.u-live th:after{content:":";vertical-align:middle}.u-inline:not(.u-live) .u-value{display:none}.u-series>*{padding:4px}.u-series th{cursor:pointer}.u-legend .u-off>*{opacity:.3}.u-select{background:#00000012;position:absolute;pointer-events:none}.u-cursor-x,.u-cursor-y{position:absolute;left:0;top:0;pointer-events:none;will-change:transform}.u-hz .u-cursor-x,.u-vt .u-cursor-y{height:100%;border-right:1px dashed #607D8B}.u-hz .u-cursor-y,.u-vt .u-cursor-x{width:100%;border-bottom:1px dashed #607D8B}.u-cursor-pt{position:absolute;top:0;left:0;border-radius:50%;border:0 solid;pointer-events:none;will-change:transform;background-clip:padding-box!important}.u-axis.u-off,.u-select.u-off,.u-cursor-x.u-off,.u-cursor-y.u-off,.u-cursor-pt.u-off{display:none}
|
||||
File diff suppressed because one or more lines are too long
@@ -1,57 +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 your log data with VictoriaLogs UI"/>
|
||||
<!--
|
||||
manifest.json provides metadata used when your web app is installed on a
|
||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
-->
|
||||
<link rel="manifest" href="./manifest.json"/>
|
||||
<!--
|
||||
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", "/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>UI for VictoriaLogs</title>
|
||||
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta name="twitter:title" content="UI for VictoriaLogs">
|
||||
<meta name="twitter:site" content="@https://victoriametrics.com/products/victorialogs/">
|
||||
<meta name="twitter:description" content="Explore your log data with VictoriaLogs UI">
|
||||
<meta name="twitter:image" content="./preview.jpg">
|
||||
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:title" content="UI for VictoriaLogs">
|
||||
<meta property="og:url" content="https://victoriametrics.com/products/victorialogs/">
|
||||
<meta property="og:description" content="Explore your log data with VictoriaLogs UI">
|
||||
<script type="module" crossorigin src="./assets/index-DokhsDQR.js"></script>
|
||||
<link rel="modulepreload" crossorigin href="./assets/vendor-DojlIpLz.js">
|
||||
<link rel="stylesheet" crossorigin href="./assets/vendor-D1GxaB_c.css">
|
||||
<link rel="stylesheet" crossorigin href="./assets/index-Do1w9kcl.css">
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm start` or `yarn start`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
-->
|
||||
</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 your log data with VictoriaLogs UI"/><link rel="manifest" href="./manifest.json"/><title>UI for VictoriaLogs</title><meta name="twitter:card" content="summary"><meta name="twitter:title" content="UI for VictoriaLogs"><meta name="twitter:site" content="@https://victoriametrics.com/products/victorialogs/"><meta name="twitter:description" content="Explore your log data with VictoriaLogs UI"><meta name="twitter:image" content="./preview.jpg"><meta property="og:type" content="website"><meta property="og:title" content="UI for VictoriaLogs"><meta property="og:url" content="https://victoriametrics.com/products/victorialogs/"><meta property="og:description" content="Explore your log data with VictoriaLogs UI"><script defer="defer" src="./static/js/main.55c8060b.js"></script><link href="./static/css/main.02a1c6cb.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
|
||||
1
app/vlselect/vmui/static/css/main.02a1c6cb.css
Normal file
1
app/vlselect/vmui/static/css/main.02a1c6cb.css
Normal file
File diff suppressed because one or more lines are too long
1
app/vlselect/vmui/static/js/685.f772060c.chunk.js
Normal file
1
app/vlselect/vmui/static/js/685.f772060c.chunk.js
Normal file
File diff suppressed because one or more lines are too long
2
app/vlselect/vmui/static/js/main.55c8060b.js
Normal file
2
app/vlselect/vmui/static/js/main.55c8060b.js
Normal file
File diff suppressed because one or more lines are too long
38
app/vlselect/vmui/static/js/main.55c8060b.js.LICENSE.txt
Normal file
38
app/vlselect/vmui/static/js/main.55c8060b.js.LICENSE.txt
Normal file
@@ -0,0 +1,38 @@
|
||||
/*!
|
||||
Copyright (c) 2018 Jed Watson.
|
||||
Licensed under the MIT License (MIT), see
|
||||
http://jedwatson.github.io/classnames
|
||||
*/
|
||||
|
||||
/**
|
||||
* @remix-run/router v1.19.2
|
||||
*
|
||||
* Copyright (c) Remix Software Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE.md file in the root directory of this source tree.
|
||||
*
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
/**
|
||||
* React Router DOM v6.26.2
|
||||
*
|
||||
* Copyright (c) Remix Software Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE.md file in the root directory of this source tree.
|
||||
*
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
/**
|
||||
* React Router v6.26.2
|
||||
*
|
||||
* Copyright (c) Remix Software Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE.md file in the root directory of this source tree.
|
||||
*
|
||||
* @license MIT
|
||||
*/
|
||||
@@ -70,7 +70,7 @@ func (am *AlertManager) Send(ctx context.Context, alerts []Alert, headers map[st
|
||||
|
||||
func (am *AlertManager) send(ctx context.Context, alerts []Alert, headers map[string]string) error {
|
||||
b := &bytes.Buffer{}
|
||||
alertsToSend := make([]Alert, 0, len(alerts))
|
||||
alertsToSend := alerts[:0]
|
||||
lblss := make([][]prompbmarshal.Label, 0, len(alerts))
|
||||
for _, a := range alerts {
|
||||
lbls := a.applyRelabelingIfNeeded(am.relabelConfigs)
|
||||
|
||||
@@ -162,7 +162,7 @@ func TestHandler(t *testing.T) {
|
||||
|
||||
gotRuleWithUpdates := apiRuleWithUpdates{}
|
||||
getResp(t, ts.URL+"/"+expRule.APILink(), &gotRuleWithUpdates, 200)
|
||||
if len(gotRuleWithUpdates.StateUpdates) < 1 {
|
||||
if gotRuleWithUpdates.StateUpdates == nil || len(gotRuleWithUpdates.StateUpdates) < 1 {
|
||||
t.Fatalf("expected %+v to have state updates field not empty", gotRuleWithUpdates.StateUpdates)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -98,7 +98,6 @@ func TestCreateTargetURLSuccess(t *testing.T) {
|
||||
up, hc := ui.getURLPrefixAndHeaders(u, u.Host, nil)
|
||||
if up == nil {
|
||||
t.Fatalf("cannot match available backend: %s", err)
|
||||
return
|
||||
}
|
||||
bu := up.getBackendURL()
|
||||
target := mergeURLs(bu.url, u, up.dropSrcPathPrefixParts)
|
||||
@@ -310,7 +309,6 @@ func TestUserInfoGetBackendURL_SRV(t *testing.T) {
|
||||
up, _ := ui.getURLPrefixAndHeaders(u, u.Host, nil)
|
||||
if up == nil {
|
||||
t.Fatalf("cannot match available backend: %s", err)
|
||||
return
|
||||
}
|
||||
bu := up.getBackendURL()
|
||||
target := mergeURLs(bu.url, u, up.dropSrcPathPrefixParts)
|
||||
|
||||
@@ -791,7 +791,7 @@ func LabelNames(qt *querytracer.Tracer, sq *storage.SearchQuery, maxLabelNames i
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
labels, err := vmstorage.SearchLabelNames(qt, tfss, tr, maxLabelNames, sq.MaxMetrics, deadline.Deadline())
|
||||
labels, err := vmstorage.SearchLabelNamesWithFiltersOnTimeRange(qt, tfss, tr, maxLabelNames, sq.MaxMetrics, deadline.Deadline())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error during labels search on time range: %w", err)
|
||||
}
|
||||
@@ -864,7 +864,7 @@ func LabelValues(qt *querytracer.Tracer, labelName string, sq *storage.SearchQue
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
labelValues, err := vmstorage.SearchLabelValues(qt, labelName, tfss, tr, maxLabelValues, sq.MaxMetrics, deadline.Deadline())
|
||||
labelValues, err := vmstorage.SearchLabelValuesWithFiltersOnTimeRange(qt, labelName, tfss, tr, maxLabelValues, sq.MaxMetrics, deadline.Deadline())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error during label values search on time range for labelName=%q: %w", labelName, err)
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ var (
|
||||
"It can be overridden on per-query basis via latency_offset arg. "+
|
||||
"Too small value can result in incomplete last points for query results")
|
||||
maxQueryLen = flagutil.NewBytes("search.maxQueryLen", 16*1024, "The maximum search query length in bytes")
|
||||
maxLookback = flag.Duration("search.maxLookback", 0, "Synonym to -query.lookback-delta from Prometheus. "+
|
||||
maxLookback = flag.Duration("search.maxLookback", 0, "Synonym to -search.lookback-delta from Prometheus. "+
|
||||
"The value is dynamically detected from interval between time series datapoints if not set. It can be overridden on per-query basis via max_lookback arg. "+
|
||||
"See also '-search.maxStalenessInterval' flag, which has the same meaning due to historical reasons")
|
||||
maxStalenessInterval = flag.Duration("search.maxStalenessInterval", 0, "The maximum interval for staleness calculations. "+
|
||||
|
||||
@@ -71,11 +71,6 @@ var (
|
||||
"See https://docs.victoriametrics.com/single-server-victoriametrics/#cache-tuning")
|
||||
cacheSizeIndexDBTagFilters = flagutil.NewBytes("storage.cacheSizeIndexDBTagFilters", 0, "Overrides max size for indexdb/tagFiltersToMetricIDs cache. "+
|
||||
"See https://docs.victoriametrics.com/single-server-victoriametrics/#cache-tuning")
|
||||
|
||||
disablePerDayIndex = flag.Bool("disablePerDayIndex", false, "Disable per-day index and use global index for all searches. "+
|
||||
"This may improve performance and decrease disk space usage for the use cases with fixed set of timeseries scattered across a "+
|
||||
"big time range (for example, when loading years of historical data). "+
|
||||
"See https://docs.victoriametrics.com/single-server-victoriametrics/#index-tuning")
|
||||
)
|
||||
|
||||
// CheckTimeRange returns true if the given tr is denied for querying.
|
||||
@@ -115,14 +110,7 @@ func Init(resetCacheIfNeeded func(mrs []storage.MetricRow)) {
|
||||
logger.Infof("opening storage at %q with -retentionPeriod=%s", *DataPath, retentionPeriod)
|
||||
startTime := time.Now()
|
||||
WG = syncwg.WaitGroup{}
|
||||
|
||||
opts := storage.OpenOptions{
|
||||
Retention: retentionPeriod.Duration(),
|
||||
MaxHourlySeries: *maxHourlySeries,
|
||||
MaxDailySeries: *maxDailySeries,
|
||||
DisablePerDayIndex: *disablePerDayIndex,
|
||||
}
|
||||
strg := storage.MustOpenStorage(*DataPath, opts)
|
||||
strg := storage.MustOpenStorage(*DataPath, retentionPeriod.Duration(), *maxHourlySeries, *maxDailySeries)
|
||||
Storage = strg
|
||||
initStaleSnapshotsRemover(strg)
|
||||
|
||||
@@ -201,19 +189,20 @@ func SearchMetricNames(qt *querytracer.Tracer, tfss []*storage.TagFilters, tr st
|
||||
return metricNames, err
|
||||
}
|
||||
|
||||
// SearchLabelNames searches for tag keys matching the given tfss on tr.
|
||||
func SearchLabelNames(qt *querytracer.Tracer, tfss []*storage.TagFilters, tr storage.TimeRange, maxTagKeys, maxMetrics int, deadline uint64) ([]string, error) {
|
||||
// SearchLabelNamesWithFiltersOnTimeRange searches for tag keys matching the given tfss on tr.
|
||||
func SearchLabelNamesWithFiltersOnTimeRange(qt *querytracer.Tracer, tfss []*storage.TagFilters, tr storage.TimeRange, maxTagKeys, maxMetrics int, deadline uint64) ([]string, error) {
|
||||
WG.Add(1)
|
||||
labelNames, err := Storage.SearchLabelNames(qt, tfss, tr, maxTagKeys, maxMetrics, deadline)
|
||||
labelNames, err := Storage.SearchLabelNamesWithFiltersOnTimeRange(qt, tfss, tr, maxTagKeys, maxMetrics, deadline)
|
||||
WG.Done()
|
||||
return labelNames, err
|
||||
}
|
||||
|
||||
// SearchLabelValues searches for label values for the given labelName, tfss and
|
||||
// tr.
|
||||
func SearchLabelValues(qt *querytracer.Tracer, labelName string, tfss []*storage.TagFilters, tr storage.TimeRange, maxLabelValues, maxMetrics int, deadline uint64) ([]string, error) {
|
||||
// SearchLabelValuesWithFiltersOnTimeRange searches for label values for the given labelName, tfss and tr.
|
||||
func SearchLabelValuesWithFiltersOnTimeRange(qt *querytracer.Tracer, labelName string, tfss []*storage.TagFilters,
|
||||
tr storage.TimeRange, maxLabelValues, maxMetrics int, deadline uint64,
|
||||
) ([]string, error) {
|
||||
WG.Add(1)
|
||||
labelValues, err := Storage.SearchLabelValues(qt, labelName, tfss, tr, maxLabelValues, maxMetrics, deadline)
|
||||
labelValues, err := Storage.SearchLabelValuesWithFiltersOnTimeRange(qt, labelName, tfss, tr, maxLabelValues, maxMetrics, deadline)
|
||||
WG.Done()
|
||||
return labelValues, err
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
VITE_APP_TYPE=victoriametrics
|
||||
FAST_REFRESH=false
|
||||
@@ -1 +0,0 @@
|
||||
VITE_APP_TYPE=victorialogs
|
||||
@@ -1 +0,0 @@
|
||||
VITE_APP_TYPE=vmanomaly
|
||||
48
app/vmui/packages/vmui/.eslintrc.js
Normal file
48
app/vmui/packages/vmui/.eslintrc.js
Normal file
@@ -0,0 +1,48 @@
|
||||
// eslint-disable-next-line no-undef
|
||||
module.exports = {
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es2021": true
|
||||
},
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:react/recommended",
|
||||
"plugin:@typescript-eslint/recommended"
|
||||
],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"ecmaFeatures": { "jsx": true },
|
||||
"ecmaVersion": 12,
|
||||
"sourceType": "module"
|
||||
},
|
||||
"plugins": [
|
||||
"react",
|
||||
"@typescript-eslint"
|
||||
],
|
||||
"rules": {
|
||||
"@typescript-eslint/no-unused-vars": ["warn", { "varsIgnorePattern": "^_" }],
|
||||
"react/jsx-closing-bracket-location": [1, "line-aligned"],
|
||||
"react/jsx-max-props-per-line":[1, { "maximum": 1 }],
|
||||
"react/jsx-first-prop-new-line": [1, "multiline"],
|
||||
"object-curly-spacing": [2, "always"],
|
||||
"indent": ["error", 2, { "SwitchCase": 1 }],
|
||||
"linebreak-style": ["error", "unix"],
|
||||
"quotes": ["error", "double"],
|
||||
"semi": ["error", "always"],
|
||||
"react/prop-types": 0
|
||||
},
|
||||
"settings": {
|
||||
"react": {
|
||||
"pragma": "React", // Pragma to use, default to "React"
|
||||
"version": "detect"
|
||||
},
|
||||
"linkComponents": [
|
||||
// Components used as alternatives to <a> for linking, eg. <Link to={ url } />
|
||||
"Hyperlink",
|
||||
{
|
||||
"name": "Link", "linkAttribute": "to"
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
42
app/vmui/packages/vmui/config-overrides.js
Normal file
42
app/vmui/packages/vmui/config-overrides.js
Normal file
@@ -0,0 +1,42 @@
|
||||
/* eslint-disable */
|
||||
const { override, addExternalBabelPlugin, addWebpackAlias, addWebpackPlugin } = require("customize-cra");
|
||||
const webpack = require("webpack");
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
// This will replace the default check
|
||||
const pathIndexHTML = (() => {
|
||||
switch (process.env.REACT_APP_TYPE) {
|
||||
case 'logs':
|
||||
return 'src/html/victorialogs.html';
|
||||
case 'anomaly':
|
||||
return 'src/html/vmanomaly.html';
|
||||
default:
|
||||
return 'src/html/victoriametrics.html';
|
||||
}
|
||||
})();
|
||||
const fileContent = fs.readFileSync(path.resolve(__dirname, pathIndexHTML), 'utf8');
|
||||
fs.writeFileSync(path.resolve(__dirname, 'public/index.html'), fileContent);
|
||||
|
||||
module.exports = override(
|
||||
addExternalBabelPlugin("@babel/plugin-proposal-nullish-coalescing-operator"),
|
||||
addWebpackAlias({
|
||||
"react": "preact/compat",
|
||||
"react-dom/test-utils": "preact/test-utils",
|
||||
"react-dom": "preact/compat", // Must be below test-utils
|
||||
"react/jsx-runtime": "preact/jsx-runtime"
|
||||
}),
|
||||
addWebpackPlugin(
|
||||
new webpack.NormalModuleReplacementPlugin(
|
||||
/\.\/App/,
|
||||
function (resource) {
|
||||
if (process.env.REACT_APP_TYPE === "logs") {
|
||||
resource.request = "./AppLogs";
|
||||
}
|
||||
if (process.env.REACT_APP_TYPE === "anomaly") {
|
||||
resource.request = "./AppAnomaly";
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
);
|
||||
@@ -1,23 +0,0 @@
|
||||
import { readFile } from "fs/promises";
|
||||
import { IndexHtmlTransform } from "vite";
|
||||
|
||||
/**
|
||||
* Vite plugin to dynamically load index.html based on the current mode.
|
||||
* If a specific mode-based index file (e.g., index.victorialogs.html) exists, it is used.
|
||||
* Otherwise, the default index.html is loaded.
|
||||
*/
|
||||
export default function dynamicIndexHtmlPlugin({ mode }) {
|
||||
return {
|
||||
name: "vm-dynamic-index-html",
|
||||
transformIndexHtml: {
|
||||
order: "pre",
|
||||
handler: async () => {
|
||||
try {
|
||||
return await readFile(`./index.${mode}.html`, "utf8");
|
||||
} catch (error) {
|
||||
return await readFile("./index.html", "utf8");
|
||||
}
|
||||
}
|
||||
} as IndexHtmlTransform
|
||||
};
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
import react from "eslint-plugin-react";
|
||||
import typescriptEslint from "@typescript-eslint/eslint-plugin";
|
||||
import globals from "globals";
|
||||
import tsParser from "@typescript-eslint/parser";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import js from "@eslint/js";
|
||||
import { FlatCompat } from "@eslint/eslintrc";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
const compat = new FlatCompat({
|
||||
baseDirectory: __dirname,
|
||||
recommendedConfig: js.configs.recommended,
|
||||
allConfig: js.configs.all
|
||||
});
|
||||
|
||||
export default [...compat.extends(
|
||||
"eslint:recommended",
|
||||
"plugin:react/recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
), {
|
||||
plugins: {
|
||||
react,
|
||||
"@typescript-eslint": typescriptEslint,
|
||||
},
|
||||
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...globals.browser,
|
||||
},
|
||||
|
||||
parser: tsParser,
|
||||
ecmaVersion: 12,
|
||||
sourceType: "module",
|
||||
|
||||
parserOptions: {
|
||||
ecmaFeatures: {
|
||||
jsx: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
settings: {
|
||||
react: {
|
||||
pragma: "React",
|
||||
version: "detect",
|
||||
},
|
||||
|
||||
linkComponents: ["Hyperlink", {
|
||||
name: "Link",
|
||||
linkAttribute: "to",
|
||||
}],
|
||||
},
|
||||
|
||||
rules: {
|
||||
"@typescript-eslint/no-unused-expressions": ["error", {
|
||||
allowShortCircuit: true,
|
||||
allowTernary: true
|
||||
}],
|
||||
|
||||
"@typescript-eslint/no-unused-vars": ["warn", {
|
||||
"argsIgnorePattern": "^_",
|
||||
"caughtErrors": "none",
|
||||
"caughtErrorsIgnorePattern": "^_",
|
||||
"destructuredArrayIgnorePattern": "^_",
|
||||
"varsIgnorePattern": "^_",
|
||||
"ignoreRestSiblings": true
|
||||
}],
|
||||
|
||||
"react/jsx-closing-bracket-location": [1, "line-aligned"],
|
||||
|
||||
"react/jsx-max-props-per-line": [1, {
|
||||
maximum: 1,
|
||||
}],
|
||||
|
||||
"react/jsx-first-prop-new-line": [1, "multiline"],
|
||||
"object-curly-spacing": [2, "always"],
|
||||
|
||||
indent: ["error", 2, {
|
||||
SwitchCase: 1,
|
||||
}],
|
||||
|
||||
"linebreak-style": ["error", "unix"],
|
||||
quotes: ["error", "double"],
|
||||
semi: ["error", "always"],
|
||||
"react/prop-types": 0,
|
||||
|
||||
},
|
||||
}];
|
||||
20119
app/vmui/packages/vmui/package-lock.json
generated
20119
app/vmui/packages/vmui/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -3,40 +3,50 @@
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"homepage": "./",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@types/lodash.debounce": "^4.0.9",
|
||||
"@types/lodash.get": "^4.4.9",
|
||||
"@types/qs": "^6.9.18",
|
||||
"@types/react": "^19.0.8",
|
||||
"@types/react-input-mask": "^3.0.6",
|
||||
"@types/lodash.throttle": "^4.1.9",
|
||||
"@types/node": "^22.5.4",
|
||||
"@types/qs": "^6.9.15",
|
||||
"@types/react-input-mask": "^3.0.5",
|
||||
"@types/react-router-dom": "^5.3.3",
|
||||
"@types/webpack-env": "^1.18.5",
|
||||
"classnames": "^2.5.1",
|
||||
"dayjs": "^1.11.13",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"lodash.get": "^4.4.2",
|
||||
"marked": "^15.0.6",
|
||||
"marked-emoji": "^1.4.3",
|
||||
"preact": "^10.25.4",
|
||||
"qs": "^6.14.0",
|
||||
"lodash.throttle": "^4.1.1",
|
||||
"marked": "^14.1.2",
|
||||
"marked-emoji": "^1.4.2",
|
||||
"preact": "^10.23.2",
|
||||
"qs": "^6.13.0",
|
||||
"react-input-mask": "^2.0.4",
|
||||
"react-router-dom": "^7.1.5",
|
||||
"uplot": "^1.6.31",
|
||||
"vite": "^6.0.11",
|
||||
"web-vitals": "^4.2.4"
|
||||
"react-router-dom": "^6.26.2",
|
||||
"sass": "^1.78.0",
|
||||
"source-map-explorer": "^2.5.3",
|
||||
"typescript": "~4.6.2",
|
||||
"uplot": "^1.6.30",
|
||||
"web-vitals": "^4.2.3"
|
||||
},
|
||||
"scripts": {
|
||||
"prestart": "npm run copy-metricsql-docs",
|
||||
"start": "vite",
|
||||
"start:logs": "vite --mode victorialogs",
|
||||
"start:anomaly": "vite --mode vmanomaly",
|
||||
"build": "vite build",
|
||||
"build:logs": "vite build --mode victorialogs",
|
||||
"build:anomaly": "vite build --mode vmanomaly",
|
||||
"lint": "eslint 'src/**/*.{ts,tsx}'",
|
||||
"lint:fix": "eslint 'src/**/*.{ts,tsx}' --fix",
|
||||
"copy-metricsql-docs": "cp ../../../../docs/MetricsQL.md src/assets/MetricsQL.md || true",
|
||||
"preview": "vite preview"
|
||||
"start": "react-app-rewired start",
|
||||
"start:logs": "cross-env REACT_APP_TYPE=logs npm run start",
|
||||
"start:anomaly": "cross-env REACT_APP_TYPE=anomaly npm run start",
|
||||
"build": "GENERATE_SOURCEMAP=false react-app-rewired build",
|
||||
"build:logs": "cross-env REACT_APP_TYPE=logs npm run build",
|
||||
"build:anomaly": "cross-env REACT_APP_TYPE=anomaly npm run build",
|
||||
"lint": "eslint src --ext tsx,ts",
|
||||
"lint:fix": "eslint src --ext tsx,ts --fix",
|
||||
"analyze": "source-map-explorer 'build/static/js/*.js'",
|
||||
"copy-metricsql-docs": "cp ../../../../docs/MetricsQL.md src/assets/MetricsQL.md || true"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"react-app",
|
||||
"react-app/jest"
|
||||
]
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
@@ -51,24 +61,26 @@
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6",
|
||||
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.16.7",
|
||||
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
|
||||
"@eslint/eslintrc": "^3.2.0",
|
||||
"@eslint/js": "^9.19.0",
|
||||
"@preact/preset-vite": "^2.10.1",
|
||||
"@types/node": "^22.13.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.22.0",
|
||||
"@typescript-eslint/parser": "^8.22.0",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.15.0",
|
||||
"@typescript-eslint/parser": "^5.15.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"eslint": "^9.19.0",
|
||||
"eslint-plugin-react": "^7.37.4",
|
||||
"globals": "^15.14.0",
|
||||
"http-proxy-middleware": "^3.0.3",
|
||||
"postcss": "^8.5.1",
|
||||
"rollup-plugin-visualizer": "^5.14.0",
|
||||
"sass": "^1.83.4",
|
||||
"sass-embedded": "^1.83.4",
|
||||
"typescript": "^5.7.3",
|
||||
"webpack": "^5.97.1"
|
||||
"customize-cra": "^1.0.0",
|
||||
"eslint": "^8.44.0",
|
||||
"eslint-config-react-app": "^7.0.1",
|
||||
"eslint-plugin-react": "^7.36.1",
|
||||
"http-proxy-middleware": "^3.0.2",
|
||||
"react-app-rewired": "^2.2.1",
|
||||
"webpack": "^5.94.0"
|
||||
},
|
||||
"overrides": {
|
||||
"react-app-rewired": {
|
||||
"nth-check": "^2.0.1"
|
||||
},
|
||||
"css-select": {
|
||||
"nth-check": "^2.0.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,4 +38,4 @@ const AppAnomaly: FC = () => {
|
||||
</>;
|
||||
};
|
||||
|
||||
export default AppAnomaly;
|
||||
export default AppAnomaly;
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { FC, useCallback, useEffect, useRef, useState, createPortal } from "preact/compat";
|
||||
import React, { FC, useCallback, useEffect, useRef, useState } from "preact/compat";
|
||||
import { MouseEvent as ReactMouseEvent } from "react";
|
||||
import useEventListener from "../../../hooks/useEventListener";
|
||||
import ReactDOM from "react-dom";
|
||||
import classNames from "classnames";
|
||||
import uPlot from "uplot";
|
||||
import Button from "../../Main/Button/Button";
|
||||
@@ -48,7 +49,7 @@ const ChartTooltip: FC<ChartTooltipProps> = ({
|
||||
onClose && onClose(id);
|
||||
};
|
||||
|
||||
const handleMouseDown = (e: ReactMouseEvent<HTMLButtonElement>) => {
|
||||
const handleMouseDown = (e: ReactMouseEvent) => {
|
||||
setMoved(true);
|
||||
setMoving(true);
|
||||
const { clientX, clientY } = e;
|
||||
@@ -106,7 +107,7 @@ const ChartTooltip: FC<ChartTooltipProps> = ({
|
||||
|
||||
if (!u) return null;
|
||||
|
||||
return createPortal((
|
||||
return ReactDOM.createPortal((
|
||||
<div
|
||||
className={classNames({
|
||||
"vm-chart-tooltip": true,
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
@use "src/styles/variables" as *;
|
||||
@use 'sass:color';
|
||||
|
||||
$color-bar: #33BB55;
|
||||
$color-bar-highest: #F79420;
|
||||
@@ -8,7 +7,7 @@ $color-bar-highest: #F79420;
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
height: 100%;
|
||||
padding-bottom: calc($font-size-small / 2);
|
||||
padding-bottom: calc($font-size-small/2);
|
||||
overflow: hidden;
|
||||
|
||||
&-y-axis {
|
||||
@@ -55,19 +54,19 @@ $color-bar-highest: #F79420;
|
||||
flex-grow: 1;
|
||||
width: 100%;
|
||||
min-width: 1px;
|
||||
height: calc(100% - ($font-size-small * 4));
|
||||
height: calc(100% - ($font-size-small*4));
|
||||
background-color: $color-bar;
|
||||
transition: background-color 200ms ease-in;
|
||||
|
||||
&:hover {
|
||||
background-color: color.scale($color-bar, $lightness: 40%);
|
||||
background-color: lighten($color-bar, 10%);
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
background-color: $color-bar-highest;
|
||||
|
||||
&:hover {
|
||||
background-color: color.scale($color-bar-highest, $lightness: 40%);
|
||||
background-color: lighten($color-bar-highest, 10%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,11 +12,14 @@ import Timezones from "./Timezones/Timezones";
|
||||
import ThemeControl from "../ThemeControl/ThemeControl";
|
||||
import useDeviceDetect from "../../../hooks/useDeviceDetect";
|
||||
import useBoolean from "../../../hooks/useBoolean";
|
||||
import { AppType } from "../../../types/appType";
|
||||
import SwitchMarkdownParsing from "../LogsSettings/MarkdownParsing/SwitchMarkdownParsing";
|
||||
import { APP_TYPE_LOGS } from "../../../constants/appType";
|
||||
|
||||
const title = "Settings";
|
||||
|
||||
const { REACT_APP_TYPE } = process.env;
|
||||
const isLogsApp = REACT_APP_TYPE === AppType.logs;
|
||||
|
||||
export interface ChildComponentHandle {
|
||||
handleApply: () => void;
|
||||
}
|
||||
@@ -45,21 +48,21 @@ const GlobalSettings: FC = () => {
|
||||
|
||||
const controls = [
|
||||
{
|
||||
show: !appModeEnable && !APP_TYPE_LOGS,
|
||||
show: !appModeEnable && !isLogsApp,
|
||||
component: <ServerConfigurator
|
||||
ref={serverSettingRef}
|
||||
onClose={handleClose}
|
||||
/>
|
||||
},
|
||||
{
|
||||
show: !APP_TYPE_LOGS,
|
||||
show: !isLogsApp,
|
||||
component: <LimitsConfigurator
|
||||
ref={limitsSettingRef}
|
||||
onClose={handleClose}
|
||||
/>
|
||||
},
|
||||
{
|
||||
show: APP_TYPE_LOGS,
|
||||
show: isLogsApp,
|
||||
component: <SwitchMarkdownParsing/>
|
||||
},
|
||||
{
|
||||
|
||||
@@ -9,7 +9,6 @@ import { useCallback } from "react";
|
||||
import { AUTOCOMPLETE_LIMITS } from "../../../../constants/queryAutocomplete";
|
||||
import { LogsFiledValues } from "../../../../api/types";
|
||||
import { useLogsDispatch, useLogsState } from "../../../../state/logsPanel/LogsStateContext";
|
||||
import { useSearchParams } from "react-router-dom";
|
||||
|
||||
type FetchDataArgs = {
|
||||
urlSuffix: string;
|
||||
@@ -28,8 +27,6 @@ const icons = {
|
||||
};
|
||||
|
||||
export const useFetchLogsQLOptions = (contextData?: ContextData) => {
|
||||
const [searchParams] = useSearchParams();
|
||||
|
||||
const { serverUrl } = useAppState();
|
||||
const { period: { start, end } } = useTimeState();
|
||||
const { autocompleteCache } = useLogsState();
|
||||
@@ -63,18 +60,11 @@ export const useFetchLogsQLOptions = (contextData?: ContextData) => {
|
||||
};
|
||||
|
||||
const fetchData = async ({ urlSuffix, setter, type, params }: FetchDataArgs) => {
|
||||
// if (!value && type === TypeData.metric) return;
|
||||
abortControllerRef.current.abort();
|
||||
abortControllerRef.current = new AbortController();
|
||||
const { signal } = abortControllerRef.current;
|
||||
|
||||
const tenant = {
|
||||
AccountID: searchParams.get("accountID") || "0",
|
||||
ProjectID: searchParams.get("projectID") || "0"
|
||||
};
|
||||
const tenantString = new URLSearchParams(tenant).toString();
|
||||
|
||||
const key = `${urlSuffix}?${params?.toString()}&${tenantString}`;
|
||||
|
||||
const key = `${urlSuffix}?${params?.toString()}`;
|
||||
setLoading(true);
|
||||
try {
|
||||
const cachedData = autocompleteCache.get(key);
|
||||
@@ -83,12 +73,7 @@ export const useFetchLogsQLOptions = (contextData?: ContextData) => {
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const response = await fetch(`${serverUrl}/select/logsql/${urlSuffix}?${params}`, {
|
||||
signal,
|
||||
headers: { ...tenant }
|
||||
});
|
||||
|
||||
const response = await fetch(`${serverUrl}/select/logsql/${urlSuffix}?${params}`, { signal });
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
const value = (data?.values || []) as LogsFiledValues[];
|
||||
|
||||
@@ -36,7 +36,7 @@ export class QueryAutocompleteCache {
|
||||
put(key: QueryAutocompleteCacheItem, value: string[]) {
|
||||
if (this.map.size >= this.maxSize) {
|
||||
const firstKey = this.map.keys().next().value;
|
||||
firstKey && this.map.delete(firstKey);
|
||||
this.map.delete(firstKey);
|
||||
}
|
||||
this.map.set(JSON.stringify(key), value);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { FC, useEffect, useRef, useState } from "preact/compat";
|
||||
import { KeyboardEvent } from "react";
|
||||
import { ErrorTypes } from "../../../types";
|
||||
import TextField, { TextFieldKeyboardEvent } from "../../Main/TextField/TextField";
|
||||
import TextField from "../../Main/TextField/TextField";
|
||||
import "./style.scss";
|
||||
import { QueryStats } from "../../../api/types";
|
||||
import { partialWarning, seriesFetchedWarning } from "./warningText";
|
||||
@@ -80,7 +81,7 @@ const QueryEditor: FC<QueryEditorProps> = ({
|
||||
setCaretPositionInput([caretPosition, caretPosition]);
|
||||
};
|
||||
|
||||
const handleKeyDown = (e: TextFieldKeyboardEvent) => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
const { key, ctrlKey, metaKey, shiftKey } = e;
|
||||
|
||||
const value = (e.target as HTMLTextAreaElement).value || "";
|
||||
|
||||
@@ -14,8 +14,6 @@ import Popper from "../../Main/Popper/Popper";
|
||||
import useDeviceDetect from "../../../hooks/useDeviceDetect";
|
||||
import classNames from "classnames";
|
||||
import useBoolean from "../../../hooks/useBoolean";
|
||||
import { useCustomPanelState } from "../../../state/customPanel/CustomPanelStateContext";
|
||||
import Hyperlink from "../../Main/Hyperlink/Hyperlink";
|
||||
|
||||
const StepConfigurator: FC = () => {
|
||||
const appModeEnable = getAppModeEnable();
|
||||
@@ -24,20 +22,16 @@ const StepConfigurator: FC = () => {
|
||||
const { customStep: value, isHistogram } = useGraphState();
|
||||
const { period: { step, end, start } } = useTimeState();
|
||||
const graphDispatch = useGraphDispatch();
|
||||
const { displayType } = useCustomPanelState();
|
||||
|
||||
const prevDuration = usePrevious(end - start);
|
||||
|
||||
const defaultStep = useMemo(() => {
|
||||
return getStepFromDuration(end - start, isHistogram, displayType);
|
||||
}, [end, start, isHistogram, displayType]);
|
||||
const prevDefaultStep = usePrevious(defaultStep);
|
||||
return getStepFromDuration(end - start, isHistogram);
|
||||
}, [step, isHistogram]);
|
||||
|
||||
const [customStep, setCustomStep] = useState(value || defaultStep);
|
||||
const [error, setError] = useState("");
|
||||
|
||||
const isAutoStep = value === defaultStep;
|
||||
|
||||
const {
|
||||
value: openOptions,
|
||||
toggle: toggleOpenOptions,
|
||||
@@ -107,15 +101,15 @@ const StepConfigurator: FC = () => {
|
||||
|
||||
useEffect(() => {
|
||||
const dur = end - start;
|
||||
if (dur === prevDuration || !prevDuration || value !== prevDefaultStep) return;
|
||||
if (dur === prevDuration || !prevDuration) return;
|
||||
if (defaultStep) {
|
||||
handleApply(defaultStep);
|
||||
}
|
||||
}, [prevDuration, defaultStep]);
|
||||
}, [end, start, prevDuration, defaultStep]);
|
||||
|
||||
useEffect(() => {
|
||||
if (step === value || step === defaultStep) handleApply(defaultStep);
|
||||
}, [isHistogram, displayType]);
|
||||
}, [isHistogram]);
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -135,15 +129,22 @@ const StepConfigurator: FC = () => {
|
||||
<span className="vm-mobile-option__arrow"><ArrowDownIcon/></span>
|
||||
</div>
|
||||
) : (
|
||||
<Button
|
||||
className={appModeEnable ? "" : "vm-header-button"}
|
||||
variant="contained"
|
||||
color="primary"
|
||||
startIcon={<TimelineIcon/>}
|
||||
onClick={toggleOpenOptions}
|
||||
>
|
||||
Step: {isAutoStep ? `auto (${customStep})` : customStep}
|
||||
</Button>
|
||||
<Tooltip title="Query resolution step width">
|
||||
<Button
|
||||
className={appModeEnable ? "" : "vm-header-button"}
|
||||
variant="contained"
|
||||
color="primary"
|
||||
startIcon={<TimelineIcon/>}
|
||||
onClick={toggleOpenOptions}
|
||||
>
|
||||
<p>
|
||||
STEP
|
||||
<p className="vm-step-control__value">
|
||||
{customStep}
|
||||
</p>
|
||||
</p>
|
||||
</Button>
|
||||
</Tooltip>
|
||||
)}
|
||||
<Popper
|
||||
open={openOptions}
|
||||
@@ -168,7 +169,7 @@ const StepConfigurator: FC = () => {
|
||||
onFocus={handleFocus}
|
||||
onBlur={handleApply}
|
||||
endIcon={(
|
||||
<Tooltip title={`Reset to auto step (${defaultStep})`}>
|
||||
<Tooltip title={`Set default step value: ${defaultStep}`}>
|
||||
<Button
|
||||
size="small"
|
||||
variant="text"
|
||||
@@ -181,23 +182,25 @@ const StepConfigurator: FC = () => {
|
||||
)}
|
||||
/>
|
||||
<div className="vm-step-control-popper-info">
|
||||
<p>
|
||||
<code>step</code> - the <Hyperlink
|
||||
href="https://prometheus.io/docs/prometheus/latest/querying/basics/#time-durations"
|
||||
text="interval"
|
||||
/> between datapoints, which must be returned from the range query.
|
||||
The <code>query</code> is executed
|
||||
at <code>start</code>, <code>start+step</code>, <code>start+2*step</code>, …, <code>end</code> timestamps.
|
||||
</p>
|
||||
<p>
|
||||
Read more about <Hyperlink
|
||||
href="https://docs.victoriametrics.com/keyConcepts.html#range-query"
|
||||
text="Range"
|
||||
/> and <Hyperlink
|
||||
href="https://docs.victoriametrics.com/keyconcepts/#instant-query"
|
||||
text="Instant"
|
||||
/> queries.
|
||||
</p>
|
||||
<code>step</code> - the <a
|
||||
className="vm-link vm-link_colored"
|
||||
href="https://prometheus.io/docs/prometheus/latest/querying/basics/#time-durations"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
interval
|
||||
</a>
|
||||
between datapoints, which must be returned from the range query.
|
||||
The <code>query</code> is executed at
|
||||
<code>start</code>, <code>start+step</code>, <code>start+2*step</code>, …, <code>end</code> timestamps.
|
||||
<a
|
||||
className="vm-link vm-link_colored"
|
||||
href="https://docs.victoriametrics.com/keyConcepts.html#range-query"
|
||||
target="_blank"
|
||||
rel="help noreferrer"
|
||||
>
|
||||
Read more about Range query
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</Popper>
|
||||
|
||||
@@ -8,10 +8,16 @@
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
&__value {
|
||||
display: inline;
|
||||
margin-left: 3px;
|
||||
}
|
||||
|
||||
&-popper {
|
||||
display: grid;
|
||||
gap: $padding-small;
|
||||
max-width: 300px;
|
||||
max-height: 208px;
|
||||
overflow: auto;
|
||||
padding: $padding-global;
|
||||
font-size: $font-size;
|
||||
@@ -30,11 +36,13 @@
|
||||
font-size: $font-size-small;
|
||||
line-height: 1.8;
|
||||
|
||||
a {
|
||||
margin: 0 0.4em;
|
||||
}
|
||||
|
||||
code {
|
||||
padding: .2em .4em;
|
||||
margin: 0;
|
||||
font-size: 85%;
|
||||
white-space: break-spaces;
|
||||
padding: 0.2em 0.4em;
|
||||
margin: 0 0.2em;
|
||||
background-color: $color-hover-black;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
@@ -14,8 +14,8 @@ interface ButtonProps {
|
||||
disabled?: boolean
|
||||
children?: ReactNode
|
||||
className?: string
|
||||
onClick?: (e: ReactMouseEvent<HTMLButtonElement>) => void
|
||||
onMouseDown?: (e: ReactMouseEvent<HTMLButtonElement>) => void
|
||||
onClick?: (e: ReactMouseEvent<HTMLButtonElement, MouseEvent>) => void
|
||||
onMouseDown?: (e: ReactMouseEvent<HTMLButtonElement, MouseEvent>) => void
|
||||
}
|
||||
|
||||
const Button: FC<ButtonProps> = ({
|
||||
|
||||
@@ -46,7 +46,7 @@ const DateTimeInput: FC<DateTimeInputProps> = ({
|
||||
onChange(maskedValue);
|
||||
};
|
||||
|
||||
const handleKeyUp = (e: KeyboardEvent<HTMLInputElement>) => {
|
||||
const handleKeyUp = (e: KeyboardEvent) => {
|
||||
if (e.key === "Enter") {
|
||||
onChange(maskedValue);
|
||||
setAwaitChangeForEnter(true);
|
||||
|
||||
@@ -89,8 +89,7 @@ const TimePicker: FC<CalendarTimepickerProps>= ({ selectDate, onChangeTime, onCl
|
||||
};
|
||||
|
||||
const handleFocusInput = (unit: TimeUnits, e: FocusEvent<HTMLInputElement>) => {
|
||||
const target = e.target as HTMLInputElement;
|
||||
target && target.select();
|
||||
e.target.select();
|
||||
setActiveField(unit);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { FC, useCallback, useEffect, createPortal } from "preact/compat";
|
||||
import React, { FC, useCallback, useEffect } from "preact/compat";
|
||||
import ReactDOM from "react-dom";
|
||||
import { CloseIcon } from "../Icons";
|
||||
import Button from "../Button/Button";
|
||||
import { ReactNode, MouseEvent } from "react";
|
||||
@@ -57,7 +58,7 @@ const Modal: FC<ModalProps> = ({
|
||||
useEventListener("popstate", handlePopstate);
|
||||
useEventListener("keyup", handleKeyUp);
|
||||
|
||||
return createPortal((
|
||||
return ReactDOM.createPortal((
|
||||
<div
|
||||
className={classNames({
|
||||
"vm-modal": true,
|
||||
|
||||
@@ -1,15 +1,6 @@
|
||||
import React, {
|
||||
FC,
|
||||
MouseEvent as ReactMouseEvent,
|
||||
ReactNode,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
useCallback,
|
||||
createPortal
|
||||
} from "react";
|
||||
import React, { FC, MouseEvent as ReactMouseEvent, ReactNode, useEffect, useMemo, useRef, useState } from "react";
|
||||
import classNames from "classnames";
|
||||
import ReactDOM from "react-dom";
|
||||
import "./style.scss";
|
||||
import useClickOutside from "../../../hooks/useClickOutside";
|
||||
import useDeviceDetect from "../../../hooks/useDeviceDetect";
|
||||
@@ -17,6 +8,7 @@ import Button from "../Button/Button";
|
||||
import { CloseIcon } from "../Icons";
|
||||
import { useLocation, useNavigate } from "react-router-dom";
|
||||
import useEventListener from "../../../hooks/useEventListener";
|
||||
import { useCallback } from "preact/compat";
|
||||
|
||||
interface PopperProps {
|
||||
children: ReactNode
|
||||
@@ -127,7 +119,7 @@ const Popper: FC<PopperProps> = ({
|
||||
return position;
|
||||
}, [buttonRef, placement, isOpen, children, fullWidth]);
|
||||
|
||||
const handleClickClose = (e: ReactMouseEvent<HTMLButtonElement>) => {
|
||||
const handleClickClose = (e: ReactMouseEvent<HTMLButtonElement, MouseEvent>) => {
|
||||
e.stopPropagation();
|
||||
onClose();
|
||||
};
|
||||
@@ -164,7 +156,7 @@ const Popper: FC<PopperProps> = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
{(isOpen || !popperSize.width) && createPortal((
|
||||
{(isOpen || !popperSize.width) && ReactDOM.createPortal((
|
||||
<div
|
||||
className={classNames({
|
||||
"vm-popper": true,
|
||||
|
||||
@@ -11,7 +11,7 @@ interface MultipleSelectedValueProps {
|
||||
const MultipleSelectedValue: FC<MultipleSelectedValueProps> = ({ values, onRemoveItem }) => {
|
||||
const { isMobile } = useDeviceDetect();
|
||||
|
||||
const createHandleClick = (value: string) => (e: MouseEvent<HTMLDivElement>) => {
|
||||
const createHandleClick = (value: string) => (e: MouseEvent) => {
|
||||
onRemoveItem(value);
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
@@ -92,7 +92,7 @@ const Select: FC<SelectProps> = ({
|
||||
setSearch((e.target as HTMLInputElement).value);
|
||||
};
|
||||
|
||||
const createHandleClick = (value: string) => (e: MouseEvent<HTMLDivElement>) => {
|
||||
const createHandleClick = (value: string) => (e: MouseEvent) => {
|
||||
handleSelected(value);
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
@@ -15,8 +15,6 @@ import useDeviceDetect from "../../../hooks/useDeviceDetect";
|
||||
import TextFieldMessage from "./TextFieldMessage";
|
||||
import "./style.scss";
|
||||
|
||||
export type TextFieldKeyboardEvent = KeyboardEvent<HTMLInputElement | HTMLTextAreaElement>;
|
||||
|
||||
interface TextFieldProps {
|
||||
label?: string,
|
||||
value?: string | number
|
||||
@@ -33,7 +31,7 @@ interface TextFieldProps {
|
||||
caretPosition?: [number, number]
|
||||
onChange?: (value: string) => void
|
||||
onEnter?: () => void
|
||||
onKeyDown?: (e: TextFieldKeyboardEvent) => void
|
||||
onKeyDown?: (e: KeyboardEvent) => void
|
||||
onFocus?: () => void
|
||||
onBlur?: () => void
|
||||
onChangeCaret?: (position: [number, number]) => void
|
||||
@@ -86,7 +84,7 @@ const TextField: FC<TextFieldProps> = ({
|
||||
updateCaretPosition(e.currentTarget);
|
||||
};
|
||||
|
||||
const handleKeyDown = (e: TextFieldKeyboardEvent) => {
|
||||
const handleKeyDown = (e: KeyboardEvent<HTMLInputElement | HTMLTextAreaElement>) => {
|
||||
onKeyDown && onKeyDown(e);
|
||||
const { key, ctrlKey, metaKey } = e;
|
||||
const isEnter = key === "Enter";
|
||||
@@ -97,7 +95,7 @@ const TextField: FC<TextFieldProps> = ({
|
||||
}
|
||||
};
|
||||
|
||||
const handleKeyUp = (e: TextFieldKeyboardEvent) => {
|
||||
const handleKeyUp = (e: KeyboardEvent<HTMLInputElement | HTMLTextAreaElement>) => {
|
||||
updateCaretPosition(e.currentTarget);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import React, { FC, useEffect, useMemo, useRef, useState, Fragment, createPortal } from "preact/compat";
|
||||
import React, { FC, useEffect, useMemo, useRef, useState, Fragment } from "preact/compat";
|
||||
import ReactDOM from "react-dom";
|
||||
import "./style.scss";
|
||||
import { ReactNode } from "react";
|
||||
import { ExoticComponent } from "react";
|
||||
import useDeviceDetect from "../../../hooks/useDeviceDetect";
|
||||
|
||||
interface TooltipProps {
|
||||
@@ -23,7 +25,7 @@ const Tooltip: FC<TooltipProps> = ({
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [popperSize, setPopperSize] = useState({ width: 0, height: 0 });
|
||||
|
||||
const buttonRef = useRef<ReactNode>(null);
|
||||
const buttonRef = useRef<ExoticComponent>(null);
|
||||
const popperRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const onScrollWindow = () => setIsOpen(false);
|
||||
@@ -118,7 +120,7 @@ const Tooltip: FC<TooltipProps> = ({
|
||||
{children}
|
||||
</Fragment>
|
||||
|
||||
{!isMobile && isOpen && createPortal((
|
||||
{!isMobile && isOpen && ReactDOM.createPortal((
|
||||
<div
|
||||
className="vm-tooltip"
|
||||
ref={popperRef}
|
||||
|
||||
@@ -8,8 +8,8 @@ import Switch from "../../Main/Switch/Switch";
|
||||
import { arrayEquals } from "../../../utils/array";
|
||||
import classNames from "classnames";
|
||||
import useBoolean from "../../../hooks/useBoolean";
|
||||
import TextField, { TextFieldKeyboardEvent } from "../../Main/TextField/TextField";
|
||||
import { useState } from "react";
|
||||
import TextField from "../../Main/TextField/TextField";
|
||||
import { KeyboardEvent, useState } from "react";
|
||||
import Modal from "../../Main/Modal/Modal";
|
||||
import { useSearchParams } from "react-router-dom";
|
||||
|
||||
@@ -96,7 +96,7 @@ const TableSettings: FC<TableSettingsProps> = ({
|
||||
setIndexFocusItem(-1);
|
||||
};
|
||||
|
||||
const handleKeyDown = (e: TextFieldKeyboardEvent) => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
const arrowUp = e.key === "ArrowUp";
|
||||
const arrowDown = e.key === "ArrowDown";
|
||||
const enter = e.key === "Enter";
|
||||
|
||||
@@ -6,8 +6,8 @@ const dateColumns = ["date", "timestamp", "time"];
|
||||
export function descendingComparator<T>(a: T, b: T, orderBy: keyof T) {
|
||||
const valueA = a[orderBy];
|
||||
const valueB = b[orderBy];
|
||||
const parsedValueA = dateColumns.includes(String(orderBy)) ? dayjs(`${valueA}`).unix() : valueA;
|
||||
const parsedValueB = dateColumns.includes(String(orderBy)) ? dayjs(`${valueB}`).unix() : valueB;
|
||||
const parsedValueA = dateColumns.includes(`${orderBy}`) ? dayjs(`${valueA}`).unix() : valueA;
|
||||
const parsedValueB = dateColumns.includes(`${orderBy}`) ? dayjs(`${valueB}`).unix() : valueB;
|
||||
if (parsedValueB < parsedValueA) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
export enum AppType {
|
||||
victoriametrics = "victoriametrics",
|
||||
victorialogs = "victorialogs",
|
||||
vmanomaly = "vmanomaly",
|
||||
}
|
||||
|
||||
export const APP_TYPE = import.meta.env.VITE_APP_TYPE;
|
||||
export const APP_TYPE_VM = APP_TYPE === AppType.victoriametrics;
|
||||
export const APP_TYPE_LOGS = APP_TYPE === AppType.victorialogs;
|
||||
export const APP_TYPE_ANOMALY = APP_TYPE === AppType.vmanomaly;
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useAppDispatch } from "../state/common/StateContext";
|
||||
import { useEffect, useState } from "preact/compat";
|
||||
import { ErrorTypes } from "../types";
|
||||
import { APP_TYPE_VM } from "../constants/appType";
|
||||
|
||||
const useFetchFlags = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
@@ -11,7 +10,7 @@ const useFetchFlags = () => {
|
||||
|
||||
useEffect(() => {
|
||||
const fetchAppConfig = async () => {
|
||||
if (!APP_TYPE_VM) return;
|
||||
if (process.env.REACT_APP_TYPE) return;
|
||||
setError("");
|
||||
setIsLoading(true);
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ import { useTimeDispatch } from "../state/time/TimeStateContext";
|
||||
import { getFromStorage } from "../utils/storage";
|
||||
import dayjs from "dayjs";
|
||||
import { getBrowserTimezone } from "../utils/time";
|
||||
import { APP_TYPE_VM } from "../constants/appType";
|
||||
|
||||
const disabledDefaultTimezone = Boolean(getFromStorage("DISABLED_DEFAULT_TIMEZONE"));
|
||||
|
||||
@@ -29,7 +28,7 @@ const useFetchDefaultTimezone = () => {
|
||||
};
|
||||
|
||||
const fetchDefaultTimezone = async () => {
|
||||
if (!serverUrl || !APP_TYPE_VM) return;
|
||||
if (!serverUrl || process.env.REACT_APP_TYPE) return;
|
||||
setError("");
|
||||
setIsLoading(true);
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useAppDispatch, useAppState } from "../state/common/StateContext";
|
||||
import { useEffect, useState } from "preact/compat";
|
||||
import { ErrorTypes } from "../types";
|
||||
import { APP_TYPE_VM } from "../constants/appType";
|
||||
|
||||
const useFetchFlags = () => {
|
||||
const { serverUrl } = useAppState();
|
||||
@@ -12,7 +11,7 @@ const useFetchFlags = () => {
|
||||
|
||||
useEffect(() => {
|
||||
const fetchFlags = async () => {
|
||||
if (!serverUrl || !APP_TYPE_VM) return;
|
||||
if (!serverUrl || process.env.REACT_APP_TYPE) return;
|
||||
setError("");
|
||||
setIsLoading(true);
|
||||
|
||||
|
||||
@@ -12,8 +12,8 @@ import { useCustomPanelState } from "../state/customPanel/CustomPanelStateContex
|
||||
import { isHistogramData } from "../utils/metric";
|
||||
import { useGraphState } from "../state/graph/GraphStateContext";
|
||||
import { getStepFromDuration } from "../utils/time";
|
||||
import { AppType } from "../types/appType";
|
||||
import { getQueryStringValue } from "../utils/query-string";
|
||||
import { APP_TYPE_ANOMALY } from "../constants/appType";
|
||||
|
||||
interface FetchQueryParams {
|
||||
predefinedQuery?: string[]
|
||||
@@ -49,6 +49,8 @@ interface FetchDataParams {
|
||||
hideQuery?: number[]
|
||||
}
|
||||
|
||||
const isAnomalyUI = AppType.anomaly === process.env.REACT_APP_TYPE;
|
||||
|
||||
export const useFetchQuery = ({
|
||||
predefinedQuery,
|
||||
visible,
|
||||
@@ -76,8 +78,8 @@ export const useFetchQuery = ({
|
||||
|
||||
const defaultStep = useMemo(() => {
|
||||
const { end, start } = period;
|
||||
return getStepFromDuration(end - start, isHistogramState, displayType);
|
||||
}, [period, isHistogramState, displayType]);
|
||||
return getStepFromDuration(end - start, isHistogramState);
|
||||
}, [period, isHistogramState]);
|
||||
|
||||
const fetchData = async ({
|
||||
fetchUrl,
|
||||
@@ -132,7 +134,7 @@ export const useFetchQuery = ({
|
||||
}
|
||||
|
||||
const preventChangeType = !!getQueryStringValue("display_mode", null);
|
||||
isHistogramResult = !APP_TYPE_ANOMALY && isDisplayChart && !preventChangeType && isHistogramData(resp.data.result);
|
||||
isHistogramResult = !isAnomalyUI && isDisplayChart && !preventChangeType && isHistogramData(resp.data.result);
|
||||
seriesLimit = isHistogramResult ? Infinity : defaultLimit;
|
||||
const freeTempSize = seriesLimit - tempData.length;
|
||||
resp.data.result.slice(0, freeTempSize).forEach((d: MetricBase) => {
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<link rel="icon" href="/favicon.svg" />
|
||||
<link rel="apple-touch-icon" href="/favicon.svg" />
|
||||
<link rel="mask-icon" href="/favicon.svg" color="#000000">
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.svg" />
|
||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/favicon.svg" />
|
||||
<link rel="mask-icon" href="%PUBLIC_URL%/favicon.svg" color="#000000">
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=5"/>
|
||||
<meta name="theme-color" content="#000000"/>
|
||||
@@ -13,13 +13,13 @@
|
||||
manifest.json provides metadata used when your web app is installed on a
|
||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
-->
|
||||
<link rel="manifest" href="/manifest.json"/>
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json"/>
|
||||
<!--
|
||||
Notice the use of in the tags above.
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
Only files inside the `public` folder can be referenced from the HTML.
|
||||
|
||||
Unlike "/favicon.ico" or "favicon.ico", "/favicon.ico" will
|
||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
@@ -29,7 +29,7 @@
|
||||
<meta name="twitter:title" content="UI for VictoriaLogs">
|
||||
<meta name="twitter:site" content="@https://victoriametrics.com/products/victorialogs/">
|
||||
<meta name="twitter:description" content="Explore your log data with VictoriaLogs UI">
|
||||
<meta name="twitter:image" content="/preview.jpg">
|
||||
<meta name="twitter:image" content="%PUBLIC_URL%/preview.jpg">
|
||||
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:title" content="UI for VictoriaLogs">
|
||||
@@ -49,6 +49,5 @@
|
||||
To begin the development, run `npm start` or `yarn start`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
-->
|
||||
<script type="module" src="/src/index.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -2,9 +2,9 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<link rel="icon" href="/favicon.svg"/>
|
||||
<link rel="apple-touch-icon" href="/favicon.svg"/>
|
||||
<link rel="mask-icon" href="/favicon.svg" color="#000000">
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.svg" />
|
||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/favicon.svg" />
|
||||
<link rel="mask-icon" href="%PUBLIC_URL%/favicon.svg" color="#000000">
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=5"/>
|
||||
<meta name="theme-color" content="#000000"/>
|
||||
@@ -13,24 +13,24 @@
|
||||
manifest.json provides metadata used when your web app is installed on a
|
||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
-->
|
||||
<link rel="manifest" href="/manifest.json"/>
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json"/>
|
||||
<!--
|
||||
Notice the use of in the tags above.
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
Only files inside the `public` folder can be referenced from the HTML.
|
||||
|
||||
Unlike "/favicon.ico" or "favicon.ico", "/favicon.ico" will
|
||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>vmui</title>
|
||||
<script src="/dashboards/index.js" type="module"></script>
|
||||
<script src="%PUBLIC_URL%/dashboards/index.js" type="module"></script>
|
||||
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta name="twitter:title" content="UI for VictoriaMetrics">
|
||||
<meta name="twitter:site" content="@https://victoriametrics.com/">
|
||||
<meta name="twitter:description" content="Explore and troubleshoot your VictoriaMetrics data">
|
||||
<meta name="twitter:image" content="/preview.jpg">
|
||||
<meta name="twitter:image" content="%PUBLIC_URL%/preview.jpg">
|
||||
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:title" content="UI for VictoriaMetrics">
|
||||
@@ -50,6 +50,5 @@
|
||||
To begin the development, run `npm start` or `yarn start`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
-->
|
||||
<script type="module" src="/src/index.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -2,9 +2,9 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<link rel="icon" href="/favicon.svg" />
|
||||
<link rel="apple-touch-icon" href="/favicon.svg" />
|
||||
<link rel="mask-icon" href="/favicon.svg" color="#000000">
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.svg" />
|
||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/favicon.svg" />
|
||||
<link rel="mask-icon" href="%PUBLIC_URL%/favicon.svg" color="#000000">
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=5"/>
|
||||
<meta name="theme-color" content="#000000"/>
|
||||
@@ -13,13 +13,13 @@
|
||||
manifest.json provides metadata used when your web app is installed on a
|
||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
-->
|
||||
<link rel="manifest" href="/manifest.json"/>
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json"/>
|
||||
<!--
|
||||
Notice the use of in the tags above.
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
Only files inside the `public` folder can be referenced from the HTML.
|
||||
|
||||
Unlike "/favicon.ico" or "favicon.ico", "/favicon.ico" will
|
||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
@@ -29,7 +29,7 @@
|
||||
<meta name="twitter:title" content="UI for VictoriaMetrics Anomaly Detection">
|
||||
<meta name="twitter:site" content="@https://victoriametrics.com/products/enterprise/anomaly-detection/">
|
||||
<meta name="twitter:description" content="Detect anomalies in your metrics with VictoriaMetrics Anomaly Detection UI">
|
||||
<meta name="twitter:image" content="/preview.jpg">
|
||||
<meta name="twitter:image" content="%PUBLIC_URL%/preview.jpg">
|
||||
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:title" content="UI for VictoriaMetrics Anomaly Detection">
|
||||
@@ -49,6 +49,5 @@
|
||||
To begin the development, run `npm start` or `yarn start`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
-->
|
||||
<script type="module" src="/src/index.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -3,23 +3,9 @@ import "./constants/dayjsPlugins";
|
||||
import App from "./App";
|
||||
import reportWebVitals from "./reportWebVitals";
|
||||
import "./styles/style.scss";
|
||||
import { APP_TYPE, AppType } from "./constants/appType";
|
||||
import AppLogs from "./AppLogs";
|
||||
import AppAnomaly from "./AppAnomaly";
|
||||
|
||||
const getAppComponent = () => {
|
||||
switch (APP_TYPE) {
|
||||
case AppType.victorialogs:
|
||||
return <AppLogs/>;
|
||||
case AppType.vmanomaly:
|
||||
return <AppAnomaly/>;
|
||||
default:
|
||||
return <App/>;
|
||||
}
|
||||
};
|
||||
|
||||
const root = document.getElementById("root");
|
||||
if (root) render(getAppComponent(), root);
|
||||
if (root) render(<App />, root);
|
||||
|
||||
|
||||
// If you want to start measuring performance in your app, pass a function
|
||||
|
||||
@@ -13,16 +13,19 @@ import HeaderControls, { ControlsProps } from "./HeaderControls/HeaderControls";
|
||||
import useDeviceDetect from "../../hooks/useDeviceDetect";
|
||||
import useWindowSize from "../../hooks/useWindowSize";
|
||||
import { ComponentType } from "react";
|
||||
import { APP_TYPE, AppType } from "../../constants/appType";
|
||||
import { AppType } from "../../types/appType";
|
||||
|
||||
export interface HeaderProps {
|
||||
controlsComponent: ComponentType<ControlsProps>
|
||||
}
|
||||
const { REACT_APP_TYPE } = process.env;
|
||||
const isCustomApp = REACT_APP_TYPE === AppType.logs || REACT_APP_TYPE === AppType.anomaly;
|
||||
|
||||
const Logo = () => {
|
||||
switch (APP_TYPE) {
|
||||
case AppType.victorialogs:
|
||||
switch (REACT_APP_TYPE) {
|
||||
case AppType.logs:
|
||||
return <LogoLogsIcon/>;
|
||||
case AppType.vmanomaly:
|
||||
case AppType.anomaly:
|
||||
return <LogoAnomalyIcon/>;
|
||||
default:
|
||||
return <LogoIcon/>;
|
||||
@@ -78,7 +81,10 @@ const Header: FC<HeaderProps> = ({ controlsComponent }) => {
|
||||
<>
|
||||
{!appModeEnable && (
|
||||
<div
|
||||
className="vm-header-logo"
|
||||
className={classNames({
|
||||
"vm-header-logo": true,
|
||||
"vm-header-logo_logs": isCustomApp
|
||||
})}
|
||||
onClick={onClickLogo}
|
||||
style={{ color }}
|
||||
>
|
||||
@@ -96,6 +102,7 @@ const Header: FC<HeaderProps> = ({ controlsComponent }) => {
|
||||
className={classNames({
|
||||
"vm-header-logo": true,
|
||||
"vm-header-logo_mobile": true,
|
||||
"vm-header-logo_logs": isCustomApp
|
||||
})}
|
||||
onClick={onClickLogo}
|
||||
style={{ color }}
|
||||
|
||||
@@ -8,13 +8,16 @@ import MenuBurger from "../../../components/Main/MenuBurger/MenuBurger";
|
||||
import useDeviceDetect from "../../../hooks/useDeviceDetect";
|
||||
import "./style.scss";
|
||||
import useBoolean from "../../../hooks/useBoolean";
|
||||
import { APP_TYPE_LOGS } from "../../../constants/appType";
|
||||
import { AppType } from "../../../types/appType";
|
||||
|
||||
interface SidebarHeaderProps {
|
||||
background: string
|
||||
color: string
|
||||
}
|
||||
|
||||
const { REACT_APP_TYPE } = process.env;
|
||||
const isLogsApp = REACT_APP_TYPE === AppType.logs;
|
||||
|
||||
const SidebarHeader: FC<SidebarHeaderProps> = ({
|
||||
background,
|
||||
color,
|
||||
@@ -61,7 +64,7 @@ const SidebarHeader: FC<SidebarHeaderProps> = ({
|
||||
/>
|
||||
</div>
|
||||
<div className="vm-header-sidebar-menu-settings">
|
||||
{!isMobile && !APP_TYPE_LOGS && <ShortcutKeys showTitle={true}/>}
|
||||
{!isMobile && !isLogsApp && <ShortcutKeys showTitle={true}/>}
|
||||
</div>
|
||||
</div>
|
||||
</div>;
|
||||
|
||||
@@ -47,14 +47,14 @@
|
||||
justify-content: flex-start;
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
max-width: 75px;
|
||||
min-width: 75px;
|
||||
max-width: 65px;
|
||||
min-width: 65px;
|
||||
margin-bottom: 2px;
|
||||
overflow: hidden;
|
||||
|
||||
svg {
|
||||
max-width: 75px;
|
||||
min-width: 75px;
|
||||
max-width: 65px;
|
||||
min-width: 65px;
|
||||
}
|
||||
|
||||
&_mobile {
|
||||
@@ -62,5 +62,10 @@
|
||||
min-width: 65px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
&_logs, &_logs svg {
|
||||
max-width: 75px;
|
||||
min-width: 75px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ const EnhancedTable: FC<TableProps> = ({
|
||||
const [orderBy, setOrderBy] = useState<keyof Data>(defaultSortColumn);
|
||||
|
||||
const handleRequestSort = (
|
||||
event: MouseEvent<HTMLTableCellElement>,
|
||||
event: MouseEvent<unknown>,
|
||||
property: keyof Data,
|
||||
) => {
|
||||
const isAsc = orderBy === property && order === "asc";
|
||||
|
||||
@@ -7,7 +7,7 @@ import Tooltip from "../../../components/Main/Tooltip/Tooltip";
|
||||
|
||||
export function EnhancedTableHead(props: EnhancedHeaderTableProps) {
|
||||
const { order, orderBy, onRequestSort, headerCells } = props;
|
||||
const createSortHandler = (property: keyof Data) => (event: MouseEvent<HTMLTableCellElement>) => {
|
||||
const createSortHandler = (property: keyof Data) => (event: MouseEvent<unknown>) => {
|
||||
onRequestSort(event, property);
|
||||
};
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ export interface HeadCell {
|
||||
}
|
||||
|
||||
export interface EnhancedHeaderTableProps {
|
||||
onRequestSort: (event: MouseEvent<HTMLTableCellElement>, property: keyof Data) => void;
|
||||
onRequestSort: (event: MouseEvent<unknown>, property: keyof Data) => void;
|
||||
order: Order;
|
||||
orderBy: string;
|
||||
rowCount: number;
|
||||
|
||||
@@ -3,11 +3,10 @@ import { useCustomPanelDispatch, useCustomPanelState } from "../../state/customP
|
||||
import { ChartIcon, CodeIcon, TableIcon } from "../../components/Main/Icons";
|
||||
import Tabs from "../../components/Main/Tabs/Tabs";
|
||||
import { DisplayType } from "../../types";
|
||||
import { ReactNode } from "react";
|
||||
|
||||
type DisplayTab = {
|
||||
value: DisplayType
|
||||
icon: ReactNode
|
||||
icon: JSX.Element
|
||||
label: string
|
||||
prometheusCode: number
|
||||
}
|
||||
|
||||
@@ -114,7 +114,7 @@ const QueryConfigurator: FC<QueryConfiguratorProps> = ({
|
||||
setStateQuery(prev => prev.filter((q, i) => i !== index));
|
||||
};
|
||||
|
||||
const handleToggleHideQuery = (e: ReactMouseEvent<HTMLButtonElement>, index: number) => {
|
||||
const handleToggleHideQuery = (e: ReactMouseEvent<HTMLButtonElement, MouseEvent>, index: number) => {
|
||||
const { ctrlKey, metaKey } = e;
|
||||
const ctrlMetaKey = ctrlKey || metaKey;
|
||||
|
||||
@@ -160,7 +160,7 @@ const QueryConfigurator: FC<QueryConfiguratorProps> = ({
|
||||
setHideQuery(prev => prev.includes(i) ? prev.filter(n => n !== i) : prev.map(n => n > i ? n - 1 : n));
|
||||
};
|
||||
|
||||
const createHandlerHideQuery = (i: number) => (e: ReactMouseEvent<HTMLButtonElement>) => {
|
||||
const createHandlerHideQuery = (i: number) => (e: ReactMouseEvent<HTMLButtonElement, MouseEvent>) => {
|
||||
handleToggleHideQuery(e, i);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
@use "src/styles/variables" as *;
|
||||
@use 'sass:color';
|
||||
|
||||
$font-size-logs: var(--font-size-logs, $font-size-small);
|
||||
|
||||
@@ -80,8 +79,8 @@ $font-size-logs: var(--font-size-logs, $font-size-small);
|
||||
&__pair {
|
||||
order: 0;
|
||||
padding: calc($padding-global / 2) $padding-global;
|
||||
background-color: color.scale($color-tropical-blue, $lightness: 60%);
|
||||
color: color.scale($color-tropical-blue, $lightness: -60%);
|
||||
background-color: lighten($color-tropical-blue, 6%);
|
||||
color: darken($color-dodger-blue, 20%);
|
||||
border-radius: $border-radius-medium;
|
||||
transition: background-color 0.3s ease-in, transform 0.1s ease-in, opacity 0.3s ease-in;
|
||||
white-space: nowrap;
|
||||
@@ -106,7 +105,7 @@ $font-size-logs: var(--font-size-logs, $font-size-small);
|
||||
}
|
||||
|
||||
&_dark {
|
||||
color: color.scale($color-dodger-blue, $lightness: 40%);
|
||||
color: lighten($color-dodger-blue, 20%);
|
||||
background-color: $color-background-body;
|
||||
opacity: 0.8;
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ const PredefinedDashboard: FC<PredefinedDashboardProps> = ({
|
||||
setPanelsWidth(width);
|
||||
}, [resize, sizeSection]);
|
||||
|
||||
const handleMouseDown = (e: ReactMouseEvent<HTMLButtonElement>, i: number) => {
|
||||
const handleMouseDown = (e: ReactMouseEvent<HTMLButtonElement, MouseEvent>, i: number) => {
|
||||
setResize({
|
||||
start: e.clientX,
|
||||
target: i,
|
||||
|
||||
@@ -3,7 +3,6 @@ import { DashboardSettings, ErrorTypes } from "../../../types";
|
||||
import { useAppState } from "../../../state/common/StateContext";
|
||||
import { useDashboardsDispatch } from "../../../state/dashboards/DashboardsStateContext";
|
||||
import { getAppModeEnable } from "../../../utils/app-mode";
|
||||
import { APP_TYPE_VM } from "../../../constants/appType";
|
||||
|
||||
const importModule = async (filename: string) => {
|
||||
const data = await fetch(`./dashboards/${filename}`);
|
||||
@@ -35,7 +34,7 @@ export const useFetchDashboards = (): {
|
||||
};
|
||||
|
||||
const fetchRemoteDashboards = async () => {
|
||||
if (!serverUrl || !APP_TYPE_VM) return;
|
||||
if (!serverUrl || process.env.REACT_APP_TYPE) return;
|
||||
setError("");
|
||||
setIsLoading(true);
|
||||
|
||||
|
||||
@@ -108,12 +108,10 @@ const QueryAnalyzer: FC = () => {
|
||||
};
|
||||
|
||||
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
if (!e.target) return;
|
||||
const target = e.target as HTMLInputElement;
|
||||
setError("");
|
||||
const files = Array.from(target.files || []);
|
||||
const files = Array.from(e.target.files || []);
|
||||
handleReadFiles(files);
|
||||
target.value = "";
|
||||
e.target.value = "";
|
||||
};
|
||||
|
||||
const handleCloseError = () => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { FC, useEffect, useMemo } from "react";
|
||||
import React, { FC, useEffect, useMemo, KeyboardEvent } from "react";
|
||||
import { useFetchTopQueries } from "./hooks/useFetchTopQueries";
|
||||
import Spinner from "../../components/Main/Spinner/Spinner";
|
||||
import TopQueryPanel from "./TopQueryPanel/TopQueryPanel";
|
||||
@@ -8,7 +8,7 @@ import dayjs from "dayjs";
|
||||
import { TopQueryStats } from "../../types";
|
||||
import Button from "../../components/Main/Button/Button";
|
||||
import { PlayIcon } from "../../components/Main/Icons";
|
||||
import TextField, { TextFieldKeyboardEvent } from "../../components/Main/TextField/TextField";
|
||||
import TextField from "../../components/Main/TextField/TextField";
|
||||
import Alert from "../../components/Main/Alert/Alert";
|
||||
import Tooltip from "../../components/Main/Tooltip/Tooltip";
|
||||
import "./style.scss";
|
||||
@@ -55,7 +55,7 @@ const TopQueries: FC = () => {
|
||||
setMaxLifetime(value);
|
||||
};
|
||||
|
||||
const onKeyDown = (e: TextFieldKeyboardEvent) => {
|
||||
const onKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.key === "Enter") fetch();
|
||||
};
|
||||
|
||||
|
||||
@@ -56,12 +56,10 @@ const TracePage: FC = () => {
|
||||
};
|
||||
|
||||
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
if (!e.target) return;
|
||||
const target = e.target as HTMLInputElement;
|
||||
setErrors([]);
|
||||
const files = Array.from(target.files || []);
|
||||
const files = Array.from(e.target.files || []);
|
||||
handleReadFiles(files);
|
||||
target.value = "";
|
||||
e.target.value = "";
|
||||
};
|
||||
|
||||
const handleTraceDelete = (trace: Trace) => {
|
||||
|
||||
1
app/vmui/packages/vmui/src/react-app-env.d.ts
vendored
Normal file
1
app/vmui/packages/vmui/src/react-app-env.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/// <reference types="react-scripts" />
|
||||
@@ -1,4 +1,4 @@
|
||||
import { APP_TYPE_LOGS } from "../constants/appType";
|
||||
import { AppType } from "../types/appType";
|
||||
|
||||
const router = {
|
||||
home: "/",
|
||||
@@ -34,12 +34,15 @@ export interface RouterOptions {
|
||||
header: RouterOptionsHeader
|
||||
}
|
||||
|
||||
const { REACT_APP_TYPE } = process.env;
|
||||
const isLogsApp = REACT_APP_TYPE === AppType.logs;
|
||||
|
||||
const routerOptionsDefault = {
|
||||
header: {
|
||||
tenant: true,
|
||||
stepControl: !APP_TYPE_LOGS,
|
||||
timeSelector: !APP_TYPE_LOGS,
|
||||
executionControls: !APP_TYPE_LOGS,
|
||||
stepControl: !isLogsApp,
|
||||
timeSelector: !isLogsApp,
|
||||
executionControls: !isLogsApp,
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -2,9 +2,11 @@ import { getAppModeEnable } from "../utils/app-mode";
|
||||
import { useDashboardsState } from "../state/dashboards/DashboardsStateContext";
|
||||
import { useAppState } from "../state/common/StateContext";
|
||||
import { useMemo } from "preact/compat";
|
||||
import { AppType } from "../types/appType";
|
||||
import { processNavigationItems } from "./utils";
|
||||
import { getAnomalyNavigation, getDefaultNavigation, getLogsNavigation } from "./navigation";
|
||||
import { APP_TYPE, AppType } from "../constants/appType";
|
||||
|
||||
const appType = process.env.REACT_APP_TYPE;
|
||||
|
||||
const useNavigationMenu = () => {
|
||||
const appModeEnable = getAppModeEnable();
|
||||
@@ -23,10 +25,10 @@ const useNavigationMenu = () => {
|
||||
|
||||
|
||||
const menu = useMemo(() => {
|
||||
switch (APP_TYPE) {
|
||||
case AppType.victorialogs:
|
||||
switch (appType) {
|
||||
case AppType.logs:
|
||||
return getLogsNavigation();
|
||||
case AppType.vmanomaly:
|
||||
case AppType.anomaly:
|
||||
return getAnomalyNavigation();
|
||||
default:
|
||||
return getDefaultNavigation(navigationConfig);
|
||||
|
||||
@@ -28,7 +28,7 @@ export function reducer(state: LogsState, action: LogsAction): LogsState {
|
||||
case "SET_AUTOCOMPLETE_CACHE": {
|
||||
if (state.autocompleteCache.size >= AUTOCOMPLETE_LIMITS.cacheLimit) {
|
||||
const firstKey = state.autocompleteCache.keys().next().value;
|
||||
firstKey && state.autocompleteCache.delete(firstKey);
|
||||
state.autocompleteCache.delete(firstKey);
|
||||
}
|
||||
state.autocompleteCache.set(action.payload.key, action.payload.value);
|
||||
|
||||
|
||||
4
app/vmui/packages/vmui/src/types/appType.ts
Normal file
4
app/vmui/packages/vmui/src/types/appType.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export enum AppType {
|
||||
logs = "logs",
|
||||
anomaly = "anomaly",
|
||||
}
|
||||
@@ -4,7 +4,7 @@ export const arrayEquals = (a: (string|number)[], b: (string|number)[]) => {
|
||||
|
||||
export function groupByMultipleKeys<T>(items: T[], keys: (keyof T)[]): { keys: string[], values: T[] }[] {
|
||||
const groups = items.reduce((result, item) => {
|
||||
const compositeKey = keys.map(key => `${String(key)}: ${item[key] || "-"}`).join("|");
|
||||
const compositeKey = keys.map(key => `${key}: ${item[key] || "-"}`).join("|");
|
||||
|
||||
(result[compositeKey] = result[compositeKey] || []).push(item);
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import React, { ComponentProps, FC, ReactNode } from "react";
|
||||
import React, { ComponentProps, FC } from "react";
|
||||
|
||||
type Props = { children: ReactNode };
|
||||
type Props = { children: JSX.Element };
|
||||
|
||||
export const combineComponents = (...components: FC<Props>[]): FC<Props> => {
|
||||
return components.reduce(
|
||||
(AccumulatedComponents, CurrentComponent) => {
|
||||
// eslint-disable-next-line react/display-name
|
||||
return ({ children }: ComponentProps<FC<Props>>): ReactNode => (
|
||||
return ({ children }: ComponentProps<FC<Props>>): JSX.Element => (
|
||||
<AccumulatedComponents>
|
||||
<CurrentComponent>{children}</CurrentComponent>
|
||||
</AccumulatedComponents>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { getAppModeParams } from "./app-mode";
|
||||
import { replaceTenantId } from "./tenants";
|
||||
import { APP_TYPE, AppType } from "../constants/appType";
|
||||
import { AppType } from "../types/appType";
|
||||
import { getFromStorage } from "./storage";
|
||||
const { REACT_APP_TYPE } = process.env;
|
||||
|
||||
export const getDefaultServer = (tenantId?: string): string => {
|
||||
const { serverURL } = getAppModeParams();
|
||||
@@ -11,10 +12,10 @@ export const getDefaultServer = (tenantId?: string): string => {
|
||||
const defaultURL = window.location.href.replace(/\/(?:prometheus\/)?(?:graph|vmui)\/.*/, "/prometheus");
|
||||
const url = serverURL || storageURL || defaultURL;
|
||||
|
||||
switch (APP_TYPE) {
|
||||
case AppType.victorialogs:
|
||||
switch (REACT_APP_TYPE) {
|
||||
case AppType.logs:
|
||||
return logsURL;
|
||||
case AppType.vmanomaly:
|
||||
case AppType.anomaly:
|
||||
return storageURL || anomalyURL;
|
||||
default:
|
||||
return tenantId ? replaceTenantId(url, tenantId) : url;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { DisplayType, RelativeTimeOption, TimeParams, TimePeriod, Timezone } from "../types";
|
||||
import { RelativeTimeOption, TimeParams, TimePeriod, Timezone } from "../types";
|
||||
import dayjs, { UnitTypeShort } from "dayjs";
|
||||
import { getQueryStringValue } from "./query-string";
|
||||
import { DATE_ISO_FORMAT } from "../constants/date";
|
||||
import timezones from "../constants/timezones";
|
||||
import { APP_TYPE_LOGS } from "../constants/appType";
|
||||
import { AppType } from "../types/appType";
|
||||
|
||||
const MAX_ITEMS_PER_CHART = window.innerWidth / 4;
|
||||
const MAX_ITEMS_PER_HISTOGRAM = window.innerWidth / 40;
|
||||
@@ -88,10 +88,7 @@ export const getSecondsFromDuration = (dur: string) => {
|
||||
return dayjs.duration(durObject).asSeconds();
|
||||
};
|
||||
|
||||
const instantQueryViews = [DisplayType.table, DisplayType.code];
|
||||
export const getStepFromDuration = (dur: number, histogram?: boolean, displayType?: DisplayType): string => {
|
||||
if (displayType && instantQueryViews.includes(displayType)) return roundStep(dur);
|
||||
|
||||
export const getStepFromDuration = (dur: number, histogram?: boolean): string => {
|
||||
const size = histogram ? MAX_ITEMS_PER_HISTOGRAM : MAX_ITEMS_PER_CHART;
|
||||
return roundStep(dur / size);
|
||||
};
|
||||
@@ -163,10 +160,11 @@ export const dateFromSeconds = (epochTimeInSeconds: number): Date => {
|
||||
const getYesterday = () => dayjs().tz().subtract(1, "day").endOf("day").toDate();
|
||||
const getToday = () => dayjs().tz().endOf("day").toDate();
|
||||
|
||||
const isLogsApp = process.env.REACT_APP_TYPE === AppType.logs;
|
||||
export const relativeTimeOptions: RelativeTimeOption[] = [
|
||||
{ title: "Last 5 minutes", duration: "5m", isDefault: APP_TYPE_LOGS },
|
||||
{ title: "Last 5 minutes", duration: "5m", isDefault: isLogsApp },
|
||||
{ title: "Last 15 minutes", duration: "15m" },
|
||||
{ title: "Last 30 minutes", duration: "30m", isDefault: !APP_TYPE_LOGS },
|
||||
{ title: "Last 30 minutes", duration: "30m", isDefault: !isLogsApp },
|
||||
{ title: "Last 1 hour", duration: "1h" },
|
||||
{ title: "Last 3 hours", duration: "3h" },
|
||||
{ title: "Last 6 hours", duration: "6h" },
|
||||
|
||||
2
app/vmui/packages/vmui/src/vite-env.d.ts
vendored
2
app/vmui/packages/vmui/src/vite-env.d.ts
vendored
@@ -1,2 +0,0 @@
|
||||
/// <reference types="vite/client" />
|
||||
/// <reference types="vite/types/importMeta.d.ts" />
|
||||
@@ -1,7 +1,6 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"types": ["vite/client"],
|
||||
"target": "es5",
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
@@ -20,13 +19,7 @@
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
"downlevelIteration": true,
|
||||
"paths": {
|
||||
"react": ["./node_modules/preact/compat/"],
|
||||
"react/jsx-runtime": ["./node_modules/preact/jsx-runtime"],
|
||||
"react-dom": ["./node_modules/preact/compat/"],
|
||||
"react-dom/*": ["./node_modules/preact/compat/*"]
|
||||
}
|
||||
"downlevelIteration": true
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
import * as path from "path";
|
||||
|
||||
import { defineConfig } from "vite";
|
||||
import preact from "@preact/preset-vite";
|
||||
import dynamicIndexHtmlPlugin from "./config/plugins/dynamicIndexHtml";
|
||||
|
||||
export default defineConfig(({ mode }) => {
|
||||
return {
|
||||
base: "",
|
||||
plugins: [
|
||||
preact(),
|
||||
dynamicIndexHtmlPlugin({ mode })
|
||||
],
|
||||
assetsInclude: ["**/*.md"],
|
||||
server: {
|
||||
open: true,
|
||||
port: 3000,
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
"src": path.resolve(__dirname, "src"),
|
||||
},
|
||||
},
|
||||
build: {
|
||||
outDir: "./build",
|
||||
rollupOptions: {
|
||||
output: {
|
||||
manualChunks(id) {
|
||||
if (id.includes("node_modules")) {
|
||||
return "vendor";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -44,4 +44,4 @@ only from `cluster` branch. Hence, not all test cases suitable to run in both br
|
||||
- If test is using binaries from `cluster` branch, then test name should be prefixed
|
||||
with `TestCluster` word
|
||||
- If test is using binaries from `master` branch, then test name should be prefixed
|
||||
with `TestSingle` word.
|
||||
with `TestVmsingle` word.
|
||||
|
||||
@@ -233,23 +233,6 @@ func (tc *TestCase) StopApp(instance string) {
|
||||
}
|
||||
}
|
||||
|
||||
// StopPrometheusWriteQuerier stop all apps that are a part of the pwq.
|
||||
func (tc *TestCase) StopPrometheusWriteQuerier(pwq PrometheusWriteQuerier) {
|
||||
tc.t.Helper()
|
||||
switch t := pwq.(type) {
|
||||
case *Vmsingle:
|
||||
tc.StopApp(t.Name())
|
||||
case *vmcluster:
|
||||
tc.StopApp(t.Vminsert.Name())
|
||||
tc.StopApp(t.Vmselect.Name())
|
||||
for _, vmstorage := range t.vmstorages {
|
||||
tc.StopApp(vmstorage.Name())
|
||||
}
|
||||
default:
|
||||
tc.t.Fatalf("Unsupported type: %v", t)
|
||||
}
|
||||
}
|
||||
|
||||
// ForceFlush flushes zero or more storages.
|
||||
func (tc *TestCase) ForceFlush(apps ...*Vmstorage) {
|
||||
tc.t.Helper()
|
||||
|
||||
@@ -1,220 +0,0 @@
|
||||
package tests
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
at "github.com/VictoriaMetrics/VictoriaMetrics/apptest"
|
||||
)
|
||||
|
||||
func TestSingleSearchWithDisabledPerDayIndex(t *testing.T) {
|
||||
tc := at.NewTestCase(t)
|
||||
defer tc.Stop()
|
||||
|
||||
testSearchWithDisabledPerDayIndex(tc, func(name string, disablePerDayIndex bool) at.PrometheusWriteQuerier {
|
||||
return tc.MustStartVmsingle("vmsingle-"+name, []string{
|
||||
"-storageDataPath=" + tc.Dir() + "/vmsingle",
|
||||
"-retentionPeriod=100y",
|
||||
"-search.maxStalenessInterval=1m",
|
||||
fmt.Sprintf("-disablePerDayIndex=%t", disablePerDayIndex),
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestClusterSearchWithDisabledPerDayIndex(t *testing.T) {
|
||||
tc := at.NewTestCase(t)
|
||||
defer tc.Stop()
|
||||
|
||||
testSearchWithDisabledPerDayIndex(tc, func(name string, disablePerDayIndex bool) at.PrometheusWriteQuerier {
|
||||
return tc.MustStartCluster(&at.ClusterOptions{
|
||||
Vmstorage1Instance: "vmstorage1-" + name,
|
||||
Vmstorage1Flags: []string{
|
||||
fmt.Sprintf("-disablePerDayIndex=%t", disablePerDayIndex),
|
||||
},
|
||||
Vmstorage2Instance: "vmstorage2-" + name,
|
||||
Vmstorage2Flags: []string{
|
||||
fmt.Sprintf("-disablePerDayIndex=%t", disablePerDayIndex),
|
||||
},
|
||||
VminsertInstance: "vminsert",
|
||||
VmselectInstance: "vmselect",
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
type startSUTFunc func(name string, disablePerDayIndex bool) at.PrometheusWriteQuerier
|
||||
|
||||
// testDisablePerDayIndex_Search shows what search results to expect when data
|
||||
// is first inserted with per-day index enabled and then with per-day index
|
||||
// disabled.
|
||||
//
|
||||
// The data inserted with enabled per-day index must be searchable with disabled
|
||||
// per-day index.
|
||||
//
|
||||
// The data inserted with disabled per-day index is not searcheable with per-day
|
||||
// index enabled unless the search time range is > 40 days.
|
||||
func testSearchWithDisabledPerDayIndex(tc *at.TestCase, start startSUTFunc) {
|
||||
t := tc.T()
|
||||
|
||||
type opts struct {
|
||||
start, end string
|
||||
wantSeries []map[string]string
|
||||
wantQueryResults []*at.QueryResult
|
||||
}
|
||||
assertSearchResults := func(sut at.PrometheusQuerier, opts *opts) {
|
||||
t.Helper()
|
||||
tc.Assert(&at.AssertOptions{
|
||||
Msg: "unexpected /api/v1/series response",
|
||||
Got: func() any {
|
||||
return sut.PrometheusAPIV1Series(t, `{__name__=~".*"}`, at.QueryOpts{
|
||||
Start: opts.start,
|
||||
End: opts.end,
|
||||
}).Sort()
|
||||
},
|
||||
Want: &at.PrometheusAPIV1SeriesResponse{
|
||||
Status: "success",
|
||||
Data: opts.wantSeries,
|
||||
},
|
||||
})
|
||||
tc.Assert(&at.AssertOptions{
|
||||
Msg: "unexpected /api/v1/query_range response",
|
||||
Got: func() any {
|
||||
return sut.PrometheusAPIV1QueryRange(t, `{__name__=~".*"}`, at.QueryOpts{
|
||||
Start: opts.start,
|
||||
End: opts.end,
|
||||
Step: "1d",
|
||||
})
|
||||
},
|
||||
Want: &at.PrometheusAPIV1QueryResponse{
|
||||
Status: "success",
|
||||
Data: &at.QueryData{
|
||||
ResultType: "matrix",
|
||||
Result: opts.wantQueryResults,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// Start vmsingle with enabled per-day index, insert sample1, and confirm it
|
||||
// is searcheable.
|
||||
sut := start("with-per-day-index", false)
|
||||
sample1 := []string{"metric1 111 1704067200000"} // 2024-01-01T00:00:00Z
|
||||
sut.PrometheusAPIV1ImportPrometheus(t, sample1, at.QueryOpts{})
|
||||
sut.ForceFlush(t)
|
||||
assertSearchResults(sut, &opts{
|
||||
start: "2024-01-01T00:00:00Z",
|
||||
end: "2024-01-01T23:59:59Z",
|
||||
wantSeries: []map[string]string{{"__name__": "metric1"}},
|
||||
wantQueryResults: []*at.QueryResult{
|
||||
{
|
||||
Metric: map[string]string{"__name__": "metric1"},
|
||||
Samples: []*at.Sample{{Timestamp: 1704067200000, Value: float64(111)}},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
// Restart vmsingle with disabled per-day index, insert sample2, and confirm
|
||||
// that both sample1 and sample2 is searcheable.
|
||||
tc.StopPrometheusWriteQuerier(sut)
|
||||
sut = start("without-per-day-index", true)
|
||||
sample2 := []string{"metric2 222 1704067200000"} // 2024-01-01T00:00:00Z
|
||||
sut.PrometheusAPIV1ImportPrometheus(t, sample2, at.QueryOpts{})
|
||||
sut.ForceFlush(t)
|
||||
assertSearchResults(sut, &opts{
|
||||
start: "2024-01-01T00:00:00Z",
|
||||
end: "2024-01-01T23:59:59Z",
|
||||
wantSeries: []map[string]string{
|
||||
{"__name__": "metric1"},
|
||||
{"__name__": "metric2"},
|
||||
},
|
||||
wantQueryResults: []*at.QueryResult{
|
||||
{
|
||||
Metric: map[string]string{"__name__": "metric1"},
|
||||
Samples: []*at.Sample{{Timestamp: 1704067200000, Value: float64(111)}},
|
||||
},
|
||||
{
|
||||
Metric: map[string]string{"__name__": "metric2"},
|
||||
Samples: []*at.Sample{{Timestamp: 1704067200000, Value: float64(222)}},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
// Insert sample1 but for a different date, restart vmsingle with enabled
|
||||
// per-day index and confirm that:
|
||||
// - sample1 is searcheable within the time range of Jan 1st
|
||||
// - sample1 is not searcheable within the time range of Jan 20th
|
||||
// - sample1 is searcheable within the time range of Jan 1st-20th (because
|
||||
// the metric1 metricID will be found in the per-day index for Jan 1st).
|
||||
// - sample2 is not searcheable when the time range is <= 40 days
|
||||
// - sample2 becomes searcheable when the time range is > 40 days
|
||||
sample3 := []string{"metric1 333 1705708800000"} // 2024-01-20T00:00:00Z
|
||||
sut.PrometheusAPIV1ImportPrometheus(t, sample3, at.QueryOpts{})
|
||||
sut.ForceFlush(t)
|
||||
tc.StopPrometheusWriteQuerier(sut)
|
||||
sut = start("with-per-day-index2", false)
|
||||
|
||||
// Time range is 1 day (Jan 1st) <= 40 days
|
||||
assertSearchResults(sut, &opts{
|
||||
start: "2024-01-01T00:00:00Z",
|
||||
end: "2024-01-01T23:59:59Z",
|
||||
wantSeries: []map[string]string{
|
||||
{"__name__": "metric1"},
|
||||
},
|
||||
wantQueryResults: []*at.QueryResult{
|
||||
{
|
||||
Metric: map[string]string{"__name__": "metric1"},
|
||||
Samples: []*at.Sample{{Timestamp: 1704067200000, Value: float64(111)}},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
// Time range is 1 day (Jan 20th) <= 40 days
|
||||
assertSearchResults(sut, &opts{
|
||||
start: "2024-01-20T00:00:00Z",
|
||||
end: "2024-01-20T23:59:59Z",
|
||||
wantSeries: []map[string]string{},
|
||||
wantQueryResults: []*at.QueryResult{},
|
||||
})
|
||||
|
||||
// Time range is 20 days (Jan 1st-20th) <= 40 days
|
||||
assertSearchResults(sut, &opts{
|
||||
start: "2024-01-01T00:00:00Z",
|
||||
end: "2024-01-20T23:59:59Z",
|
||||
wantSeries: []map[string]string{
|
||||
{"__name__": "metric1"},
|
||||
},
|
||||
wantQueryResults: []*at.QueryResult{
|
||||
{
|
||||
Metric: map[string]string{"__name__": "metric1"},
|
||||
Samples: []*at.Sample{
|
||||
{Timestamp: 1704067200000, Value: float64(111)},
|
||||
{Timestamp: 1705708800000, Value: float64(333)},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
// Time range > 40 days
|
||||
assertSearchResults(sut, &opts{
|
||||
start: "2024-01-01T00:00:00Z",
|
||||
end: "2024-02-29T23:59:59Z",
|
||||
wantSeries: []map[string]string{
|
||||
{"__name__": "metric1"},
|
||||
{"__name__": "metric2"},
|
||||
},
|
||||
wantQueryResults: []*at.QueryResult{
|
||||
{
|
||||
Metric: map[string]string{"__name__": "metric1"},
|
||||
Samples: []*at.Sample{
|
||||
{Timestamp: 1704067200000, Value: float64(111)},
|
||||
{Timestamp: 1705708800000, Value: float64(333)},
|
||||
},
|
||||
},
|
||||
{
|
||||
Metric: map[string]string{"__name__": "metric2"},
|
||||
Samples: []*at.Sample{
|
||||
{Timestamp: 1704067200000, Value: float64(222)},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/apptest"
|
||||
)
|
||||
|
||||
func TestSingleVMAuthRouterWithAuth(t *testing.T) {
|
||||
func TestVMAuthRouterWithAuth(t *testing.T) {
|
||||
tc := apptest.NewTestCase(t)
|
||||
defer tc.Stop()
|
||||
|
||||
@@ -117,7 +117,7 @@ users:
|
||||
|
||||
}
|
||||
|
||||
func TestSingleVMAuthRouterWithInternalAddr(t *testing.T) {
|
||||
func TestVMAuthRouterWithInternalAddr(t *testing.T) {
|
||||
tc := apptest.NewTestCase(t)
|
||||
defer tc.Stop()
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ services:
|
||||
# And forward them to --remoteWrite.url
|
||||
vmagent:
|
||||
container_name: vmagent
|
||||
image: victoriametrics/vmagent:v1.111.0
|
||||
image: victoriametrics/vmagent:v1.110.0
|
||||
depends_on:
|
||||
- "vminsert"
|
||||
ports:
|
||||
@@ -39,7 +39,7 @@ services:
|
||||
# where N is number of vmstorages (2 in this case).
|
||||
vmstorage-1:
|
||||
container_name: vmstorage-1
|
||||
image: victoriametrics/vmstorage:v1.111.0-cluster
|
||||
image: victoriametrics/vmstorage:v1.110.0-cluster
|
||||
ports:
|
||||
- 8482
|
||||
- 8400
|
||||
@@ -51,7 +51,7 @@ services:
|
||||
restart: always
|
||||
vmstorage-2:
|
||||
container_name: vmstorage-2
|
||||
image: victoriametrics/vmstorage:v1.111.0-cluster
|
||||
image: victoriametrics/vmstorage:v1.110.0-cluster
|
||||
ports:
|
||||
- 8482
|
||||
- 8400
|
||||
@@ -66,7 +66,7 @@ services:
|
||||
# pre-process them and distributes across configured vmstorage shards.
|
||||
vminsert:
|
||||
container_name: vminsert
|
||||
image: victoriametrics/vminsert:v1.111.0-cluster
|
||||
image: victoriametrics/vminsert:v1.110.0-cluster
|
||||
depends_on:
|
||||
- "vmstorage-1"
|
||||
- "vmstorage-2"
|
||||
@@ -81,7 +81,7 @@ services:
|
||||
# vmselect collects results from configured `--storageNode` shards.
|
||||
vmselect-1:
|
||||
container_name: vmselect-1
|
||||
image: victoriametrics/vmselect:v1.111.0-cluster
|
||||
image: victoriametrics/vmselect:v1.110.0-cluster
|
||||
depends_on:
|
||||
- "vmstorage-1"
|
||||
- "vmstorage-2"
|
||||
@@ -94,7 +94,7 @@ services:
|
||||
restart: always
|
||||
vmselect-2:
|
||||
container_name: vmselect-2
|
||||
image: victoriametrics/vmselect:v1.111.0-cluster
|
||||
image: victoriametrics/vmselect:v1.110.0-cluster
|
||||
depends_on:
|
||||
- "vmstorage-1"
|
||||
- "vmstorage-2"
|
||||
@@ -112,7 +112,7 @@ services:
|
||||
# It can be used as an authentication proxy.
|
||||
vmauth:
|
||||
container_name: vmauth
|
||||
image: victoriametrics/vmauth:v1.111.0
|
||||
image: victoriametrics/vmauth:v1.110.0
|
||||
depends_on:
|
||||
- "vmselect-1"
|
||||
- "vmselect-2"
|
||||
@@ -127,7 +127,7 @@ services:
|
||||
# vmalert executes alerting and recording rules
|
||||
vmalert:
|
||||
container_name: vmalert
|
||||
image: victoriametrics/vmalert:v1.111.0
|
||||
image: victoriametrics/vmalert:v1.110.0
|
||||
depends_on:
|
||||
- "vmauth"
|
||||
ports:
|
||||
|
||||
@@ -44,7 +44,7 @@ services:
|
||||
# storing logs and serving read queries.
|
||||
victorialogs:
|
||||
container_name: victorialogs
|
||||
image: victoriametrics/victoria-logs:v1.10.0-victorialogs
|
||||
image: victoriametrics/victoria-logs:v1.8.0-victorialogs
|
||||
command:
|
||||
- "--storageDataPath=/vlogs"
|
||||
- "--httpListenAddr=:9428"
|
||||
@@ -59,7 +59,7 @@ services:
|
||||
# scraping, storing metrics and serve read requests.
|
||||
victoriametrics:
|
||||
container_name: victoriametrics
|
||||
image: victoriametrics/victoria-metrics:v1.111.0
|
||||
image: victoriametrics/victoria-metrics:v1.110.0
|
||||
ports:
|
||||
- 8428:8428
|
||||
volumes:
|
||||
@@ -78,7 +78,7 @@ services:
|
||||
# depending on the requested path.
|
||||
vmauth:
|
||||
container_name: vmauth
|
||||
image: victoriametrics/vmauth:v1.111.0
|
||||
image: victoriametrics/vmauth:v1.110.0
|
||||
depends_on:
|
||||
- "victoriametrics"
|
||||
- "victorialogs"
|
||||
@@ -95,7 +95,7 @@ services:
|
||||
# vmalert executes alerting and recording rules according to given rule type.
|
||||
vmalert:
|
||||
container_name: vmalert
|
||||
image: victoriametrics/vmalert:v1.111.0
|
||||
image: victoriametrics/vmalert:v1.110.0
|
||||
depends_on:
|
||||
- "vmauth"
|
||||
- "alertmanager"
|
||||
|
||||
@@ -4,7 +4,7 @@ services:
|
||||
# And forward them to --remoteWrite.url
|
||||
vmagent:
|
||||
container_name: vmagent
|
||||
image: victoriametrics/vmagent:v1.111.0
|
||||
image: victoriametrics/vmagent:v1.110.0
|
||||
depends_on:
|
||||
- "victoriametrics"
|
||||
ports:
|
||||
@@ -22,7 +22,7 @@ services:
|
||||
# storing metrics and serve read requests.
|
||||
victoriametrics:
|
||||
container_name: victoriametrics
|
||||
image: victoriametrics/victoria-metrics:v1.111.0
|
||||
image: victoriametrics/victoria-metrics:v1.110.0
|
||||
ports:
|
||||
- 8428:8428
|
||||
- 8089:8089
|
||||
@@ -65,7 +65,7 @@ services:
|
||||
# vmalert executes alerting and recording rules
|
||||
vmalert:
|
||||
container_name: vmalert
|
||||
image: victoriametrics/vmalert:v1.111.0
|
||||
image: victoriametrics/vmalert:v1.110.0
|
||||
depends_on:
|
||||
- "victoriametrics"
|
||||
- "alertmanager"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
services:
|
||||
# meta service will be ignored by compose
|
||||
.victorialogs:
|
||||
image: docker.io/victoriametrics/victoria-logs:v1.10.0-victorialogs
|
||||
image: docker.io/victoriametrics/victoria-logs:v1.8.0-victorialogs
|
||||
command:
|
||||
- -storageDataPath=/vlogs
|
||||
- -loggerFormat=json
|
||||
@@ -19,7 +19,7 @@ services:
|
||||
retries: 10
|
||||
|
||||
dd-proxy:
|
||||
image: docker.io/victoriametrics/vmauth:v1.111.0
|
||||
image: docker.io/victoriametrics/vmauth:v1.110.0
|
||||
restart: on-failure
|
||||
volumes:
|
||||
- ./:/etc/vmauth
|
||||
@@ -45,7 +45,7 @@ services:
|
||||
replicas: 0
|
||||
|
||||
victoriametrics:
|
||||
image: victoriametrics/victoria-metrics:v1.111.0
|
||||
image: victoriametrics/victoria-metrics:v1.110.0
|
||||
ports:
|
||||
- '8428:8428'
|
||||
command:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
services:
|
||||
vmagent:
|
||||
container_name: vmagent
|
||||
image: victoriametrics/vmagent:v1.111.0
|
||||
image: victoriametrics/vmagent:v1.110.0
|
||||
depends_on:
|
||||
- "victoriametrics"
|
||||
ports:
|
||||
@@ -18,7 +18,7 @@ services:
|
||||
|
||||
victoriametrics:
|
||||
container_name: victoriametrics
|
||||
image: victoriametrics/victoria-metrics:v1.111.0
|
||||
image: victoriametrics/victoria-metrics:v1.110.0
|
||||
ports:
|
||||
- 8428:8428
|
||||
volumes:
|
||||
@@ -50,7 +50,7 @@ services:
|
||||
|
||||
vmalert:
|
||||
container_name: vmalert
|
||||
image: victoriametrics/vmalert:v1.111.0
|
||||
image: victoriametrics/vmalert:v1.110.0
|
||||
depends_on:
|
||||
- "victoriametrics"
|
||||
ports:
|
||||
|
||||
@@ -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.10.0-victorialogs
|
||||
image: docker.io/victoriametrics/victoria-logs:v1.8.0-victorialogs
|
||||
volumes:
|
||||
- vlogs:/vlogs
|
||||
ports:
|
||||
|
||||
@@ -1602,7 +1602,7 @@ Below is the output for `/path/to/vmselect -help`:
|
||||
-search.maxLabelsAPISeries int
|
||||
The maximum number of time series, which could be scanned when searching for the matching time series at /api/v1/labels and /api/v1/label/.../values. This option allows limiting memory usage and CPU usage. See also -search.maxLabelsAPIDuration, -search.maxTagKeys, -search.maxTagValues and -search.ignoreExtraFiltersAtLabelsAPI (default 1000000)
|
||||
-search.maxLookback duration
|
||||
Synonym to -query.lookback-delta from Prometheus. The value is dynamically detected from interval between time series datapoints if not set. It can be overridden on per-query basis via max_lookback arg. See also '-search.maxStalenessInterval' flag, which has the same meaning due to historical reasons
|
||||
Synonym to -search.lookback-delta from Prometheus. The value is dynamically detected from interval between time series datapoints if not set. It can be overridden on per-query basis via max_lookback arg. See also '-search.maxStalenessInterval' flag, which has the same meaning due to historical reasons
|
||||
-search.maxMemoryPerQuery size
|
||||
The maximum amounts of memory a single query may consume. Queries requiring more memory are rejected. The total memory limit for concurrently executed queries can be estimated as -search.maxMemoryPerQuery multiplied by -search.maxConcurrentRequests . See also -search.logQueryMemoryUsage
|
||||
Supports the following optional suffixes for size values: KB, MB, GB, TB, KiB, MiB, GiB, TiB (default 0)
|
||||
|
||||
@@ -22,5 +22,5 @@ to [the latest available releases](https://docs.victoriametrics.com/changelog/).
|
||||
|
||||
## Currently supported LTS release lines
|
||||
|
||||
- v1.110.x - the latest one is [v1.110.1 LTS release](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.110.1)
|
||||
- v1.102.x - the latest one is [v1.102.13 LTS release](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.102.13)
|
||||
- v1.102.x - the latest one is [v1.102.12 LTS release](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.102.12)
|
||||
- v1.97.x - the latest one is [v1.97.17 LTS release](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.97.17)
|
||||
|
||||
@@ -55,8 +55,8 @@ under the current directory:
|
||||
|
||||
|
||||
```sh
|
||||
docker pull victoriametrics/victoria-metrics:v1.111.0
|
||||
docker run -it --rm -v `pwd`/victoria-metrics-data:/victoria-metrics-data -p 8428:8428 victoriametrics/victoria-metrics:v1.111.0
|
||||
docker pull victoriametrics/victoria-metrics:v1.110.0
|
||||
docker run -it --rm -v `pwd`/victoria-metrics-data:/victoria-metrics-data -p 8428:8428 victoriametrics/victoria-metrics:v1.110.0
|
||||
```
|
||||
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user